Repository: DataBiosphere/toil

Issue:
Conditional workflows that evaluate to false log a warning about pickValue if not specified.
In issue 3990, I also see a warning posted from the toil-cwl-runner that says you need to have pickValue set to handle null outputs.  

This is not warning for cwltool.  

See issue 3990 for a use case.
```
You had a conditional step that did not run, but you did not use pickValue to handle the skipped input.
{
    "out1": null
}
```

In general, the behavior for outputs is expected.

┆Issue is synchronized with this [Jira Task](https://ucsc-cgl.atlassian.net/browse/TOIL-1122)
┆friendlyId: TOIL-1122



Discussion:

Resolving patch:
diff --git a/src/toil/cwl/cwltoil.py b/src/toil/cwl/cwltoil.py
--- a/src/toil/cwl/cwltoil.py
+++ b/src/toil/cwl/cwltoil.py
@@ -329,12 +329,12 @@ def resolve(self) -> Any:
         if isinstance(self.promise_tuples, list):
             result = self.link_merge(
                 cast(
-                    CWLObjectType, [v[1][v[0]] for v in self.promise_tuples]  # type: ignore[index]
+                    CWLObjectType, [rv[name] for name, rv in self.promise_tuples]  # type: ignore[index]
                 )
             )
         else:
-            value = self.promise_tuples
-            result = cast(Dict[str, Any], value[1]).get(value[0])
+            name, rv = self.promise_tuples
+            result = cast(Dict[str, Any], rv).get(name)
 
         result = self.pick_value(result)
         result = filter_skip_null(self.name, result)
@@ -380,6 +380,9 @@ def pick_value(self, values: Union[List[Union[str, SkipNull]], Any]) -> Any:
         if pick_value_type is None:
             return values
 
+        if isinstance(values, SkipNull):
+            return None
+
         if not isinstance(values, list):
             logger.warning("pickValue used but input %s is not a list." % self.name)
             return values


Test patch:
diff --git a/src/toil/test/cwl/conditional_wf.cwl b/src/toil/test/cwl/conditional_wf.cwl
new file mode 100644
--- /dev/null
+++ b/src/toil/test/cwl/conditional_wf.cwl
@@ -0,0 +1,25 @@
+class: Workflow
+cwlVersion: v1.2
+
+inputs:
+  message: string
+  sleep: int
+outputs:
+  out1:
+    type: File
+    outputSource:
+      - echo/echoOut
+    pickValue: first_non_null
+
+requirements:
+  InlineJavascriptRequirement: {}
+
+steps:
+  echo:
+    in:
+      message: message
+      sleep: sleep
+    run: echo.cwl
+    out: [echoOut]
+    when: $(inputs.sleep > 1)
+
diff --git a/src/toil/test/cwl/conditional_wf.yaml b/src/toil/test/cwl/conditional_wf.yaml
new file mode 100644
--- /dev/null
+++ b/src/toil/test/cwl/conditional_wf.yaml
@@ -0,0 +1,2 @@
+sleep: 1
+message: hello
diff --git a/src/toil/test/cwl/cwlTest.py b/src/toil/test/cwl/cwlTest.py
--- a/src/toil/test/cwl/cwlTest.py
+++ b/src/toil/test/cwl/cwlTest.py
@@ -1125,6 +1125,25 @@ def test_filename_conflict_resolution(tmp_path: Path):
     assert p.returncode == 0
 
 
+@needs_cwl
+@pytest.mark.cwl_small
+def test_pick_value_with_one_null_value(caplog):
+    """
+    Make sure toil-cwl-runner does not false log a warning when pickValue is
+    used but outputSource only contains one null value. See: #3991.
+    """
+    from toil.cwl import cwltoil
+
+    cwl_file = os.path.join(os.path.dirname(__file__), "conditional_wf.cwl")
+    job_file = os.path.join(os.path.dirname(__file__), "conditional_wf.yaml")
+    args = [cwl_file, job_file]
+
+    with caplog.at_level(logging.WARNING, logger="toil.cwl.cwltoil"):
+        cwltoil.main(args)
+        for line in caplog.messages:
+            assert "You had a conditional step that did not run, but you did not use pickValue to handle the skipped input." not in line
+
+
 @needs_cwl
 @pytest.mark.cwl_small
 def test_usage_message():
diff --git a/src/toil/test/cwl/echo.cwl b/src/toil/test/cwl/echo.cwl
new file mode 100644
--- /dev/null
+++ b/src/toil/test/cwl/echo.cwl
@@ -0,0 +1,12 @@
+cwlVersion: v1.2
+class: CommandLineTool
+baseCommand: [echo]
+id: "echo"
+inputs: 
+  message: 
+    type: string
+    inputBinding:
+      position: 1
+outputs: 
+  echoOut:
+    type: stdout