Repository: PrefectHQ/prefect

Issue:
Downstream mapped task attempts to run in skipped ifelse branch
In the following flow, the print_item mapped task attempts to run even though the make_list task gets skipped:

```python
from prefect import Flow, Parameter, task
from prefect.tasks.control_flow import ifelse


@task
def is_none(x):
    return x is None


@task
def make_list(): 
    return [3, 2, 1]


@task
def print_item(i):
    print(i)


@task
def none_printer():
    print('Param was none')


with Flow('mapped branch') as flow:
    param = Parameter('param', required=False)
    make_list = make_list()
    ifelse(is_none(param), none_printer(), make_list)
    print_item.map(i=make_list)


state = flow.run()
```

Output:
```
[2019-05-23 16:03:27,751] INFO - prefect.FlowRunner | Beginning Flow run for 'mapped branch'
[2019-05-23 16:03:27,752] INFO - prefect.FlowRunner | Starting flow run.
[2019-05-23 16:03:27,756] INFO - prefect.TaskRunner | Task 'param': Starting task run...
[2019-05-23 16:03:27,757] INFO - prefect.TaskRunner | Task 'param': finished task run for task with final state: 'Success'
[2019-05-23 16:03:27,757] INFO - prefect.TaskRunner | Task 'is_none': Starting task run...
[2019-05-23 16:03:27,757] INFO - prefect.TaskRunner | Task 'is_none': finished task run for task with final state: 'Success'
[2019-05-23 16:03:27,758] INFO - prefect.TaskRunner | Task 'CompareValue: "True"': Starting task run...
[2019-05-23 16:03:27,758] INFO - prefect.TaskRunner | Task 'CompareValue: "True"': finished task run for task with final state: 'Success'
[2019-05-23 16:03:27,758] INFO - prefect.TaskRunner | Task 'none_printer': Starting task run...
Param was none
[2019-05-23 16:03:27,759] INFO - prefect.TaskRunner | Task 'none_printer': finished task run for task with final state: 'Success'
[2019-05-23 16:03:27,759] INFO - prefect.TaskRunner | Task 'CompareValue: "False"': Starting task run...
[2019-05-23 16:03:27,759] INFO - prefect.TaskRunner | Task 'CompareValue: "False"': finished task run for task with final state: 'Skipped'
[2019-05-23 16:03:27,760] INFO - prefect.TaskRunner | Task 'make_list': Starting task run...
[2019-05-23 16:03:27,760] INFO - prefect.TaskRunner | Task 'make_list': finished task run for task with final state: 'Skipped'
[2019-05-23 16:03:27,760] INFO - prefect.TaskRunner | Task 'print_item': Starting task run...
[2019-05-23 16:03:27,760] INFO - prefect.TaskRunner | Unexpected error: TypeError("'NoResultType' object does not support indexing",)
[2019-05-23 16:03:27,760] INFO - prefect.TaskRunner | Task 'print_item': finished task run for task with final state: 'Failed'
[2019-05-23 16:03:27,762] INFO - prefect.FlowRunner | Flow run FAILED: some reference tasks failed.
```


Discussion:
Viz for posterity

