Repository: PrefectHQ/prefect

Issue:
Mapping w/ SKIP signals doesn't work as intended
Consider the following flow:
```python
from prefect import *

@task
def my_list():
    return [1, 2, 3]

@task
def add(x):
    if x == 1:
        raise engine.signals.SKIP("One is no good")
    else:
        return x + 1

with Flow() as f:
    res = add.map(add.map(my_list))

flow_state = f.run(return_tasks=f.tasks)
```

We expect `flow_state.result[res]` to be a list with 1 Skipped state and 2 success states with result values 4 and 5.  However, instead, the entire second mapping is skipped:
```python
flow_state.result
{<Task: add>: [Skipped("One is no good"),
  Success("Task run succeeded."),
  Success("Task run succeeded.")],
 <Task: my_list>: Success("Task run succeeded."),
 <Task: add>: Skipped("Upstream task was skipped; if this was not the intended behavior, consider changing `skip_on_upstream_skip=False` for this task.")}
```

**cc:** @jlowin 


Discussion:
I believe this is happening because all _three_ of the upstream mapped tasks are considered upstream of the final task and we have a hard stop on Skips; interestingly, `FAILED` states propagate correctly.
Ah ha, we ignore triggers if the task is the parent mapped task, but we don't ignore the skipped upstream check!
This definitely raises the question of what should happen for the other checks as well, just to be safe

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
@@ -255,7 +255,7 @@ def run(
 
                 # check if any upstream tasks skipped (and if we need to skip)
                 state = self.check_upstream_skipped(
-                    state, upstream_states_set=upstream_states_set
+                    state, upstream_states_set=upstream_states_set, mapped=mapped
                 )
 
                 # check if the task's trigger passes
@@ -380,7 +380,7 @@ def check_upstream_finished(
 
     @call_state_handlers
     def check_upstream_skipped(
-        self, state: State, upstream_states_set: Set[State]
+        self, state: State, upstream_states_set: Set[State], mapped: bool = False
     ) -> State:
         """
         Checks if any of the upstream tasks have skipped.
@@ -388,12 +388,16 @@ def check_upstream_skipped(
         Args:
             - state (State): the current state of this task
             - upstream_states_set: a set containing the states of any upstream tasks.
+            - mapped (bool): whether this task represents a parent mapped task,
+                in which case this check will be skipped
 
         Returns:
             - State: the state of the task after running the check
         """
-        if self.task.skip_on_upstream_skip and any(
-            s.is_skipped() for s in upstream_states_set
+        if (
+            not mapped
+            and self.task.skip_on_upstream_skip
+            and any(s.is_skipped() for s in upstream_states_set)
         ):
             raise ENDRUN(
                 state=Skipped(
@@ -581,7 +585,9 @@ def check_upstreams_for_mapping(
 
         ## no inputs provided
         if not mapped_upstreams:
-            raise ENDRUN(state=Skipped(message="No inputs provided to map over."))
+            raise ENDRUN(
+                state=Skipped(message="No inputs provided to map over.", result=[])
+            )
 
         iterable_values = []
         for value in mapped_upstreams:
@@ -594,7 +600,9 @@ def check_upstreams_for_mapping(
 
         ## check that no upstream values are empty
         if any([len(v) == 0 for v in iterable_values]):
-            raise ENDRUN(state=Skipped(message="Empty inputs provided to map over."))
+            raise ENDRUN(
+                state=Skipped(message="Empty inputs provided to map over.", result=[])
+            )
 
         return 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
@@ -178,6 +178,31 @@ def test_map_failures_dont_leak_out(executor):
     assert isinstance(slist[0], prefect.engine.state.TriggerFailed)
 
 
+@pytest.mark.parametrize(
+    "executor", ["local", "sync", "mproc", "mthread"], indirect=True
+)
+def test_map_skips_dont_leak_out(executor):
+    ll = ListTask()
+
+    @task
+    def add(x):
+        if x == 1:
+            raise prefect.engine.signals.SKIP("One is no good")
+        else:
+            return x + 1
+
+    with Flow() as f:
+        res = add.map(add.map(ll))
+
+    s = f.run(return_tasks=f.tasks, executor=executor)
+    slist = s.result[res]
+    assert s.is_successful()
+    assert isinstance(slist, list)
+    assert len(slist) == 3
+    assert [r.result for r in slist][1:] == [4, 5]
+    assert isinstance(slist[0], prefect.engine.state.Skipped)
+
+
 @pytest.mark.parametrize(
     "executor", ["local", "sync", "mproc", "mthread"], indirect=True
 )