Repository: common-workflow-language/cwltool

Issue:
TypeError: 'NoneType' object is not iterable
https://github.com/ska-sa/den/tree/1c9e1f28bd06c07e808a82fd86f0cc25d08be251

```
$ cwltool --debug main.cwl
cwltool 1.0.20180330141240
Resolved 'main.cwl' to 'file:///Users/gijs/Work/den/main.cwl'
I'm sorry, I couldn't load this CWL file.  The error was:
Traceback (most recent call last):
[...]
  File "/Users/gijs/Work/den/.venv2/lib/python2.7/site-packages/cwltool/load_tool.py", line 126, in _convert_stdstreams_to_files
    for out in workflowobj.get('outputs', []):
TypeError: 'NoneType' object is not iterable
```




Discussion:

Resolving patch:
diff --git a/cwltool/load_tool.py b/cwltool/load_tool.py
--- a/cwltool/load_tool.py
+++ b/cwltool/load_tool.py
@@ -123,24 +123,30 @@ def _convert_stdstreams_to_files(workflowobj):
 
     if isinstance(workflowobj, dict):
         if workflowobj.get('class') == 'CommandLineTool':
-            for out in workflowobj.get('outputs', []):
-                if type(out) is not CommentedMap:
-                    with SourceLine(workflowobj, "outputs", ValidationException, _logger.isEnabledFor(logging.DEBUG)):
-                        raise ValidationException("Output '%s' is not a valid OutputParameter." % out)
-                for streamtype in ['stdout', 'stderr']:
-                    if out.get('type') == streamtype:
-                        if 'outputBinding' in out:
-                            raise ValidationException(
-                                "Not allowed to specify outputBinding when"
-                                " using %s shortcut." % streamtype)
-                        if streamtype in workflowobj:
-                            filename = workflowobj[streamtype]
-                        else:
-                            filename = Text(hashlib.sha1(json.dumps(workflowobj,
-                                        sort_keys=True).encode('utf-8')).hexdigest())
-                            workflowobj[streamtype] = filename
-                        out['type'] = 'File'
-                        out['outputBinding'] = cmap({'glob': filename})
+            with SourceLine(workflowobj, "outputs", ValidationException,
+                            _logger.isEnabledFor(logging.DEBUG)):
+                outputs = workflowobj.get('outputs', [])
+                if not isinstance(outputs, CommentedSeq):
+                    raise ValidationException('"outputs" section is not '
+                            'valid.')
+                for out in workflowobj.get('outputs', []):
+                    if type(out) is not CommentedMap:
+                        raise ValidationException("Output '{}' is not a "
+                                    "valid OutputParameter.".format(out))
+                    for streamtype in ['stdout', 'stderr']:
+                        if out.get('type') == streamtype:
+                            if 'outputBinding' in out:
+                                raise ValidationException(
+                                    "Not allowed to specify outputBinding when"
+                                    " using %s shortcut." % streamtype)
+                            if streamtype in workflowobj:
+                                filename = workflowobj[streamtype]
+                            else:
+                                filename = Text(hashlib.sha1(json.dumps(workflowobj,
+                                            sort_keys=True).encode('utf-8')).hexdigest())
+                                workflowobj[streamtype] = filename
+                            out['type'] = 'File'
+                            out['outputBinding'] = cmap({'glob': filename})
             for inp in workflowobj.get('inputs', []):
                 if inp.get('type') == 'stdin':
                     if 'inputBinding' in inp:
@@ -219,9 +225,11 @@ def validate_document(document_loader,  # type: Loader
             workflowobj['cwlVersion'] = metadata['cwlVersion']
         else:
             raise ValidationException(
-                  "No cwlVersion found. "
-                  "Use the following syntax in your CWL document to declare the version: cwlVersion: <version>.\n"
-                  "Note: if this is a CWL draft-2 (pre v1.0) document then it will need to be upgraded first.")
+                "No cwlVersion found. "
+                "Use the following syntax in your CWL document to declare "
+                "the version: cwlVersion: <version>.\n"
+                "Note: if this is a CWL draft-2 (pre v1.0) document then it "
+                "will need to be upgraded first.")
 
     if not isinstance(workflowobj["cwlVersion"], (str, Text)):
         raise Exception("'cwlVersion' must be a string, got %s" % type(workflowobj["cwlVersion"]))


Test patch:
diff --git a/tests/echo_broken_outputs.cwl b/tests/echo_broken_outputs.cwl
new file mode 100755
--- /dev/null
+++ b/tests/echo_broken_outputs.cwl
@@ -0,0 +1,10 @@
+#!/usr/bin/env cwl-runner
+cwlVersion: v1.0
+class: CommandLineTool
+inputs:
+  - id: inp
+    type: string
+    inputBinding: {}
+baseCommand: echo
+stdout: out.txt
+outputs:
diff --git a/tests/test_examples.py b/tests/test_examples.py
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -138,6 +138,11 @@ def test_factory(self):
         echo = f.make(get_data("tests/echo.cwl"))
         self.assertEqual(echo(inp="foo"), {"out": "foo\n"})
 
+    def test_factory_bad_outputs(self):
+        f = cwltool.factory.Factory()
+        with self.assertRaises(schema_salad.validate.ValidationException):
+            echo = f.make(get_data("tests/echo_broken_outputs.cwl"))
+
     def test_default_args(self):
         f = cwltool.factory.Factory()
         assert f.execkwargs["use_container"] is True