![tmpg2vfna40](https://user-images.githubusercontent.com/2774241/58275441-09227c00-7d63-11e9-9780-fa40c636f528.png)


Resolving patch:
diff --git a/src/prefect/engine/task_runner.py b/src/prefect/engine/task_runner.py
--- a/src/prefect/engine/task_runner.py
+++ b/src/prefect/engine/task_runner.py
@@ -216,6 +216,11 @@ def run(
                     state, upstream_states=upstream_states
                 )
 
+                # check if any upstream tasks skipped (and if we need to skip)
+                state = self.check_upstream_skipped(
+                    state, upstream_states=upstream_states
+                )
+
                 # if the task is mapped, process the mapped children and exit
                 if mapped:
                     state = self.run_mapped_task(
@@ -234,11 +239,6 @@ def run(
                     )
                     raise ENDRUN(state)
 
-                # check if any upstream tasks skipped (and if we need to skip)
-                state = self.check_upstream_skipped(
-                    state, upstream_states=upstream_states
-                )
-
                 # retrieve task inputs from upstream and also explicitly passed inputs
                 task_inputs = self.get_task_inputs(
                     state=state, upstream_states=upstream_states
@@ -336,8 +336,12 @@ def check_upstream_skipped(
         """
 
         all_states = set()  # type: Set[State]
-        for upstream_state in upstream_states.values():
-            if isinstance(upstream_state, Mapped):
+        for edge, upstream_state in upstream_states.items():
+
+            # if the upstream state is Mapped, and this task is also mapped,
+            # we want each individual child to determine if it should
+            # skip or not based on its upstream parent in the mapping
+            if isinstance(upstream_state, Mapped) and not edge.mapped:
                 all_states.update(upstream_state.map_states)
             else:
                 all_states.add(upstream_state)


Test patch:
diff --git a/tests/core/test_task_map.py b/tests/core/test_task_map.py
--- a/tests/core/test_task_map.py
+++ b/tests/core/test_task_map.py
@@ -222,6 +222,50 @@ def add(x):
     assert isinstance(m.map_states[0], prefect.engine.state.Skipped)
 
 
+@pytest.mark.parametrize(
+    "executor", ["local", "sync", "mproc", "mthread"], indirect=True
+)
+def test_upstream_skip_signals_are_handled_properly(executor):
+    @task
+    def skip_task():
+        raise prefect.engine.signals.SKIP("Not going to run.")
+
+    @task
+    def add(x):
+        return x + 1
+
+    with Flow(name="test") as f:
+        res = add.map(skip_task)
+
+    s = f.run(executor=executor)
+    m = s.result[res]
+    assert s.is_successful()
+    assert m.is_skipped()
+
+
+@pytest.mark.parametrize(
+    "executor", ["local", "sync", "mproc", "mthread"], indirect=True
+)
+def test_upstream_skipped_states_are_handled_properly(executor):
+    @task
+    def skip_task():
+        pass
+
+    @task
+    def add(x):
+        return x + 1
+
+    with Flow(name="test") as f:
+        res = add.map(skip_task)
+
+    s = f.run(
+        executor=executor, task_states={skip_task: prefect.engine.state.Skipped()}
+    )
+    m = s.result[res]
+    assert s.is_successful()
+    assert m.is_skipped()
+
+
 @pytest.mark.parametrize(
     "executor", ["local", "sync", "mproc", "mthread"], indirect=True
 )
diff --git a/tests/engine/test_task_runner.py b/tests/engine/test_task_runner.py
--- a/tests/engine/test_task_runner.py
+++ b/tests/engine/test_task_runner.py
@@ -451,6 +451,26 @@ def test_doesnt_raise_with_skipped_and_flag_set(self):
         )
         assert new_state is state
 
+    def test_raises_if_single_mapped_upstream_skipped(self):
+        state = Pending()
+        task = Task()
+        with pytest.raises(ENDRUN) as exc:
+            edge = Edge(1, 2, mapped=False)
+            new_state = TaskRunner(task).check_upstream_skipped(
+                state=state,
+                upstream_states={edge: Mapped(map_states=[Skipped(), Success()])},
+            )
+
+    def test_doesnt_raise_if_single_mapped_upstream_skipped_and_edge_is_mapped(self):
+        state = Pending()
+        task = Task()
+        edge = Edge(1, 2, mapped=True)
+        new_state = TaskRunner(task).check_upstream_skipped(
+            state=state,
+            upstream_states={edge: Mapped(map_states=[Skipped(), Success()])},
+        )
+        assert new_state is state
+
 
 class TestCheckTaskTrigger:
     def test_all_successful_pass(self):