13493: Merge branch 'master' into 13493-federation-proxy
[arvados.git] / sdk / cwl / tests / test_submit.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 import copy
6 import cStringIO
7 import functools
8 import hashlib
9 import json
10 import logging
11 import mock
12 import sys
13 import unittest
14
15 import arvados
16 import arvados.collection
17 import arvados_cwl
18 import arvados_cwl.runner
19 import arvados.keep
20
21 from .matcher import JsonDiffMatcher, StripYAMLComments
22 from .mock_discovery import get_rootDesc
23
24 import ruamel.yaml as yaml
25
26 _rootDesc = None
27
28 def stubs(func):
29     @functools.wraps(func)
30     @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
31     @mock.patch("arvados.collection.KeepClient")
32     @mock.patch("arvados.keep.KeepClient")
33     @mock.patch("arvados.events.subscribe")
34     def wrapped(self, events, keep_client1, keep_client2, keepdocker, *args, **kwargs):
35         class Stubs:
36             pass
37         stubs = Stubs()
38         stubs.events = events
39         stubs.keepdocker = keepdocker
40
41         def putstub(p, **kwargs):
42             return "%s+%i" % (hashlib.md5(p).hexdigest(), len(p))
43         keep_client1().put.side_effect = putstub
44         keep_client1.put.side_effect = putstub
45         keep_client2().put.side_effect = putstub
46         keep_client2.put.side_effect = putstub
47
48         stubs.keep_client = keep_client2
49         stubs.keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
50         stubs.fake_user_uuid = "zzzzz-tpzed-zzzzzzzzzzzzzzz"
51
52         stubs.api = mock.MagicMock()
53         stubs.api._rootDesc = get_rootDesc()
54
55         stubs.api.users().current().execute.return_value = {
56             "uuid": stubs.fake_user_uuid,
57         }
58         stubs.api.collections().list().execute.return_value = {"items": []}
59
60         class CollectionExecute(object):
61             def __init__(self, exe):
62                 self.exe = exe
63             def execute(self, num_retries=None):
64                 return self.exe
65
66         def collection_createstub(created_collections, body, ensure_unique_name=None):
67             mt = body["manifest_text"]
68             uuid = "zzzzz-4zz18-zzzzzzzzzzzzzz%d" % len(created_collections)
69             pdh = "%s+%i" % (hashlib.md5(mt).hexdigest(), len(mt))
70             created_collections[uuid] = {
71                 "uuid": uuid,
72                 "portable_data_hash": pdh,
73                 "manifest_text": mt
74             }
75             return CollectionExecute(created_collections[uuid])
76
77         def collection_getstub(created_collections, uuid):
78             for v in created_collections.itervalues():
79                 if uuid in (v["uuid"], v["portable_data_hash"]):
80                     return CollectionExecute(v)
81
82         created_collections = {
83             "99999999999999999999999999999998+99": {
84                 "uuid": "",
85                 "portable_data_hash": "99999999999999999999999999999998+99",
86                 "manifest_text": ". 99999999999999999999999999999998+99 0:0:file1.txt"
87             },
88             "99999999999999999999999999999994+99": {
89                 "uuid": "",
90                 "portable_data_hash": "99999999999999999999999999999994+99",
91                 "manifest_text": ". 99999999999999999999999999999994+99 0:0:expect_arvworkflow.cwl"
92             }
93         }
94         stubs.api.collections().create.side_effect = functools.partial(collection_createstub, created_collections)
95         stubs.api.collections().get.side_effect = functools.partial(collection_getstub, created_collections)
96
97         stubs.expect_job_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
98         stubs.api.jobs().create().execute.return_value = {
99             "uuid": stubs.expect_job_uuid,
100             "state": "Queued",
101         }
102
103         stubs.expect_container_request_uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzzz"
104         stubs.api.container_requests().create().execute.return_value = {
105             "uuid": stubs.expect_container_request_uuid,
106             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
107             "state": "Queued"
108         }
109
110         stubs.expect_pipeline_template_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
111         stubs.api.pipeline_templates().create().execute.return_value = {
112             "uuid": stubs.expect_pipeline_template_uuid,
113         }
114         stubs.expect_job_spec = {
115             'runtime_constraints': {
116                 'docker_image': 'arvados/jobs:'+arvados_cwl.__version__,
117                 'min_ram_mb_per_node': 1024
118             },
119             'script_parameters': {
120                 'x': {
121                     'basename': 'blorp.txt',
122                     'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
123                     'class': 'File'
124                 },
125                 'y': {
126                     'basename': '99999999999999999999999999999998+99',
127                     'location': 'keep:99999999999999999999999999999998+99',
128                     'class': 'Directory'
129                 },
130                 'z': {
131                     'basename': 'anonymous',
132                     "listing": [{
133                         "basename": "renamed.txt",
134                         "class": "File",
135                         "location": "keep:99999999999999999999999999999998+99/file1.txt"
136                     }],
137                     'class': 'Directory'
138                 },
139                 'cwl:tool': '3fffdeaa75e018172e1b583425f4ebff+60/workflow.cwl#main'
140             },
141             'repository': 'arvados',
142             'script_version': 'master',
143             'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
144             'script': 'cwl-runner'
145         }
146         stubs.pipeline_component = stubs.expect_job_spec.copy()
147         stubs.expect_pipeline_instance = {
148             'name': 'submit_wf.cwl',
149             'state': 'RunningOnServer',
150             'owner_uuid': None,
151             "components": {
152                 "cwl-runner": {
153                     'runtime_constraints': {'docker_image': 'arvados/jobs:'+arvados_cwl.__version__, 'min_ram_mb_per_node': 1024},
154                     'script_parameters': {
155                         'y': {"value": {'basename': '99999999999999999999999999999998+99', 'location': 'keep:99999999999999999999999999999998+99', 'class': 'Directory'}},
156                         'x': {"value": {
157                             'basename': 'blorp.txt',
158                             'class': 'File',
159                             'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
160                             "size": 16
161                         }},
162                         'z': {"value": {'basename': 'anonymous', 'class': 'Directory',
163                               'listing': [
164                                   {
165                                       'basename': 'renamed.txt',
166                                       'class': 'File', 'location':
167                                       'keep:99999999999999999999999999999998+99/file1.txt'
168                                   }
169                               ]}},
170                         'cwl:tool': '3fffdeaa75e018172e1b583425f4ebff+60/workflow.cwl#main',
171                         'arv:debug': True,
172                         'arv:enable_reuse': True,
173                         'arv:on_error': 'continue'
174                     },
175                     'repository': 'arvados',
176                     'script_version': 'master',
177                     'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
178                     'script': 'cwl-runner',
179                     'job': {'state': 'Queued', 'uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}
180                 }
181             }
182         }
183         stubs.pipeline_create = copy.deepcopy(stubs.expect_pipeline_instance)
184         stubs.expect_pipeline_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
185         stubs.pipeline_create["uuid"] = stubs.expect_pipeline_uuid
186         stubs.pipeline_with_job = copy.deepcopy(stubs.pipeline_create)
187         stubs.pipeline_with_job["components"]["cwl-runner"]["job"] = {
188             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
189             "state": "Queued"
190         }
191         stubs.api.pipeline_instances().create().execute.return_value = stubs.pipeline_create
192         stubs.api.pipeline_instances().get().execute.return_value = stubs.pipeline_with_job
193
194         with open("tests/wf/submit_wf_packed.cwl") as f:
195             expect_packed_workflow = yaml.round_trip_load(f)
196
197         stubs.expect_container_spec = {
198             'priority': 500,
199             'mounts': {
200                 '/var/spool/cwl': {
201                     'writable': True,
202                     'kind': 'collection'
203                 },
204                 '/var/lib/cwl/workflow.json': {
205                     'content': expect_packed_workflow,
206                     'kind': 'json'
207                 },
208                 'stdout': {
209                     'path': '/var/spool/cwl/cwl.output.json',
210                     'kind': 'file'
211                 },
212                 '/var/lib/cwl/cwl.input.json': {
213                     'kind': 'json',
214                     'content': {
215                         'y': {
216                             'basename': '99999999999999999999999999999998+99',
217                             'location': 'keep:99999999999999999999999999999998+99',
218                             'class': 'Directory'},
219                         'x': {
220                             'basename': u'blorp.txt',
221                             'class': 'File',
222                             'location': u'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
223                             "size": 16
224                         },
225                         'z': {'basename': 'anonymous', 'class': 'Directory', 'listing': [
226                             {'basename': 'renamed.txt',
227                              'class': 'File',
228                              'location': 'keep:99999999999999999999999999999998+99/file1.txt'
229                             }
230                         ]}
231                     },
232                     'kind': 'json'
233                 }
234             },
235             'secret_mounts': {},
236             'state': 'Committed',
237             'command': ['arvados-cwl-runner', '--local', '--api=containers',
238                         '--no-log-timestamps', '--disable-validate',
239                         '--eval-timeout=20', '--thread-count=4',
240                         '--enable-reuse', '--debug', '--on-error=continue',
241                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
242             'name': 'submit_wf.cwl',
243             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
244             'output_path': '/var/spool/cwl',
245             'cwd': '/var/spool/cwl',
246             'runtime_constraints': {
247                 'API': True,
248                 'vcpus': 1,
249                 'ram': 1024*1024*1024
250             },
251             'use_existing': True,
252             'properties': {},
253             'secret_mounts': {}
254         }
255
256         stubs.expect_workflow_uuid = "zzzzz-7fd4e-zzzzzzzzzzzzzzz"
257         stubs.api.workflows().create().execute.return_value = {
258             "uuid": stubs.expect_workflow_uuid,
259         }
260         def update_mock(**kwargs):
261             stubs.updated_uuid = kwargs.get('uuid')
262             return mock.DEFAULT
263         stubs.api.workflows().update.side_effect = update_mock
264         stubs.api.workflows().update().execute.side_effect = lambda **kwargs: {
265             "uuid": stubs.updated_uuid,
266         }
267
268         return func(self, stubs, *args, **kwargs)
269     return wrapped
270
271
272 class TestSubmit(unittest.TestCase):
273     @mock.patch("arvados_cwl.runner.arv_docker_get_image")
274     @mock.patch("time.sleep")
275     @stubs
276     def test_submit(self, stubs, tm, arvdock):
277         capture_stdout = cStringIO.StringIO()
278         exited = arvados_cwl.main(
279             ["--submit", "--no-wait", "--api=jobs", "--debug",
280              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
281             capture_stdout, sys.stderr, api_client=stubs.api)
282         self.assertEqual(exited, 0)
283
284         stubs.api.collections().create.assert_has_calls([
285             mock.call(body=JsonDiffMatcher({
286                 'manifest_text':
287                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
288                 'replication_desired': None,
289                 'name': 'submit_tool.cwl dependencies',
290             }), ensure_unique_name=True),
291             mock.call(body=JsonDiffMatcher({
292                 'manifest_text':
293                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
294                 'replication_desired': None,
295                 'name': 'submit_wf.cwl input',
296             }), ensure_unique_name=True),
297             mock.call(body=JsonDiffMatcher({
298                 'manifest_text':
299                 '. 61df2ed9ee3eb7dd9b799e5ca35305fa+1217 0:1217:workflow.cwl\n',
300                 'replication_desired': None,
301                 'name': 'submit_wf.cwl',
302             }), ensure_unique_name=True)        ])
303
304         arvdock.assert_has_calls([
305             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8"}, True, None),
306             mock.call(stubs.api, {'dockerPull': 'arvados/jobs:'+arvados_cwl.__version__}, True, None)
307         ])
308
309         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
310         stubs.api.pipeline_instances().create.assert_called_with(
311             body=JsonDiffMatcher(expect_pipeline))
312         self.assertEqual(capture_stdout.getvalue(),
313                          stubs.expect_pipeline_uuid + '\n')
314
315
316     @mock.patch("time.sleep")
317     @stubs
318     def test_submit_no_reuse(self, stubs, tm):
319         capture_stdout = cStringIO.StringIO()
320         exited = arvados_cwl.main(
321             ["--submit", "--no-wait", "--api=jobs", "--debug", "--disable-reuse",
322              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
323             capture_stdout, sys.stderr, api_client=stubs.api)
324         self.assertEqual(exited, 0)
325
326         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
327         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:enable_reuse"] = {"value": False}
328         expect_pipeline["properties"] = {"run_options": {"enable_job_reuse": False}}
329
330         stubs.api.pipeline_instances().create.assert_called_with(
331             body=JsonDiffMatcher(expect_pipeline))
332         self.assertEqual(capture_stdout.getvalue(),
333                          stubs.expect_pipeline_uuid + '\n')
334
335     @stubs
336     def test_error_when_multiple_storage_classes_specified(self, stubs):
337         storage_classes = "foo,bar"
338         exited = arvados_cwl.main(
339                 ["--debug", "--storage-classes", storage_classes,
340                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
341                 sys.stdin, sys.stderr, api_client=stubs.api)
342         self.assertEqual(exited, 1)
343
344     @mock.patch("time.sleep")
345     @stubs
346     def test_submit_on_error(self, stubs, tm):
347         capture_stdout = cStringIO.StringIO()
348         exited = arvados_cwl.main(
349             ["--submit", "--no-wait", "--api=jobs", "--debug", "--on-error=stop",
350              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
351             capture_stdout, sys.stderr, api_client=stubs.api)
352         self.assertEqual(exited, 0)
353
354         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
355         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:on_error"] = "stop"
356
357         stubs.api.pipeline_instances().create.assert_called_with(
358             body=JsonDiffMatcher(expect_pipeline))
359         self.assertEqual(capture_stdout.getvalue(),
360                          stubs.expect_pipeline_uuid + '\n')
361
362
363     @mock.patch("time.sleep")
364     @stubs
365     def test_submit_runner_ram(self, stubs, tm):
366         capture_stdout = cStringIO.StringIO()
367         exited = arvados_cwl.main(
368             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=2048",
369              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
370             capture_stdout, sys.stderr, api_client=stubs.api)
371         self.assertEqual(exited, 0)
372
373         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
374         expect_pipeline["components"]["cwl-runner"]["runtime_constraints"]["min_ram_mb_per_node"] = 2048
375
376         stubs.api.pipeline_instances().create.assert_called_with(
377             body=JsonDiffMatcher(expect_pipeline))
378         self.assertEqual(capture_stdout.getvalue(),
379                          stubs.expect_pipeline_uuid + '\n')
380
381
382     @mock.patch("time.sleep")
383     @stubs
384     def test_submit_invalid_runner_ram(self, stubs, tm):
385         capture_stdout = cStringIO.StringIO()
386         exited = arvados_cwl.main(
387             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
388              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
389             capture_stdout, sys.stderr, api_client=stubs.api)
390         self.assertEqual(exited, 1)
391
392     @mock.patch("time.sleep")
393     @stubs
394     def test_submit_output_name(self, stubs, tm):
395         output_name = "test_output_name"
396
397         capture_stdout = cStringIO.StringIO()
398         exited = arvados_cwl.main(
399             ["--submit", "--no-wait", "--debug", "--output-name", output_name,
400              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
401             capture_stdout, sys.stderr, api_client=stubs.api)
402         self.assertEqual(exited, 0)
403
404         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
405         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_name"] = output_name
406
407         stubs.api.pipeline_instances().create.assert_called_with(
408             body=JsonDiffMatcher(expect_pipeline))
409         self.assertEqual(capture_stdout.getvalue(),
410                          stubs.expect_pipeline_uuid + '\n')
411
412
413     @mock.patch("time.sleep")
414     @stubs
415     def test_submit_pipeline_name(self, stubs, tm):
416         capture_stdout = cStringIO.StringIO()
417         exited = arvados_cwl.main(
418             ["--submit", "--no-wait", "--debug", "--name=hello job 123",
419              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
420             capture_stdout, sys.stderr, api_client=stubs.api)
421         self.assertEqual(exited, 0)
422
423         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
424         expect_pipeline["name"] = "hello job 123"
425
426         stubs.api.pipeline_instances().create.assert_called_with(
427             body=JsonDiffMatcher(expect_pipeline))
428         self.assertEqual(capture_stdout.getvalue(),
429                          stubs.expect_pipeline_uuid + '\n')
430
431     @mock.patch("time.sleep")
432     @stubs
433     def test_submit_output_tags(self, stubs, tm):
434         output_tags = "tag0,tag1,tag2"
435
436         capture_stdout = cStringIO.StringIO()
437         exited = arvados_cwl.main(
438             ["--submit", "--no-wait", "--debug", "--output-tags", output_tags,
439              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
440             capture_stdout, sys.stderr, api_client=stubs.api)
441         self.assertEqual(exited, 0)
442
443         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
444         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_tags"] = output_tags
445
446         stubs.api.pipeline_instances().create.assert_called_with(
447             body=JsonDiffMatcher(expect_pipeline))
448         self.assertEqual(capture_stdout.getvalue(),
449                          stubs.expect_pipeline_uuid + '\n')
450
451     @mock.patch("time.sleep")
452     @stubs
453     def test_submit_with_project_uuid(self, stubs, tm):
454         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
455
456         exited = arvados_cwl.main(
457             ["--submit", "--no-wait", "--debug",
458              "--project-uuid", project_uuid,
459              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
460             sys.stdout, sys.stderr, api_client=stubs.api)
461         self.assertEqual(exited, 0)
462
463         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
464         expect_pipeline["owner_uuid"] = project_uuid
465         stubs.api.pipeline_instances().create.assert_called_with(
466             body=JsonDiffMatcher(expect_pipeline))
467
468     @stubs
469     def test_submit_container(self, stubs):
470         capture_stdout = cStringIO.StringIO()
471         try:
472             exited = arvados_cwl.main(
473                 ["--submit", "--no-wait", "--api=containers", "--debug",
474                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
475                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
476             self.assertEqual(exited, 0)
477         except:
478             logging.exception("")
479
480         stubs.api.collections().create.assert_has_calls([
481             mock.call(body=JsonDiffMatcher({
482                 'manifest_text':
483                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
484                 'replication_desired': None,
485                 'name': 'submit_tool.cwl dependencies',
486             }), ensure_unique_name=True),
487             mock.call(body=JsonDiffMatcher({
488                 'manifest_text':
489                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
490                 'replication_desired': None,
491                 'name': 'submit_wf.cwl input',
492             }), ensure_unique_name=True)])
493
494         expect_container = copy.deepcopy(stubs.expect_container_spec)
495         stubs.api.container_requests().create.assert_called_with(
496             body=JsonDiffMatcher(expect_container))
497         self.assertEqual(capture_stdout.getvalue(),
498                          stubs.expect_container_request_uuid + '\n')
499
500     @stubs
501     def test_submit_container_no_reuse(self, stubs):
502         capture_stdout = cStringIO.StringIO()
503         try:
504             exited = arvados_cwl.main(
505                 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
506                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
507                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
508             self.assertEqual(exited, 0)
509         except:
510             logging.exception("")
511
512         expect_container = copy.deepcopy(stubs.expect_container_spec)
513         expect_container["command"] = [
514             'arvados-cwl-runner', '--local', '--api=containers',
515             '--no-log-timestamps', '--disable-validate',
516             '--eval-timeout=20', '--thread-count=4',
517             '--disable-reuse', '--debug', '--on-error=continue',
518             '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
519         expect_container["use_existing"] = False
520
521         stubs.api.container_requests().create.assert_called_with(
522             body=JsonDiffMatcher(expect_container))
523         self.assertEqual(capture_stdout.getvalue(),
524                          stubs.expect_container_request_uuid + '\n')
525
526
527     @stubs
528     def test_submit_container_reuse_disabled_by_workflow(self, stubs):
529         capture_stdout = cStringIO.StringIO()
530
531         exited = arvados_cwl.main(
532             ["--submit", "--no-wait", "--api=containers", "--debug",
533              "tests/wf/submit_wf_no_reuse.cwl", "tests/submit_test_job.json"],
534             capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
535         self.assertEqual(exited, 0)
536
537         expect_container = copy.deepcopy(stubs.expect_container_spec)
538         expect_container["command"] = [
539             'arvados-cwl-runner', '--local', '--api=containers',
540             '--no-log-timestamps', '--disable-validate',
541             '--eval-timeout=20', '--thread-count=4',
542             '--disable-reuse', '--debug', '--on-error=continue',
543             '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
544         expect_container["use_existing"] = False
545         expect_container["name"] = "submit_wf_no_reuse.cwl"
546         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
547             {
548                 "class": "http://arvados.org/cwl#ReuseRequirement",
549                 "enableReuse": False,
550             },
551         ]
552         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
553             "arv": "http://arvados.org/cwl#",
554             "cwltool": "http://commonwl.org/cwltool#"
555         }
556
557         stubs.api.container_requests().create.assert_called_with(
558             body=JsonDiffMatcher(expect_container))
559         self.assertEqual(capture_stdout.getvalue(),
560                          stubs.expect_container_request_uuid + '\n')
561
562
563     @stubs
564     def test_submit_container_on_error(self, stubs):
565         capture_stdout = cStringIO.StringIO()
566         try:
567             exited = arvados_cwl.main(
568                 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
569                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
570                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
571             self.assertEqual(exited, 0)
572         except:
573             logging.exception("")
574
575         expect_container = copy.deepcopy(stubs.expect_container_spec)
576         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
577                                        '--no-log-timestamps', '--disable-validate',
578                                        '--eval-timeout=20', '--thread-count=4',
579                                        '--enable-reuse', '--debug', '--on-error=stop',
580                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
581
582         stubs.api.container_requests().create.assert_called_with(
583             body=JsonDiffMatcher(expect_container))
584         self.assertEqual(capture_stdout.getvalue(),
585                          stubs.expect_container_request_uuid + '\n')
586
587     @stubs
588     def test_submit_container_output_name(self, stubs):
589         output_name = "test_output_name"
590
591         capture_stdout = cStringIO.StringIO()
592         try:
593             exited = arvados_cwl.main(
594                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
595                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
596                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
597             self.assertEqual(exited, 0)
598         except:
599             logging.exception("")
600
601         expect_container = copy.deepcopy(stubs.expect_container_spec)
602         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
603                                        '--no-log-timestamps', '--disable-validate',
604                                        '--eval-timeout=20', '--thread-count=4',
605                                        '--enable-reuse',
606                                        "--output-name="+output_name, '--debug', '--on-error=continue',
607                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
608         expect_container["output_name"] = output_name
609
610         stubs.api.container_requests().create.assert_called_with(
611             body=JsonDiffMatcher(expect_container))
612         self.assertEqual(capture_stdout.getvalue(),
613                          stubs.expect_container_request_uuid + '\n')
614
615     @stubs
616     def test_submit_storage_classes(self, stubs):
617         capture_stdout = cStringIO.StringIO()
618         try:
619             exited = arvados_cwl.main(
620                 ["--debug", "--submit", "--no-wait", "--api=containers", "--storage-classes=foo",
621                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
622                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
623             self.assertEqual(exited, 0)
624         except:
625             logging.exception("")
626
627         expect_container = copy.deepcopy(stubs.expect_container_spec)
628         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
629                                        '--no-log-timestamps', '--disable-validate',
630                                        '--eval-timeout=20', '--thread-count=4',
631                                        '--enable-reuse', "--debug",
632                                        "--storage-classes=foo", '--on-error=continue',
633                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
634
635         stubs.api.container_requests().create.assert_called_with(
636             body=JsonDiffMatcher(expect_container))
637         self.assertEqual(capture_stdout.getvalue(),
638                          stubs.expect_container_request_uuid + '\n')
639
640     @mock.patch("arvados_cwl.task_queue.TaskQueue")
641     @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
642     @mock.patch("arvados_cwl.ArvCwlRunner.make_output_collection", return_value = (None, None))
643     @stubs
644     def test_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
645         def set_final_output(job_order, output_callback, runtimeContext):
646             output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
647             return []
648         job.side_effect = set_final_output
649
650         try:
651             exited = arvados_cwl.main(
652                 ["--debug", "--local", "--storage-classes=foo",
653                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
654                 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
655             self.assertEqual(exited, 0)
656         except:
657             logging.exception("")
658
659         make_output.assert_called_with(u'Output of submit_wf.cwl', ['foo'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
660
661     @mock.patch("arvados_cwl.task_queue.TaskQueue")
662     @mock.patch("arvados_cwl.arvworkflow.ArvadosWorkflow.job")
663     @mock.patch("arvados_cwl.ArvCwlRunner.make_output_collection", return_value = (None, None))
664     @stubs
665     def test_default_storage_classes_correctly_propagate_to_make_output_collection(self, stubs, make_output, job, tq):
666         def set_final_output(job_order, output_callback, runtimeContext):
667             output_callback("zzzzz-4zz18-zzzzzzzzzzzzzzzz", "success")
668             return []
669         job.side_effect = set_final_output
670
671         try:
672             exited = arvados_cwl.main(
673                 ["--debug", "--local",
674                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
675                 sys.stdin, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
676             self.assertEqual(exited, 0)
677         except:
678             logging.exception("")
679
680         make_output.assert_called_with(u'Output of submit_wf.cwl', ['default'], '', 'zzzzz-4zz18-zzzzzzzzzzzzzzzz')
681
682     @stubs
683     def test_submit_container_output_ttl(self, stubs):
684         capture_stdout = cStringIO.StringIO()
685         try:
686             exited = arvados_cwl.main(
687                 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
688                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
689                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
690             self.assertEqual(exited, 0)
691         except:
692             logging.exception("")
693
694         expect_container = copy.deepcopy(stubs.expect_container_spec)
695         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
696                                        '--no-log-timestamps', '--disable-validate',
697                                        '--eval-timeout=20', '--thread-count=4',
698                                        '--enable-reuse', '--debug', '--on-error=continue',
699                                        "--intermediate-output-ttl=3600",
700                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
701
702         stubs.api.container_requests().create.assert_called_with(
703             body=JsonDiffMatcher(expect_container))
704         self.assertEqual(capture_stdout.getvalue(),
705                          stubs.expect_container_request_uuid + '\n')
706
707     @stubs
708     def test_submit_container_trash_intermediate(self, stubs):
709         capture_stdout = cStringIO.StringIO()
710         try:
711             exited = arvados_cwl.main(
712                 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
713                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
714                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
715             self.assertEqual(exited, 0)
716         except:
717             logging.exception("")
718
719         expect_container = copy.deepcopy(stubs.expect_container_spec)
720         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
721                                        '--no-log-timestamps', '--disable-validate',
722                                        '--eval-timeout=20', '--thread-count=4',
723                                        '--enable-reuse', '--debug', '--on-error=continue',
724                                        "--trash-intermediate",
725                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
726
727         stubs.api.container_requests().create.assert_called_with(
728             body=JsonDiffMatcher(expect_container))
729         self.assertEqual(capture_stdout.getvalue(),
730                          stubs.expect_container_request_uuid + '\n')
731
732     @stubs
733     def test_submit_container_output_tags(self, stubs):
734         output_tags = "tag0,tag1,tag2"
735
736         capture_stdout = cStringIO.StringIO()
737         try:
738             exited = arvados_cwl.main(
739                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
740                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
741                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
742             self.assertEqual(exited, 0)
743         except:
744             logging.exception("")
745
746         expect_container = copy.deepcopy(stubs.expect_container_spec)
747         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
748                                        '--no-log-timestamps', '--disable-validate',
749                                        '--eval-timeout=20', '--thread-count=4',
750                                        '--enable-reuse',
751                                        "--output-tags="+output_tags, '--debug', '--on-error=continue',
752                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
753
754         stubs.api.container_requests().create.assert_called_with(
755             body=JsonDiffMatcher(expect_container))
756         self.assertEqual(capture_stdout.getvalue(),
757                          stubs.expect_container_request_uuid + '\n')
758
759     @stubs
760     def test_submit_container_runner_ram(self, stubs):
761         capture_stdout = cStringIO.StringIO()
762         try:
763             exited = arvados_cwl.main(
764                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
765                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
766                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
767             self.assertEqual(exited, 0)
768         except:
769             logging.exception("")
770
771         expect_container = copy.deepcopy(stubs.expect_container_spec)
772         expect_container["runtime_constraints"]["ram"] = 2048*1024*1024
773
774         stubs.api.container_requests().create.assert_called_with(
775             body=JsonDiffMatcher(expect_container))
776         self.assertEqual(capture_stdout.getvalue(),
777                          stubs.expect_container_request_uuid + '\n')
778
779     @mock.patch("arvados.collection.CollectionReader")
780     @mock.patch("time.sleep")
781     @stubs
782     def test_submit_file_keepref(self, stubs, tm, collectionReader):
783         capture_stdout = cStringIO.StringIO()
784         exited = arvados_cwl.main(
785             ["--submit", "--no-wait", "--api=containers", "--debug",
786              "tests/wf/submit_keepref_wf.cwl"],
787             capture_stdout, sys.stderr, api_client=stubs.api)
788         self.assertEqual(exited, 0)
789
790
791     @mock.patch("arvados.collection.CollectionReader")
792     @mock.patch("time.sleep")
793     @stubs
794     def test_submit_keepref(self, stubs, tm, reader):
795         capture_stdout = cStringIO.StringIO()
796
797         with open("tests/wf/expect_arvworkflow.cwl") as f:
798             reader().open().__enter__().read.return_value = f.read()
799
800         exited = arvados_cwl.main(
801             ["--submit", "--no-wait", "--api=containers", "--debug",
802              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
803             capture_stdout, sys.stderr, api_client=stubs.api)
804         self.assertEqual(exited, 0)
805
806         expect_container = {
807             'priority': 500,
808             'mounts': {
809                 '/var/spool/cwl': {
810                     'writable': True,
811                     'kind': 'collection'
812                 },
813                 'stdout': {
814                     'path': '/var/spool/cwl/cwl.output.json',
815                     'kind': 'file'
816                 },
817                 '/var/lib/cwl/workflow': {
818                     'portable_data_hash': '99999999999999999999999999999994+99',
819                     'kind': 'collection'
820                 },
821                 '/var/lib/cwl/cwl.input.json': {
822                     'content': {
823                         'x': 'XxX'
824                     },
825                     'kind': 'json'
826                 }
827             }, 'state': 'Committed',
828             'output_path': '/var/spool/cwl',
829             'name': 'expect_arvworkflow.cwl#main',
830             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
831             'command': ['arvados-cwl-runner', '--local', '--api=containers',
832                         '--no-log-timestamps', '--disable-validate',
833                         '--eval-timeout=20', '--thread-count=4',
834                         '--enable-reuse', '--debug', '--on-error=continue',
835                         '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
836             'cwd': '/var/spool/cwl',
837             'runtime_constraints': {
838                 'API': True,
839                 'vcpus': 1,
840                 'ram': 1073741824
841             },
842             'use_existing': True,
843             'properties': {},
844             'secret_mounts': {}
845         }
846
847         stubs.api.container_requests().create.assert_called_with(
848             body=JsonDiffMatcher(expect_container))
849         self.assertEqual(capture_stdout.getvalue(),
850                          stubs.expect_container_request_uuid + '\n')
851
852
853     @mock.patch("arvados.collection.CollectionReader")
854     @mock.patch("time.sleep")
855     @stubs
856     def test_submit_jobs_keepref(self, stubs, tm, reader):
857         capture_stdout = cStringIO.StringIO()
858
859         with open("tests/wf/expect_arvworkflow.cwl") as f:
860             reader().open().__enter__().read.return_value = f.read()
861
862         exited = arvados_cwl.main(
863             ["--submit", "--no-wait", "--api=jobs", "--debug",
864              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
865             capture_stdout, sys.stderr, api_client=stubs.api)
866         self.assertEqual(exited, 0)
867
868         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
869         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["x"] = "XxX"
870         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["y"]
871         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["z"]
872         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["cwl:tool"] = "99999999999999999999999999999994+99/expect_arvworkflow.cwl#main"
873         expect_pipeline["name"] = "expect_arvworkflow.cwl#main"
874         stubs.api.pipeline_instances().create.assert_called_with(
875             body=JsonDiffMatcher(expect_pipeline))
876
877     @mock.patch("time.sleep")
878     @stubs
879     def test_submit_arvworkflow(self, stubs, tm):
880         capture_stdout = cStringIO.StringIO()
881
882         with open("tests/wf/expect_arvworkflow.cwl") as f:
883             stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
884
885         exited = arvados_cwl.main(
886             ["--submit", "--no-wait", "--api=containers", "--debug",
887              "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
888             capture_stdout, sys.stderr, api_client=stubs.api)
889         self.assertEqual(exited, 0)
890
891         expect_container = {
892             'priority': 500,
893             'mounts': {
894                 '/var/spool/cwl': {
895                     'writable': True,
896                     'kind': 'collection'
897                 },
898                 'stdout': {
899                     'path': '/var/spool/cwl/cwl.output.json',
900                     'kind': 'file'
901                 },
902                 '/var/lib/cwl/workflow.json': {
903                     'kind': 'json',
904                     'content': {
905                         'cwlVersion': 'v1.0',
906                         '$graph': [
907                             {
908                                 'id': '#main',
909                                 'inputs': [
910                                     {'type': 'string', 'id': '#main/x'}
911                                 ],
912                                 'steps': [
913                                     {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
914                                      'run': '#submit_tool.cwl',
915                                      'id': '#main/step1',
916                                      'out': []}
917                                 ],
918                                 'class': 'Workflow',
919                                 'outputs': []
920                             },
921                             {
922                                 'inputs': [
923                                     {
924                                         'inputBinding': {'position': 1},
925                                         'type': 'string',
926                                         'id': '#submit_tool.cwl/x'}
927                                 ],
928                                 'requirements': [
929                                     {'dockerPull': 'debian:8', 'class': 'DockerRequirement'}
930                                 ],
931                                 'id': '#submit_tool.cwl',
932                                 'outputs': [],
933                                 'baseCommand': 'cat',
934                                 'class': 'CommandLineTool'
935                             }
936                         ]
937                     }
938                 },
939                 '/var/lib/cwl/cwl.input.json': {
940                     'content': {
941                         'x': 'XxX'
942                     },
943                     'kind': 'json'
944                 }
945             }, 'state': 'Committed',
946             'output_path': '/var/spool/cwl',
947             'name': 'a test workflow',
948             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
949             'command': ['arvados-cwl-runner', '--local', '--api=containers',
950                         '--no-log-timestamps', '--disable-validate',
951                         '--eval-timeout=20', '--thread-count=4',
952                         '--enable-reuse', '--debug', '--on-error=continue',
953                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
954             'cwd': '/var/spool/cwl',
955             'runtime_constraints': {
956                 'API': True,
957                 'vcpus': 1,
958                 'ram': 1073741824
959             },
960             'use_existing': True,
961             'properties': {
962                 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
963             },
964             'secret_mounts': {}
965         }
966
967         stubs.api.container_requests().create.assert_called_with(
968             body=JsonDiffMatcher(expect_container))
969         self.assertEqual(capture_stdout.getvalue(),
970                          stubs.expect_container_request_uuid + '\n')
971
972
973     @stubs
974     def test_submit_container_name(self, stubs):
975         capture_stdout = cStringIO.StringIO()
976         try:
977             exited = arvados_cwl.main(
978                 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
979                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
980                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
981             self.assertEqual(exited, 0)
982         except:
983             logging.exception("")
984
985         expect_container = copy.deepcopy(stubs.expect_container_spec)
986         expect_container["name"] = "hello container 123"
987
988         stubs.api.container_requests().create.assert_called_with(
989             body=JsonDiffMatcher(expect_container))
990         self.assertEqual(capture_stdout.getvalue(),
991                          stubs.expect_container_request_uuid + '\n')
992
993
994     @stubs
995     def test_submit_container_project(self, stubs):
996         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
997         capture_stdout = cStringIO.StringIO()
998         try:
999             exited = arvados_cwl.main(
1000                 ["--submit", "--no-wait", "--api=containers", "--debug", "--project-uuid="+project_uuid,
1001                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1002                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1003             self.assertEqual(exited, 0)
1004         except:
1005             logging.exception("")
1006
1007         expect_container = copy.deepcopy(stubs.expect_container_spec)
1008         expect_container["owner_uuid"] = project_uuid
1009         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1010                                        '--no-log-timestamps', '--disable-validate',
1011                                        "--eval-timeout=20", "--thread-count=4",
1012                                        '--enable-reuse', '--debug', '--on-error=continue',
1013                                        '--project-uuid='+project_uuid,
1014                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1015
1016         stubs.api.container_requests().create.assert_called_with(
1017             body=JsonDiffMatcher(expect_container))
1018         self.assertEqual(capture_stdout.getvalue(),
1019                          stubs.expect_container_request_uuid + '\n')
1020
1021     @stubs
1022     def test_submit_container_eval_timeout(self, stubs):
1023         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1024         capture_stdout = cStringIO.StringIO()
1025         try:
1026             exited = arvados_cwl.main(
1027                 ["--submit", "--no-wait", "--api=containers", "--debug", "--eval-timeout=60",
1028                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1029                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1030             self.assertEqual(exited, 0)
1031         except:
1032             logging.exception("")
1033
1034         expect_container = copy.deepcopy(stubs.expect_container_spec)
1035         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1036                                        '--no-log-timestamps', '--disable-validate',
1037                                        '--eval-timeout=60.0', '--thread-count=4',
1038                                        '--enable-reuse', '--debug', '--on-error=continue',
1039                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1040
1041         stubs.api.container_requests().create.assert_called_with(
1042             body=JsonDiffMatcher(expect_container))
1043         self.assertEqual(capture_stdout.getvalue(),
1044                          stubs.expect_container_request_uuid + '\n')
1045
1046
1047     @stubs
1048     def test_submit_container_thread_count(self, stubs):
1049         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1050         capture_stdout = cStringIO.StringIO()
1051         try:
1052             exited = arvados_cwl.main(
1053                 ["--submit", "--no-wait", "--api=containers", "--debug", "--thread-count=20",
1054                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1055                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1056             self.assertEqual(exited, 0)
1057         except:
1058             logging.exception("")
1059
1060         expect_container = copy.deepcopy(stubs.expect_container_spec)
1061         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers',
1062                                        '--no-log-timestamps', '--disable-validate',
1063                                        '--eval-timeout=20', '--thread-count=20',
1064                                        '--enable-reuse', '--debug', '--on-error=continue',
1065                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
1066
1067         stubs.api.container_requests().create.assert_called_with(
1068             body=JsonDiffMatcher(expect_container))
1069         self.assertEqual(capture_stdout.getvalue(),
1070                          stubs.expect_container_request_uuid + '\n')
1071
1072
1073     @stubs
1074     def test_submit_job_runner_image(self, stubs):
1075         capture_stdout = cStringIO.StringIO()
1076         try:
1077             exited = arvados_cwl.main(
1078                 ["--submit", "--no-wait", "--api=jobs", "--debug", "--submit-runner-image=arvados/jobs:123",
1079                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1080                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1081             self.assertEqual(exited, 0)
1082         except:
1083             logging.exception("")
1084
1085         stubs.expect_pipeline_instance["components"]["cwl-runner"]["runtime_constraints"]["docker_image"] = "arvados/jobs:123"
1086
1087         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
1088         stubs.api.pipeline_instances().create.assert_called_with(
1089             body=JsonDiffMatcher(expect_pipeline))
1090         self.assertEqual(capture_stdout.getvalue(),
1091                          stubs.expect_pipeline_uuid + '\n')
1092
1093     @stubs
1094     def test_submit_container_runner_image(self, stubs):
1095         capture_stdout = cStringIO.StringIO()
1096         try:
1097             exited = arvados_cwl.main(
1098                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
1099                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1100                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1101             self.assertEqual(exited, 0)
1102         except:
1103             logging.exception("")
1104
1105         stubs.expect_container_spec["container_image"] = "arvados/jobs:123"
1106
1107         expect_container = copy.deepcopy(stubs.expect_container_spec)
1108         stubs.api.container_requests().create.assert_called_with(
1109             body=JsonDiffMatcher(expect_container))
1110         self.assertEqual(capture_stdout.getvalue(),
1111                          stubs.expect_container_request_uuid + '\n')
1112
1113     @stubs
1114     def test_submit_priority(self, stubs):
1115         capture_stdout = cStringIO.StringIO()
1116         try:
1117             exited = arvados_cwl.main(
1118                 ["--submit", "--no-wait", "--api=containers", "--debug", "--priority=669",
1119                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1120                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1121             self.assertEqual(exited, 0)
1122         except:
1123             logging.exception("")
1124
1125         stubs.expect_container_spec["priority"] = 669
1126
1127         expect_container = copy.deepcopy(stubs.expect_container_spec)
1128         stubs.api.container_requests().create.assert_called_with(
1129             body=JsonDiffMatcher(expect_container))
1130         self.assertEqual(capture_stdout.getvalue(),
1131                          stubs.expect_container_request_uuid + '\n')
1132
1133
1134     @stubs
1135     def test_submit_wf_runner_resources(self, stubs):
1136         capture_stdout = cStringIO.StringIO()
1137         try:
1138             exited = arvados_cwl.main(
1139                 ["--submit", "--no-wait", "--api=containers", "--debug",
1140                  "tests/wf/submit_wf_runner_resources.cwl", "tests/submit_test_job.json"],
1141                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1142             self.assertEqual(exited, 0)
1143         except:
1144             logging.exception("")
1145
1146         expect_container = copy.deepcopy(stubs.expect_container_spec)
1147         expect_container["runtime_constraints"] = {
1148             "API": True,
1149             "vcpus": 2,
1150             "ram": 2000 * 2**20
1151         }
1152         expect_container["name"] = "submit_wf_runner_resources.cwl"
1153         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][1]["hints"] = [
1154             {
1155                 "class": "http://arvados.org/cwl#WorkflowRunnerResources",
1156                 "coresMin": 2,
1157                 "ramMin": 2000
1158             }
1159         ]
1160         expect_container["mounts"]["/var/lib/cwl/workflow.json"]["content"]["$graph"][0]["$namespaces"] = {
1161             "arv": "http://arvados.org/cwl#",
1162         }
1163
1164         stubs.api.container_requests().create.assert_called_with(
1165             body=JsonDiffMatcher(expect_container))
1166         self.assertEqual(capture_stdout.getvalue(),
1167                          stubs.expect_container_request_uuid + '\n')
1168
1169
1170     @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
1171     @mock.patch("cwltool.docker.DockerCommandLineJob.get_image")
1172     @mock.patch("arvados.api")
1173     def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
1174         arvrunner = mock.MagicMock()
1175         arvrunner.project_uuid = ""
1176         api.return_value = mock.MagicMock()
1177         arvrunner.api = api.return_value
1178         arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
1179                                                                         "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1180                                                                         "link_class": "docker_image_repo+tag",
1181                                                                         "name": "arvados/jobs:"+arvados_cwl.__version__,
1182                                                                         "owner_uuid": "",
1183                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
1184                                                             {"items": [{"created_at": "",
1185                                                                         "head_uuid": "",
1186                                                                         "link_class": "docker_image_hash",
1187                                                                         "name": "123456",
1188                                                                         "owner_uuid": "",
1189                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
1190         )
1191         find_one_image_hash.return_value = "123456"
1192
1193         arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
1194                                                                               "owner_uuid": "",
1195                                                                               "manifest_text": "",
1196                                                                               "properties": ""
1197                                                                           }], "items_available": 1, "offset": 0},)
1198         arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
1199         self.assertEqual("arvados/jobs:"+arvados_cwl.__version__,
1200                          arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__))
1201
1202     @stubs
1203     def test_submit_secrets(self, stubs):
1204         capture_stdout = cStringIO.StringIO()
1205         try:
1206             exited = arvados_cwl.main(
1207                 ["--submit", "--no-wait", "--api=containers", "--debug",
1208                  "tests/wf/secret_wf.cwl", "tests/secret_test_job.yml"],
1209                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1210             self.assertEqual(exited, 0)
1211         except:
1212             logging.exception("")
1213
1214
1215         expect_container = {
1216             "command": [
1217                 "arvados-cwl-runner",
1218                 "--local",
1219                 "--api=containers",
1220                 "--no-log-timestamps",
1221                 "--disable-validate",
1222                 "--eval-timeout=20",
1223                 '--thread-count=4',
1224                 "--enable-reuse",
1225                 '--debug',
1226                 "--on-error=continue",
1227                 "/var/lib/cwl/workflow.json#main",
1228                 "/var/lib/cwl/cwl.input.json"
1229             ],
1230             "container_image": "arvados/jobs:"+arvados_cwl.__version__,
1231             "cwd": "/var/spool/cwl",
1232             "mounts": {
1233                 "/var/lib/cwl/cwl.input.json": {
1234                     "content": {
1235                         "pw": {
1236                             "$include": "/secrets/s0"
1237                         }
1238                     },
1239                     "kind": "json"
1240                 },
1241                 "/var/lib/cwl/workflow.json": {
1242                     "content": {
1243                         "$graph": [
1244                             {
1245                                 "$namespaces": {
1246                                     "cwltool": "http://commonwl.org/cwltool#"
1247                                 },
1248                                 "arguments": [
1249                                     "md5sum",
1250                                     "example.conf"
1251                                 ],
1252                                 "class": "CommandLineTool",
1253                                 "hints": [
1254                                     {
1255                                         "class": "http://commonwl.org/cwltool#Secrets",
1256                                         "secrets": [
1257                                             "#secret_job.cwl/pw"
1258                                         ]
1259                                     }
1260                                 ],
1261                                 "id": "#secret_job.cwl",
1262                                 "inputs": [
1263                                     {
1264                                         "id": "#secret_job.cwl/pw",
1265                                         "type": "string"
1266                                     }
1267                                 ],
1268                                 "outputs": [
1269                                     {
1270                                         "id": "#secret_job.cwl/out",
1271                                         "type": "stdout"
1272                                     }
1273                                 ],
1274                                 "stdout": "hashed_example.txt",
1275                                 "requirements": [
1276                                     {
1277                                         "class": "InitialWorkDirRequirement",
1278                                         "listing": [
1279                                             {
1280                                                 "entry": "username: user\npassword: $(inputs.pw)\n",
1281                                                 "entryname": "example.conf"
1282                                             }
1283                                         ]
1284                                     }
1285                                 ]
1286                             },
1287                             {
1288                                 "class": "Workflow",
1289                                 "hints": [
1290                                     {
1291                                         "class": "DockerRequirement",
1292                                         "dockerPull": "debian:8"
1293                                     },
1294                                     {
1295                                         "class": "http://commonwl.org/cwltool#Secrets",
1296                                         "secrets": [
1297                                             "#main/pw"
1298                                         ]
1299                                     }
1300                                 ],
1301                                 "id": "#main",
1302                                 "inputs": [
1303                                     {
1304                                         "id": "#main/pw",
1305                                         "type": "string"
1306                                     }
1307                                 ],
1308                                 "outputs": [
1309                                     {
1310                                         "id": "#main/out",
1311                                         "outputSource": "#main/step1/out",
1312                                         "type": "File"
1313                                     }
1314                                 ],
1315                                 "steps": [
1316                                     {
1317                                         "id": "#main/step1",
1318                                         "in": [
1319                                             {
1320                                                 "id": "#main/step1/pw",
1321                                                 "source": "#main/pw"
1322                                             }
1323                                         ],
1324                                         "out": [
1325                                             "#main/step1/out"
1326                                         ],
1327                                         "run": "#secret_job.cwl"
1328                                     }
1329                                 ]
1330                             }
1331                         ],
1332                         "cwlVersion": "v1.0"
1333                     },
1334                     "kind": "json"
1335                 },
1336                 "/var/spool/cwl": {
1337                     "kind": "collection",
1338                     "writable": True
1339                 },
1340                 "stdout": {
1341                     "kind": "file",
1342                     "path": "/var/spool/cwl/cwl.output.json"
1343                 }
1344             },
1345             "name": "secret_wf.cwl",
1346             "output_path": "/var/spool/cwl",
1347             "priority": 500,
1348             "properties": {},
1349             "runtime_constraints": {
1350                 "API": True,
1351                 "ram": 1073741824,
1352                 "vcpus": 1
1353             },
1354             "secret_mounts": {
1355                 "/secrets/s0": {
1356                     "content": "blorp",
1357                     "kind": "text"
1358                 }
1359             },
1360             "state": "Committed",
1361             "use_existing": True
1362         }
1363
1364         stubs.api.container_requests().create.assert_called_with(
1365             body=JsonDiffMatcher(expect_container))
1366         self.assertEqual(capture_stdout.getvalue(),
1367                          stubs.expect_container_request_uuid + '\n')
1368
1369     @stubs
1370     def test_submit_request_uuid(self, stubs):
1371         stubs.expect_container_request_uuid = "zzzzz-xvhdp-yyyyyyyyyyyyyyy"
1372
1373         stubs.api.container_requests().update().execute.return_value = {
1374             "uuid": stubs.expect_container_request_uuid,
1375             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
1376             "state": "Queued"
1377         }
1378
1379         capture_stdout = cStringIO.StringIO()
1380         try:
1381             exited = arvados_cwl.main(
1382                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-request-uuid=zzzzz-xvhdp-yyyyyyyyyyyyyyy",
1383                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1384                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
1385             self.assertEqual(exited, 0)
1386         except:
1387             logging.exception("")
1388
1389         stubs.api.container_requests().update.assert_called_with(
1390             uuid="zzzzz-xvhdp-yyyyyyyyyyyyyyy", body=JsonDiffMatcher(stubs.expect_container_spec))
1391         self.assertEqual(capture_stdout.getvalue(),
1392                          stubs.expect_container_request_uuid + '\n')
1393
1394
1395 class TestCreateTemplate(unittest.TestCase):
1396     existing_template_uuid = "zzzzz-d1hrv-validworkfloyml"
1397
1398     def _adjust_script_params(self, expect_component):
1399         expect_component['script_parameters']['x'] = {
1400             'dataclass': 'File',
1401             'required': True,
1402             'type': 'File',
1403             'value': '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
1404         }
1405         expect_component['script_parameters']['y'] = {
1406             'dataclass': 'Collection',
1407             'required': True,
1408             'type': 'Directory',
1409             'value': '99999999999999999999999999999998+99',
1410         }
1411         expect_component['script_parameters']['z'] = {
1412             'dataclass': 'Collection',
1413             'required': True,
1414             'type': 'Directory',
1415         }
1416
1417     @stubs
1418     def test_create(self, stubs):
1419         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1420
1421         capture_stdout = cStringIO.StringIO()
1422
1423         exited = arvados_cwl.main(
1424             ["--create-workflow", "--debug",
1425              "--api=jobs",
1426              "--project-uuid", project_uuid,
1427              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1428             capture_stdout, sys.stderr, api_client=stubs.api)
1429         self.assertEqual(exited, 0)
1430
1431         stubs.api.pipeline_instances().create.refute_called()
1432         stubs.api.jobs().create.refute_called()
1433
1434         expect_component = copy.deepcopy(stubs.expect_job_spec)
1435         self._adjust_script_params(expect_component)
1436         expect_template = {
1437             "components": {
1438                 "submit_wf.cwl": expect_component,
1439             },
1440             "name": "submit_wf.cwl",
1441             "owner_uuid": project_uuid,
1442         }
1443         stubs.api.pipeline_templates().create.assert_called_with(
1444             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1445
1446         self.assertEqual(capture_stdout.getvalue(),
1447                          stubs.expect_pipeline_template_uuid + '\n')
1448
1449
1450     @stubs
1451     def test_create_name(self, stubs):
1452         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1453
1454         capture_stdout = cStringIO.StringIO()
1455
1456         exited = arvados_cwl.main(
1457             ["--create-workflow", "--debug",
1458              "--project-uuid", project_uuid,
1459              "--api=jobs",
1460              "--name", "testing 123",
1461              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1462             capture_stdout, sys.stderr, api_client=stubs.api)
1463         self.assertEqual(exited, 0)
1464
1465         stubs.api.pipeline_instances().create.refute_called()
1466         stubs.api.jobs().create.refute_called()
1467
1468         expect_component = copy.deepcopy(stubs.expect_job_spec)
1469         self._adjust_script_params(expect_component)
1470         expect_template = {
1471             "components": {
1472                 "testing 123": expect_component,
1473             },
1474             "name": "testing 123",
1475             "owner_uuid": project_uuid,
1476         }
1477         stubs.api.pipeline_templates().create.assert_called_with(
1478             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1479
1480         self.assertEqual(capture_stdout.getvalue(),
1481                          stubs.expect_pipeline_template_uuid + '\n')
1482
1483
1484     @stubs
1485     def test_update_name(self, stubs):
1486         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1487
1488         capture_stdout = cStringIO.StringIO()
1489
1490         exited = arvados_cwl.main(
1491             ["--update-workflow", self.existing_template_uuid,
1492              "--debug",
1493              "--project-uuid", project_uuid,
1494              "--api=jobs",
1495              "--name", "testing 123",
1496              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1497             capture_stdout, sys.stderr, api_client=stubs.api)
1498         self.assertEqual(exited, 0)
1499
1500         stubs.api.pipeline_instances().create.refute_called()
1501         stubs.api.jobs().create.refute_called()
1502
1503         expect_component = copy.deepcopy(stubs.expect_job_spec)
1504         self._adjust_script_params(expect_component)
1505         expect_template = {
1506             "components": {
1507                 "testing 123": expect_component,
1508             },
1509             "name": "testing 123",
1510             "owner_uuid": project_uuid,
1511         }
1512         stubs.api.pipeline_templates().create.refute_called()
1513         stubs.api.pipeline_templates().update.assert_called_with(
1514             body=JsonDiffMatcher(expect_template), uuid=self.existing_template_uuid)
1515
1516         self.assertEqual(capture_stdout.getvalue(),
1517                          self.existing_template_uuid + '\n')
1518
1519
1520 class TestCreateWorkflow(unittest.TestCase):
1521     existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1522     expect_workflow = StripYAMLComments(
1523         open("tests/wf/expect_packed.cwl").read())
1524
1525     @stubs
1526     def test_create(self, stubs):
1527         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1528
1529         capture_stdout = cStringIO.StringIO()
1530
1531         exited = arvados_cwl.main(
1532             ["--create-workflow", "--debug",
1533              "--api=containers",
1534              "--project-uuid", project_uuid,
1535              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1536             capture_stdout, sys.stderr, api_client=stubs.api)
1537         self.assertEqual(exited, 0)
1538
1539         stubs.api.pipeline_templates().create.refute_called()
1540         stubs.api.container_requests().create.refute_called()
1541
1542         body = {
1543             "workflow": {
1544                 "owner_uuid": project_uuid,
1545                 "name": "submit_wf.cwl",
1546                 "description": "",
1547                 "definition": self.expect_workflow,
1548             }
1549         }
1550         stubs.api.workflows().create.assert_called_with(
1551             body=JsonDiffMatcher(body))
1552
1553         self.assertEqual(capture_stdout.getvalue(),
1554                          stubs.expect_workflow_uuid + '\n')
1555
1556
1557     @stubs
1558     def test_create_name(self, stubs):
1559         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1560
1561         capture_stdout = cStringIO.StringIO()
1562
1563         exited = arvados_cwl.main(
1564             ["--create-workflow", "--debug",
1565              "--api=containers",
1566              "--project-uuid", project_uuid,
1567              "--name", "testing 123",
1568              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1569             capture_stdout, sys.stderr, api_client=stubs.api)
1570         self.assertEqual(exited, 0)
1571
1572         stubs.api.pipeline_templates().create.refute_called()
1573         stubs.api.container_requests().create.refute_called()
1574
1575         body = {
1576             "workflow": {
1577                 "owner_uuid": project_uuid,
1578                 "name": "testing 123",
1579                 "description": "",
1580                 "definition": self.expect_workflow,
1581             }
1582         }
1583         stubs.api.workflows().create.assert_called_with(
1584             body=JsonDiffMatcher(body))
1585
1586         self.assertEqual(capture_stdout.getvalue(),
1587                          stubs.expect_workflow_uuid + '\n')
1588
1589     @stubs
1590     def test_incompatible_api(self, stubs):
1591         capture_stderr = cStringIO.StringIO()
1592         logging.getLogger('arvados.cwl-runner').addHandler(
1593             logging.StreamHandler(capture_stderr))
1594
1595         exited = arvados_cwl.main(
1596             ["--update-workflow", self.existing_workflow_uuid,
1597              "--api=jobs",
1598              "--debug",
1599              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1600             sys.stderr, sys.stderr, api_client=stubs.api)
1601         self.assertEqual(exited, 1)
1602         self.assertRegexpMatches(
1603             capture_stderr.getvalue(),
1604             "--update-workflow arg '{}' uses 'containers' API, but --api='jobs' specified".format(self.existing_workflow_uuid))
1605
1606     @stubs
1607     def test_update(self, stubs):
1608         capture_stdout = cStringIO.StringIO()
1609
1610         exited = arvados_cwl.main(
1611             ["--update-workflow", self.existing_workflow_uuid,
1612              "--debug",
1613              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1614             capture_stdout, sys.stderr, api_client=stubs.api)
1615         self.assertEqual(exited, 0)
1616
1617         body = {
1618             "workflow": {
1619                 "name": "submit_wf.cwl",
1620                 "description": "",
1621                 "definition": self.expect_workflow,
1622             }
1623         }
1624         stubs.api.workflows().update.assert_called_with(
1625             uuid=self.existing_workflow_uuid,
1626             body=JsonDiffMatcher(body))
1627         self.assertEqual(capture_stdout.getvalue(),
1628                          self.existing_workflow_uuid + '\n')
1629
1630
1631     @stubs
1632     def test_update_name(self, stubs):
1633         capture_stdout = cStringIO.StringIO()
1634
1635         exited = arvados_cwl.main(
1636             ["--update-workflow", self.existing_workflow_uuid,
1637              "--debug", "--name", "testing 123",
1638              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1639             capture_stdout, sys.stderr, api_client=stubs.api)
1640         self.assertEqual(exited, 0)
1641
1642         body = {
1643             "workflow": {
1644                 "name": "testing 123",
1645                 "description": "",
1646                 "definition": self.expect_workflow,
1647             }
1648         }
1649         stubs.api.workflows().update.assert_called_with(
1650             uuid=self.existing_workflow_uuid,
1651             body=JsonDiffMatcher(body))
1652         self.assertEqual(capture_stdout.getvalue(),
1653                          self.existing_workflow_uuid + '\n')
1654
1655
1656     @stubs
1657     def test_create_collection_per_tool(self, stubs):
1658         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1659
1660         capture_stdout = cStringIO.StringIO()
1661
1662         exited = arvados_cwl.main(
1663             ["--create-workflow", "--debug",
1664              "--api=containers",
1665              "--project-uuid", project_uuid,
1666              "tests/collection_per_tool/collection_per_tool.cwl"],
1667             capture_stdout, sys.stderr, api_client=stubs.api)
1668         self.assertEqual(exited, 0)
1669
1670         toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1671         expect_workflow = StripYAMLComments(open(toolfile).read())
1672
1673         body = {
1674             "workflow": {
1675                 "owner_uuid": project_uuid,
1676                 "name": "collection_per_tool.cwl",
1677                 "description": "",
1678                 "definition": expect_workflow,
1679             }
1680         }
1681         stubs.api.workflows().create.assert_called_with(
1682             body=JsonDiffMatcher(body))
1683
1684         self.assertEqual(capture_stdout.getvalue(),
1685                          stubs.expect_workflow_uuid + '\n')
1686
1687 class TestTemplateInputs(unittest.TestCase):
1688     expect_template = {
1689         "components": {
1690             "inputs_test.cwl": {
1691                 'runtime_constraints': {
1692                     'docker_image': 'arvados/jobs:'+arvados_cwl.__version__,
1693                     'min_ram_mb_per_node': 1024
1694                 },
1695                 'script_parameters': {
1696                     'cwl:tool':
1697                     '6c5ee1cd606088106d9f28367cde1e41+60/workflow.cwl#main',
1698                     'optionalFloatInput': None,
1699                     'fileInput': {
1700                         'type': 'File',
1701                         'dataclass': 'File',
1702                         'required': True,
1703                         'title': "It's a file; we expect to find some characters in it.",
1704                         'description': 'If there were anything further to say, it would be said here,\nor here.'
1705                     },
1706                     'floatInput': {
1707                         'type': 'float',
1708                         'dataclass': 'number',
1709                         'required': True,
1710                         'title': 'Floats like a duck',
1711                         'default': 0.1,
1712                         'value': 0.1,
1713                     },
1714                     'optionalFloatInput': {
1715                         'type': ['null', 'float'],
1716                         'dataclass': 'number',
1717                         'required': False,
1718                     },
1719                     'boolInput': {
1720                         'type': 'boolean',
1721                         'dataclass': 'boolean',
1722                         'required': True,
1723                         'title': 'True or false?',
1724                     },
1725                 },
1726                 'repository': 'arvados',
1727                 'script_version': 'master',
1728                 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
1729                 'script': 'cwl-runner',
1730             },
1731         },
1732         "name": "inputs_test.cwl",
1733     }
1734
1735     @stubs
1736     def test_inputs_empty(self, stubs):
1737         exited = arvados_cwl.main(
1738             ["--create-template",
1739              "tests/wf/inputs_test.cwl", "tests/order/empty_order.json"],
1740             cStringIO.StringIO(), sys.stderr, api_client=stubs.api)
1741         self.assertEqual(exited, 0)
1742
1743         stubs.api.pipeline_templates().create.assert_called_with(
1744             body=JsonDiffMatcher(self.expect_template), ensure_unique_name=True)
1745
1746     @stubs
1747     def test_inputs(self, stubs):
1748         exited = arvados_cwl.main(
1749             ["--create-template",
1750              "tests/wf/inputs_test.cwl", "tests/order/inputs_test_order.json"],
1751             cStringIO.StringIO(), sys.stderr, api_client=stubs.api)
1752         self.assertEqual(exited, 0)
1753
1754         expect_template = copy.deepcopy(self.expect_template)
1755         params = expect_template[
1756             "components"]["inputs_test.cwl"]["script_parameters"]
1757         params["fileInput"]["value"] = '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt'
1758         params["cwl:tool"] = '6c5ee1cd606088106d9f28367cde1e41+60/workflow.cwl#main'
1759         params["floatInput"]["value"] = 1.234
1760         params["boolInput"]["value"] = True
1761
1762         stubs.api.pipeline_templates().create.assert_called_with(
1763             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)