11948: Fix unit tests for additional file checking and nameroot/nameext fields.
[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         stubs.api.collections().create.side_effect = functools.partial(collection_createstub, created_collections)
89         stubs.api.collections().get.side_effect = functools.partial(collection_getstub, created_collections)
90
91         stubs.expect_job_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
92         stubs.api.jobs().create().execute.return_value = {
93             "uuid": stubs.expect_job_uuid,
94             "state": "Queued",
95         }
96
97         stubs.expect_container_request_uuid = "zzzzz-xvhdp-zzzzzzzzzzzzzzz"
98         stubs.api.container_requests().create().execute.return_value = {
99             "uuid": stubs.expect_container_request_uuid,
100             "container_uuid": "zzzzz-dz642-zzzzzzzzzzzzzzz",
101             "state": "Queued"
102         }
103
104         stubs.expect_pipeline_template_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
105         stubs.api.pipeline_templates().create().execute.return_value = {
106             "uuid": stubs.expect_pipeline_template_uuid,
107         }
108         stubs.expect_job_spec = {
109             'runtime_constraints': {
110                 'docker_image': 'arvados/jobs:'+arvados_cwl.__version__,
111                 'min_ram_mb_per_node': 1024
112             },
113             'script_parameters': {
114                 'x': {
115                     'basename': 'blorp.txt',
116                     'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
117                     'class': 'File'
118                 },
119                 'y': {
120                     'basename': '99999999999999999999999999999998+99',
121                     'location': 'keep:99999999999999999999999999999998+99',
122                     'class': 'Directory'
123                 },
124                 'z': {
125                     'basename': 'anonymous',
126                     "listing": [{
127                         "basename": "renamed.txt",
128                         "class": "File",
129                         "location": "keep:99999999999999999999999999999998+99/file1.txt"
130                     }],
131                     'class': 'Directory'
132                 },
133                 'cwl:tool': '3fffdeaa75e018172e1b583425f4ebff+60/workflow.cwl#main'
134             },
135             'repository': 'arvados',
136             'script_version': 'master',
137             'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
138             'script': 'cwl-runner'
139         }
140         stubs.pipeline_component = stubs.expect_job_spec.copy()
141         stubs.expect_pipeline_instance = {
142             'name': 'submit_wf.cwl',
143             'state': 'RunningOnServer',
144             'owner_uuid': None,
145             "components": {
146                 "cwl-runner": {
147                     'runtime_constraints': {'docker_image': 'arvados/jobs:'+arvados_cwl.__version__, 'min_ram_mb_per_node': 1024},
148                     'script_parameters': {
149                         'y': {"value": {'basename': '99999999999999999999999999999998+99', 'location': 'keep:99999999999999999999999999999998+99', 'class': 'Directory'}},
150                         'x': {"value": {
151                             'basename': 'blorp.txt',
152                             'class': 'File',
153                             'location': 'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
154                             "nameext": ".txt",
155                             "nameroot": "blorp"
156                         }},
157                         'z': {"value": {'basename': 'anonymous', 'class': 'Directory',
158                               'listing': [
159                                   {
160                                       'basename': 'renamed.txt',
161                                       'class': 'File', 'location':
162                                       'keep:99999999999999999999999999999998+99/file1.txt',
163                                       "nameext": ".txt",
164                                       "nameroot": "renamed"
165                                   }
166                               ]}},
167                         'cwl:tool': '3fffdeaa75e018172e1b583425f4ebff+60/workflow.cwl#main',
168                         'arv:enable_reuse': True,
169                         'arv:on_error': 'continue'
170                     },
171                     'repository': 'arvados',
172                     'script_version': 'master',
173                     'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
174                     'script': 'cwl-runner',
175                     'job': {'state': 'Queued', 'uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}
176                 }
177             }
178         }
179         stubs.pipeline_create = copy.deepcopy(stubs.expect_pipeline_instance)
180         stubs.expect_pipeline_uuid = "zzzzz-d1hrv-zzzzzzzzzzzzzzz"
181         stubs.pipeline_create["uuid"] = stubs.expect_pipeline_uuid
182         stubs.pipeline_with_job = copy.deepcopy(stubs.pipeline_create)
183         stubs.pipeline_with_job["components"]["cwl-runner"]["job"] = {
184             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
185             "state": "Queued"
186         }
187         stubs.api.pipeline_instances().create().execute.return_value = stubs.pipeline_create
188         stubs.api.pipeline_instances().get().execute.return_value = stubs.pipeline_with_job
189
190         with open("tests/wf/submit_wf_packed.cwl") as f:
191             expect_packed_workflow = yaml.round_trip_load(f)
192
193         stubs.expect_container_spec = {
194             'priority': 1,
195             'mounts': {
196                 '/var/spool/cwl': {
197                     'writable': True,
198                     'kind': 'collection'
199                 },
200                 '/var/lib/cwl/workflow.json': {
201                     'content': expect_packed_workflow,
202                     'kind': 'json'
203                 },
204                 'stdout': {
205                     'path': '/var/spool/cwl/cwl.output.json',
206                     'kind': 'file'
207                 },
208                 '/var/lib/cwl/cwl.input.json': {
209                     'kind': 'json',
210                     'content': {
211                         'y': {
212                             'basename': '99999999999999999999999999999998+99',
213                             'location': 'keep:99999999999999999999999999999998+99',
214                             'class': 'Directory'},
215                         'x': {
216                             'basename': u'blorp.txt',
217                             'class': 'File',
218                             'location': u'keep:169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
219                             "nameext": ".txt",
220                             "nameroot": "blorp"
221                         },
222                         'z': {'basename': 'anonymous', 'class': 'Directory', 'listing': [
223                             {'basename': 'renamed.txt',
224                              'class': 'File',
225                              'location': 'keep:99999999999999999999999999999998+99/file1.txt',
226                              "nameext": ".txt",
227                              "nameroot": "renamed"
228                             }
229                         ]}
230                     },
231                     'kind': 'json'
232                 }
233             },
234             'state': 'Committed',
235             'owner_uuid': None,
236             'command': ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
237                         '--enable-reuse', '--on-error=continue',
238                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
239             'name': 'submit_wf.cwl',
240             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
241             'output_path': '/var/spool/cwl',
242             'cwd': '/var/spool/cwl',
243             'runtime_constraints': {
244                 'API': True,
245                 'vcpus': 1,
246                 'ram': 1024*1024*1024
247             },
248             "properties": {}
249         }
250
251         stubs.expect_workflow_uuid = "zzzzz-7fd4e-zzzzzzzzzzzzzzz"
252         stubs.api.workflows().create().execute.return_value = {
253             "uuid": stubs.expect_workflow_uuid,
254         }
255         def update_mock(**kwargs):
256             stubs.updated_uuid = kwargs.get('uuid')
257             return mock.DEFAULT
258         stubs.api.workflows().update.side_effect = update_mock
259         stubs.api.workflows().update().execute.side_effect = lambda **kwargs: {
260             "uuid": stubs.updated_uuid,
261         }
262
263         return func(self, stubs, *args, **kwargs)
264     return wrapped
265
266
267 class TestSubmit(unittest.TestCase):
268     @mock.patch("arvados_cwl.runner.arv_docker_get_image")
269     @mock.patch("time.sleep")
270     @stubs
271     def test_submit(self, stubs, tm, arvdock):
272         capture_stdout = cStringIO.StringIO()
273         exited = arvados_cwl.main(
274             ["--submit", "--no-wait", "--api=jobs", "--debug",
275              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
276             capture_stdout, sys.stderr, api_client=stubs.api)
277         self.assertEqual(exited, 0)
278
279         stubs.api.collections().create.assert_has_calls([
280             mock.call(body=JsonDiffMatcher({
281                 'manifest_text':
282                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
283                 'replication_desired': None,
284                 'name': 'submit_tool.cwl dependencies',
285             }), ensure_unique_name=True),
286             mock.call(body=JsonDiffMatcher({
287                 'manifest_text':
288                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
289                 'replication_desired': None,
290                 'name': 'submit_wf.cwl input',
291             }), ensure_unique_name=True),
292             mock.call(body=JsonDiffMatcher({
293                 'manifest_text':
294                 '. 61df2ed9ee3eb7dd9b799e5ca35305fa+1217 0:1217:workflow.cwl\n',
295                 'replication_desired': None,
296                 'name': 'submit_wf.cwl',
297             }), ensure_unique_name=True)        ])
298
299         arvdock.assert_has_calls([
300             mock.call(stubs.api, {"class": "DockerRequirement", "dockerPull": "debian:8"}, True, None),
301             mock.call(stubs.api, {'dockerPull': 'arvados/jobs:'+arvados_cwl.__version__}, True, None)
302         ])
303
304         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
305         stubs.api.pipeline_instances().create.assert_called_with(
306             body=JsonDiffMatcher(expect_pipeline))
307         self.assertEqual(capture_stdout.getvalue(),
308                          stubs.expect_pipeline_uuid + '\n')
309
310
311     @mock.patch("time.sleep")
312     @stubs
313     def test_submit_no_reuse(self, stubs, tm):
314         capture_stdout = cStringIO.StringIO()
315         exited = arvados_cwl.main(
316             ["--submit", "--no-wait", "--api=jobs", "--debug", "--disable-reuse",
317              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
318             capture_stdout, sys.stderr, api_client=stubs.api)
319         self.assertEqual(exited, 0)
320
321         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
322         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:enable_reuse"] = {"value": False}
323
324         stubs.api.pipeline_instances().create.assert_called_with(
325             body=JsonDiffMatcher(expect_pipeline))
326         self.assertEqual(capture_stdout.getvalue(),
327                          stubs.expect_pipeline_uuid + '\n')
328
329     @mock.patch("time.sleep")
330     @stubs
331     def test_submit_on_error(self, stubs, tm):
332         capture_stdout = cStringIO.StringIO()
333         exited = arvados_cwl.main(
334             ["--submit", "--no-wait", "--api=jobs", "--debug", "--on-error=stop",
335              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
336             capture_stdout, sys.stderr, api_client=stubs.api)
337         self.assertEqual(exited, 0)
338
339         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
340         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:on_error"] = "stop"
341
342         stubs.api.pipeline_instances().create.assert_called_with(
343             body=JsonDiffMatcher(expect_pipeline))
344         self.assertEqual(capture_stdout.getvalue(),
345                          stubs.expect_pipeline_uuid + '\n')
346
347
348     @mock.patch("time.sleep")
349     @stubs
350     def test_submit_runner_ram(self, stubs, tm):
351         capture_stdout = cStringIO.StringIO()
352         exited = arvados_cwl.main(
353             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=2048",
354              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
355             capture_stdout, sys.stderr, api_client=stubs.api)
356         self.assertEqual(exited, 0)
357
358         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
359         expect_pipeline["components"]["cwl-runner"]["runtime_constraints"]["min_ram_mb_per_node"] = 2048
360
361         stubs.api.pipeline_instances().create.assert_called_with(
362             body=JsonDiffMatcher(expect_pipeline))
363         self.assertEqual(capture_stdout.getvalue(),
364                          stubs.expect_pipeline_uuid + '\n')
365
366
367     @mock.patch("time.sleep")
368     @stubs
369     def test_submit_invalid_runner_ram(self, stubs, tm):
370         capture_stdout = cStringIO.StringIO()
371         exited = arvados_cwl.main(
372             ["--submit", "--no-wait", "--debug", "--submit-runner-ram=-2048",
373              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
374             capture_stdout, sys.stderr, api_client=stubs.api)
375         self.assertEqual(exited, 1)
376
377     @mock.patch("time.sleep")
378     @stubs
379     def test_submit_output_name(self, stubs, tm):
380         output_name = "test_output_name"
381
382         capture_stdout = cStringIO.StringIO()
383         exited = arvados_cwl.main(
384             ["--submit", "--no-wait", "--debug", "--output-name", output_name,
385              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
386             capture_stdout, sys.stderr, api_client=stubs.api)
387         self.assertEqual(exited, 0)
388
389         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
390         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_name"] = output_name
391
392         stubs.api.pipeline_instances().create.assert_called_with(
393             body=JsonDiffMatcher(expect_pipeline))
394         self.assertEqual(capture_stdout.getvalue(),
395                          stubs.expect_pipeline_uuid + '\n')
396
397
398     @mock.patch("time.sleep")
399     @stubs
400     def test_submit_pipeline_name(self, stubs, tm):
401         capture_stdout = cStringIO.StringIO()
402         exited = arvados_cwl.main(
403             ["--submit", "--no-wait", "--debug", "--name=hello job 123",
404              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
405             capture_stdout, sys.stderr, api_client=stubs.api)
406         self.assertEqual(exited, 0)
407
408         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
409         expect_pipeline["name"] = "hello job 123"
410
411         stubs.api.pipeline_instances().create.assert_called_with(
412             body=JsonDiffMatcher(expect_pipeline))
413         self.assertEqual(capture_stdout.getvalue(),
414                          stubs.expect_pipeline_uuid + '\n')
415
416     @mock.patch("time.sleep")
417     @stubs
418     def test_submit_output_tags(self, stubs, tm):
419         output_tags = "tag0,tag1,tag2"
420
421         capture_stdout = cStringIO.StringIO()
422         exited = arvados_cwl.main(
423             ["--submit", "--no-wait", "--debug", "--output-tags", output_tags,
424              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
425             capture_stdout, sys.stderr, api_client=stubs.api)
426         self.assertEqual(exited, 0)
427
428         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
429         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["arv:output_tags"] = output_tags
430
431         stubs.api.pipeline_instances().create.assert_called_with(
432             body=JsonDiffMatcher(expect_pipeline))
433         self.assertEqual(capture_stdout.getvalue(),
434                          stubs.expect_pipeline_uuid + '\n')
435
436     @mock.patch("time.sleep")
437     @stubs
438     def test_submit_with_project_uuid(self, stubs, tm):
439         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
440
441         exited = arvados_cwl.main(
442             ["--submit", "--no-wait",
443              "--project-uuid", project_uuid,
444              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
445             sys.stdout, sys.stderr, api_client=stubs.api)
446         self.assertEqual(exited, 0)
447
448         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
449         expect_pipeline["owner_uuid"] = project_uuid
450         stubs.api.pipeline_instances().create.assert_called_with(
451             body=JsonDiffMatcher(expect_pipeline))
452
453     @stubs
454     def test_submit_container(self, stubs):
455         capture_stdout = cStringIO.StringIO()
456         try:
457             exited = arvados_cwl.main(
458                 ["--submit", "--no-wait", "--api=containers", "--debug",
459                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
460                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
461             self.assertEqual(exited, 0)
462         except:
463             logging.exception("")
464
465         stubs.api.collections().create.assert_has_calls([
466             mock.call(body=JsonDiffMatcher({
467                 'manifest_text':
468                 '. 5bcc9fe8f8d5992e6cf418dc7ce4dbb3+16 0:16:blub.txt\n',
469                 'replication_desired': None,
470                 'name': 'submit_tool.cwl dependencies',
471             }), ensure_unique_name=True),
472             mock.call(body=JsonDiffMatcher({
473                 'manifest_text':
474                 '. 979af1245a12a1fed634d4222473bfdc+16 0:16:blorp.txt\n',
475                 'replication_desired': None,
476                 'name': 'submit_wf.cwl input',
477             }), ensure_unique_name=True)])
478
479         expect_container = copy.deepcopy(stubs.expect_container_spec)
480         stubs.api.container_requests().create.assert_called_with(
481             body=JsonDiffMatcher(expect_container))
482         self.assertEqual(capture_stdout.getvalue(),
483                          stubs.expect_container_request_uuid + '\n')
484
485     @stubs
486     def test_submit_container_no_reuse(self, stubs):
487         capture_stdout = cStringIO.StringIO()
488         try:
489             exited = arvados_cwl.main(
490                 ["--submit", "--no-wait", "--api=containers", "--debug", "--disable-reuse",
491                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
492                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
493             self.assertEqual(exited, 0)
494         except:
495             logging.exception("")
496
497         expect_container = copy.deepcopy(stubs.expect_container_spec)
498         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
499                                                   '--disable-reuse', '--on-error=continue',
500                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
501
502         stubs.api.container_requests().create.assert_called_with(
503             body=JsonDiffMatcher(expect_container))
504         self.assertEqual(capture_stdout.getvalue(),
505                          stubs.expect_container_request_uuid + '\n')
506
507
508     @stubs
509     def test_submit_container_on_error(self, stubs):
510         capture_stdout = cStringIO.StringIO()
511         try:
512             exited = arvados_cwl.main(
513                 ["--submit", "--no-wait", "--api=containers", "--debug", "--on-error=stop",
514                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
515                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
516             self.assertEqual(exited, 0)
517         except:
518             logging.exception("")
519
520         expect_container = copy.deepcopy(stubs.expect_container_spec)
521         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
522                                                   '--enable-reuse', '--on-error=stop',
523                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
524
525         stubs.api.container_requests().create.assert_called_with(
526             body=JsonDiffMatcher(expect_container))
527         self.assertEqual(capture_stdout.getvalue(),
528                          stubs.expect_container_request_uuid + '\n')
529
530     @stubs
531     def test_submit_container_output_name(self, stubs):
532         output_name = "test_output_name"
533
534         capture_stdout = cStringIO.StringIO()
535         try:
536             exited = arvados_cwl.main(
537                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-name", output_name,
538                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
539                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
540             self.assertEqual(exited, 0)
541         except:
542             logging.exception("")
543
544         expect_container = copy.deepcopy(stubs.expect_container_spec)
545         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
546                                                   "--output-name="+output_name, '--enable-reuse', '--on-error=continue',
547                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
548         expect_container["output_name"] = output_name
549
550         stubs.api.container_requests().create.assert_called_with(
551             body=JsonDiffMatcher(expect_container))
552         self.assertEqual(capture_stdout.getvalue(),
553                          stubs.expect_container_request_uuid + '\n')
554
555
556     @stubs
557     def test_submit_container_output_ttl(self, stubs):
558         capture_stdout = cStringIO.StringIO()
559         try:
560             exited = arvados_cwl.main(
561                 ["--submit", "--no-wait", "--api=containers", "--debug", "--intermediate-output-ttl", "3600",
562                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
563                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
564             self.assertEqual(exited, 0)
565         except:
566             logging.exception("")
567
568         expect_container = copy.deepcopy(stubs.expect_container_spec)
569         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
570                                        '--enable-reuse', '--on-error=continue',
571                                        "--intermediate-output-ttl=3600",
572                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
573
574         stubs.api.container_requests().create.assert_called_with(
575             body=JsonDiffMatcher(expect_container))
576         self.assertEqual(capture_stdout.getvalue(),
577                          stubs.expect_container_request_uuid + '\n')
578
579     @stubs
580     def test_submit_container_trash_intermediate(self, stubs):
581         capture_stdout = cStringIO.StringIO()
582         try:
583             exited = arvados_cwl.main(
584                 ["--submit", "--no-wait", "--api=containers", "--debug", "--trash-intermediate",
585                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
586                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
587             self.assertEqual(exited, 0)
588         except:
589             logging.exception("")
590
591         expect_container = copy.deepcopy(stubs.expect_container_spec)
592         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
593                                        '--enable-reuse', '--on-error=continue',
594                                        "--trash-intermediate",
595                                        '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
596
597         stubs.api.container_requests().create.assert_called_with(
598             body=JsonDiffMatcher(expect_container))
599         self.assertEqual(capture_stdout.getvalue(),
600                          stubs.expect_container_request_uuid + '\n')
601
602     @stubs
603     def test_submit_container_output_tags(self, stubs):
604         output_tags = "tag0,tag1,tag2"
605
606         capture_stdout = cStringIO.StringIO()
607         try:
608             exited = arvados_cwl.main(
609                 ["--submit", "--no-wait", "--api=containers", "--debug", "--output-tags", output_tags,
610                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
611                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
612             self.assertEqual(exited, 0)
613         except:
614             logging.exception("")
615
616         expect_container = copy.deepcopy(stubs.expect_container_spec)
617         expect_container["command"] = ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
618                                                   "--output-tags="+output_tags, '--enable-reuse', '--on-error=continue',
619                                                   '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json']
620
621         stubs.api.container_requests().create.assert_called_with(
622             body=JsonDiffMatcher(expect_container))
623         self.assertEqual(capture_stdout.getvalue(),
624                          stubs.expect_container_request_uuid + '\n')
625
626     @stubs
627     def test_submit_container_runner_ram(self, stubs):
628         capture_stdout = cStringIO.StringIO()
629         try:
630             exited = arvados_cwl.main(
631                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-ram=2048",
632                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
633                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
634             self.assertEqual(exited, 0)
635         except:
636             logging.exception("")
637
638         expect_container = copy.deepcopy(stubs.expect_container_spec)
639         expect_container["runtime_constraints"]["ram"] = 2048*1024*1024
640
641         stubs.api.container_requests().create.assert_called_with(
642             body=JsonDiffMatcher(expect_container))
643         self.assertEqual(capture_stdout.getvalue(),
644                          stubs.expect_container_request_uuid + '\n')
645
646     @mock.patch("arvados.collection.CollectionReader")
647     @mock.patch("time.sleep")
648     @stubs
649     def test_submit_file_keepref(self, stubs, tm, collectionReader):
650         capture_stdout = cStringIO.StringIO()
651         exited = arvados_cwl.main(
652             ["--submit", "--no-wait", "--api=containers", "--debug",
653              "tests/wf/submit_keepref_wf.cwl"],
654             capture_stdout, sys.stderr, api_client=stubs.api)
655         self.assertEqual(exited, 0)
656
657
658     @mock.patch("arvados.collection.CollectionReader")
659     @mock.patch("time.sleep")
660     @stubs
661     def test_submit_keepref(self, stubs, tm, reader):
662         capture_stdout = cStringIO.StringIO()
663
664         with open("tests/wf/expect_arvworkflow.cwl") as f:
665             reader().open().__enter__().read.return_value = f.read()
666
667         exited = arvados_cwl.main(
668             ["--submit", "--no-wait", "--api=containers", "--debug",
669              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
670             capture_stdout, sys.stderr, api_client=stubs.api)
671         self.assertEqual(exited, 0)
672
673         expect_container = {
674             'priority': 1,
675             'mounts': {
676                 '/var/spool/cwl': {
677                     'writable': True,
678                     'kind': 'collection'
679                 },
680                 'stdout': {
681                     'path': '/var/spool/cwl/cwl.output.json',
682                     'kind': 'file'
683                 },
684                 '/var/lib/cwl/workflow': {
685                     'portable_data_hash': '99999999999999999999999999999994+99',
686                     'kind': 'collection'
687                 },
688                 '/var/lib/cwl/cwl.input.json': {
689                     'content': {
690                         'x': 'XxX'
691                     },
692                     'kind': 'json'
693                 }
694             }, 'state': 'Committed',
695             'owner_uuid': None,
696             'output_path': '/var/spool/cwl',
697             'name': 'expect_arvworkflow.cwl#main',
698             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
699             'command': ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
700                         '--enable-reuse', '--on-error=continue',
701                         '/var/lib/cwl/workflow/expect_arvworkflow.cwl#main', '/var/lib/cwl/cwl.input.json'],
702             'cwd': '/var/spool/cwl',
703             'runtime_constraints': {
704                 'API': True,
705                 'vcpus': 1,
706                 'ram': 1073741824
707             },
708             "properties": {}
709         }
710
711         stubs.api.container_requests().create.assert_called_with(
712             body=JsonDiffMatcher(expect_container))
713         self.assertEqual(capture_stdout.getvalue(),
714                          stubs.expect_container_request_uuid + '\n')
715
716
717     @mock.patch("arvados.collection.CollectionReader")
718     @mock.patch("time.sleep")
719     @stubs
720     def test_submit_jobs_keepref(self, stubs, tm, reader):
721         capture_stdout = cStringIO.StringIO()
722
723         with open("tests/wf/expect_arvworkflow.cwl") as f:
724             reader().open().__enter__().read.return_value = f.read()
725
726         exited = arvados_cwl.main(
727             ["--submit", "--no-wait", "--api=jobs", "--debug",
728              "keep:99999999999999999999999999999994+99/expect_arvworkflow.cwl#main", "-x", "XxX"],
729             capture_stdout, sys.stderr, api_client=stubs.api)
730         self.assertEqual(exited, 0)
731
732         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
733         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["x"] = "XxX"
734         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["y"]
735         del expect_pipeline["components"]["cwl-runner"]["script_parameters"]["z"]
736         expect_pipeline["components"]["cwl-runner"]["script_parameters"]["cwl:tool"] = "99999999999999999999999999999994+99/expect_arvworkflow.cwl#main"
737         expect_pipeline["name"] = "expect_arvworkflow.cwl#main"
738         stubs.api.pipeline_instances().create.assert_called_with(
739             body=JsonDiffMatcher(expect_pipeline))
740
741     @mock.patch("time.sleep")
742     @stubs
743     def test_submit_arvworkflow(self, stubs, tm):
744         capture_stdout = cStringIO.StringIO()
745
746         with open("tests/wf/expect_arvworkflow.cwl") as f:
747             stubs.api.workflows().get().execute.return_value = {"definition": f.read(), "name": "a test workflow"}
748
749         exited = arvados_cwl.main(
750             ["--submit", "--no-wait", "--api=containers", "--debug",
751              "962eh-7fd4e-gkbzl62qqtfig37", "-x", "XxX"],
752             capture_stdout, sys.stderr, api_client=stubs.api)
753         self.assertEqual(exited, 0)
754
755         expect_container = {
756             'priority': 1,
757             'mounts': {
758                 '/var/spool/cwl': {
759                     'writable': True,
760                     'kind': 'collection'
761                 },
762                 'stdout': {
763                     'path': '/var/spool/cwl/cwl.output.json',
764                     'kind': 'file'
765                 },
766                 '/var/lib/cwl/workflow.json': {
767                     'kind': 'json',
768                     'content': {
769                         'cwlVersion': 'v1.0',
770                         '$graph': [
771                             {
772                                 'id': '#main',
773                                 'inputs': [
774                                     {'type': 'string', 'id': '#main/x'}
775                                 ],
776                                 'steps': [
777                                     {'in': [{'source': '#main/x', 'id': '#main/step1/x'}],
778                                      'run': '#submit_tool.cwl',
779                                      'id': '#main/step1',
780                                      'out': []}
781                                 ],
782                                 'class': 'Workflow',
783                                 'outputs': []
784                             },
785                             {
786                                 'inputs': [
787                                     {
788                                         'inputBinding': {'position': 1},
789                                         'type': 'string',
790                                         'id': '#submit_tool.cwl/x'}
791                                 ],
792                                 'requirements': [
793                                     {'dockerPull': 'debian:8', 'class': 'DockerRequirement'}
794                                 ],
795                                 'id': '#submit_tool.cwl',
796                                 'outputs': [],
797                                 'baseCommand': 'cat',
798                                 'class': 'CommandLineTool'
799                             }
800                         ]
801                     }
802                 },
803                 '/var/lib/cwl/cwl.input.json': {
804                     'content': {
805                         'x': 'XxX'
806                     },
807                     'kind': 'json'
808                 }
809             }, 'state': 'Committed',
810             'owner_uuid': None,
811             'output_path': '/var/spool/cwl',
812             'name': 'a test workflow',
813             'container_image': 'arvados/jobs:'+arvados_cwl.__version__,
814             'command': ['arvados-cwl-runner', '--local', '--api=containers', '--no-log-timestamps',
815                         '--enable-reuse', '--on-error=continue',
816                         '/var/lib/cwl/workflow.json#main', '/var/lib/cwl/cwl.input.json'],
817             'cwd': '/var/spool/cwl',
818             'runtime_constraints': {
819                 'API': True,
820                 'vcpus': 1,
821                 'ram': 1073741824
822             },
823             "properties": {
824                 "template_uuid": "962eh-7fd4e-gkbzl62qqtfig37"
825             }
826         }
827
828         stubs.api.container_requests().create.assert_called_with(
829             body=JsonDiffMatcher(expect_container))
830         self.assertEqual(capture_stdout.getvalue(),
831                          stubs.expect_container_request_uuid + '\n')
832
833
834     @stubs
835     def test_submit_container_name(self, stubs):
836         capture_stdout = cStringIO.StringIO()
837         try:
838             exited = arvados_cwl.main(
839                 ["--submit", "--no-wait", "--api=containers", "--debug", "--name=hello container 123",
840                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
841                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
842             self.assertEqual(exited, 0)
843         except:
844             logging.exception("")
845
846         expect_container = copy.deepcopy(stubs.expect_container_spec)
847         expect_container["name"] = "hello container 123"
848
849         stubs.api.container_requests().create.assert_called_with(
850             body=JsonDiffMatcher(expect_container))
851         self.assertEqual(capture_stdout.getvalue(),
852                          stubs.expect_container_request_uuid + '\n')
853
854
855     @stubs
856     def test_submit_job_runner_image(self, stubs):
857         capture_stdout = cStringIO.StringIO()
858         try:
859             exited = arvados_cwl.main(
860                 ["--submit", "--no-wait", "--api=jobs", "--debug", "--submit-runner-image=arvados/jobs:123",
861                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
862                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
863             self.assertEqual(exited, 0)
864         except:
865             logging.exception("")
866
867         stubs.expect_pipeline_instance["components"]["cwl-runner"]["runtime_constraints"]["docker_image"] = "arvados/jobs:123"
868
869         expect_pipeline = copy.deepcopy(stubs.expect_pipeline_instance)
870         stubs.api.pipeline_instances().create.assert_called_with(
871             body=JsonDiffMatcher(expect_pipeline))
872         self.assertEqual(capture_stdout.getvalue(),
873                          stubs.expect_pipeline_uuid + '\n')
874
875     @stubs
876     def test_submit_container_runner_image(self, stubs):
877         capture_stdout = cStringIO.StringIO()
878         try:
879             exited = arvados_cwl.main(
880                 ["--submit", "--no-wait", "--api=containers", "--debug", "--submit-runner-image=arvados/jobs:123",
881                  "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
882                 capture_stdout, sys.stderr, api_client=stubs.api, keep_client=stubs.keep_client)
883             self.assertEqual(exited, 0)
884         except:
885             logging.exception("")
886
887         stubs.expect_container_spec["container_image"] = "arvados/jobs:123"
888
889         expect_container = copy.deepcopy(stubs.expect_container_spec)
890         stubs.api.container_requests().create.assert_called_with(
891             body=JsonDiffMatcher(expect_container))
892         self.assertEqual(capture_stdout.getvalue(),
893                          stubs.expect_container_request_uuid + '\n')
894
895
896     @mock.patch("arvados.commands.keepdocker.find_one_image_hash")
897     @mock.patch("cwltool.docker.get_image")
898     @mock.patch("arvados.api")
899     def test_arvados_jobs_image(self, api, get_image, find_one_image_hash):
900         arvrunner = mock.MagicMock()
901         arvrunner.project_uuid = ""
902         api.return_value = mock.MagicMock()
903         arvrunner.api = api.return_value
904         arvrunner.api.links().list().execute.side_effect = ({"items": [{"created_at": "",
905                                                                         "head_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
906                                                                         "link_class": "docker_image_repo+tag",
907                                                                         "name": "arvados/jobs:"+arvados_cwl.__version__,
908                                                                         "owner_uuid": "",
909                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0},
910                                                             {"items": [{"created_at": "",
911                                                                         "head_uuid": "",
912                                                                         "link_class": "docker_image_hash",
913                                                                         "name": "123456",
914                                                                         "owner_uuid": "",
915                                                                         "properties": {"image_timestamp": ""}}], "items_available": 1, "offset": 0}
916         )
917         find_one_image_hash.return_value = "123456"
918
919         arvrunner.api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzzb",
920                                                                               "owner_uuid": "",
921                                                                               "manifest_text": "",
922                                                                               "properties": ""
923                                                                           }], "items_available": 1, "offset": 0},)
924         arvrunner.api.collections().create().execute.return_value = {"uuid": ""}
925         self.assertEqual("arvados/jobs:"+arvados_cwl.__version__,
926                          arvados_cwl.runner.arvados_jobs_image(arvrunner, "arvados/jobs:"+arvados_cwl.__version__))
927
928 class TestCreateTemplate(unittest.TestCase):
929     existing_template_uuid = "zzzzz-d1hrv-validworkfloyml"
930
931     def _adjust_script_params(self, expect_component):
932         expect_component['script_parameters']['x'] = {
933             'dataclass': 'File',
934             'required': True,
935             'type': 'File',
936             'value': '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt',
937         }
938         expect_component['script_parameters']['y'] = {
939             'dataclass': 'Collection',
940             'required': True,
941             'type': 'Directory',
942             'value': '99999999999999999999999999999998+99',
943         }
944         expect_component['script_parameters']['z'] = {
945             'dataclass': 'Collection',
946             'required': True,
947             'type': 'Directory',
948         }
949
950     @stubs
951     def test_create(self, stubs):
952         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
953
954         capture_stdout = cStringIO.StringIO()
955
956         exited = arvados_cwl.main(
957             ["--create-workflow", "--debug",
958              "--api=jobs",
959              "--project-uuid", project_uuid,
960              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
961             capture_stdout, sys.stderr, api_client=stubs.api)
962         self.assertEqual(exited, 0)
963
964         stubs.api.pipeline_instances().create.refute_called()
965         stubs.api.jobs().create.refute_called()
966
967         expect_component = copy.deepcopy(stubs.expect_job_spec)
968         self._adjust_script_params(expect_component)
969         expect_template = {
970             "components": {
971                 "submit_wf.cwl": expect_component,
972             },
973             "name": "submit_wf.cwl",
974             "owner_uuid": project_uuid,
975         }
976         stubs.api.pipeline_templates().create.assert_called_with(
977             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
978
979         self.assertEqual(capture_stdout.getvalue(),
980                          stubs.expect_pipeline_template_uuid + '\n')
981
982
983     @stubs
984     def test_create_name(self, stubs):
985         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
986
987         capture_stdout = cStringIO.StringIO()
988
989         exited = arvados_cwl.main(
990             ["--create-workflow", "--debug",
991              "--project-uuid", project_uuid,
992              "--api=jobs",
993              "--name", "testing 123",
994              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
995             capture_stdout, sys.stderr, api_client=stubs.api)
996         self.assertEqual(exited, 0)
997
998         stubs.api.pipeline_instances().create.refute_called()
999         stubs.api.jobs().create.refute_called()
1000
1001         expect_component = copy.deepcopy(stubs.expect_job_spec)
1002         self._adjust_script_params(expect_component)
1003         expect_template = {
1004             "components": {
1005                 "testing 123": expect_component,
1006             },
1007             "name": "testing 123",
1008             "owner_uuid": project_uuid,
1009         }
1010         stubs.api.pipeline_templates().create.assert_called_with(
1011             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)
1012
1013         self.assertEqual(capture_stdout.getvalue(),
1014                          stubs.expect_pipeline_template_uuid + '\n')
1015
1016
1017     @stubs
1018     def test_update_name(self, stubs):
1019         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1020
1021         capture_stdout = cStringIO.StringIO()
1022
1023         exited = arvados_cwl.main(
1024             ["--update-workflow", self.existing_template_uuid,
1025              "--debug",
1026              "--project-uuid", project_uuid,
1027              "--api=jobs",
1028              "--name", "testing 123",
1029              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1030             capture_stdout, sys.stderr, api_client=stubs.api)
1031         self.assertEqual(exited, 0)
1032
1033         stubs.api.pipeline_instances().create.refute_called()
1034         stubs.api.jobs().create.refute_called()
1035
1036         expect_component = copy.deepcopy(stubs.expect_job_spec)
1037         self._adjust_script_params(expect_component)
1038         expect_template = {
1039             "components": {
1040                 "testing 123": expect_component,
1041             },
1042             "name": "testing 123",
1043             "owner_uuid": project_uuid,
1044         }
1045         stubs.api.pipeline_templates().create.refute_called()
1046         stubs.api.pipeline_templates().update.assert_called_with(
1047             body=JsonDiffMatcher(expect_template), uuid=self.existing_template_uuid)
1048
1049         self.assertEqual(capture_stdout.getvalue(),
1050                          self.existing_template_uuid + '\n')
1051
1052
1053 class TestCreateWorkflow(unittest.TestCase):
1054     existing_workflow_uuid = "zzzzz-7fd4e-validworkfloyml"
1055     expect_workflow = StripYAMLComments(
1056         open("tests/wf/expect_packed.cwl").read())
1057
1058     @stubs
1059     def test_create(self, stubs):
1060         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1061
1062         capture_stdout = cStringIO.StringIO()
1063
1064         exited = arvados_cwl.main(
1065             ["--create-workflow", "--debug",
1066              "--api=containers",
1067              "--project-uuid", project_uuid,
1068              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1069             capture_stdout, sys.stderr, api_client=stubs.api)
1070         self.assertEqual(exited, 0)
1071
1072         stubs.api.pipeline_templates().create.refute_called()
1073         stubs.api.container_requests().create.refute_called()
1074
1075         body = {
1076             "workflow": {
1077                 "owner_uuid": project_uuid,
1078                 "name": "submit_wf.cwl",
1079                 "description": "",
1080                 "definition": self.expect_workflow,
1081             }
1082         }
1083         stubs.api.workflows().create.assert_called_with(
1084             body=JsonDiffMatcher(body))
1085
1086         self.assertEqual(capture_stdout.getvalue(),
1087                          stubs.expect_workflow_uuid + '\n')
1088
1089
1090     @stubs
1091     def test_create_name(self, stubs):
1092         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1093
1094         capture_stdout = cStringIO.StringIO()
1095
1096         exited = arvados_cwl.main(
1097             ["--create-workflow", "--debug",
1098              "--api=containers",
1099              "--project-uuid", project_uuid,
1100              "--name", "testing 123",
1101              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1102             capture_stdout, sys.stderr, api_client=stubs.api)
1103         self.assertEqual(exited, 0)
1104
1105         stubs.api.pipeline_templates().create.refute_called()
1106         stubs.api.container_requests().create.refute_called()
1107
1108         body = {
1109             "workflow": {
1110                 "owner_uuid": project_uuid,
1111                 "name": "testing 123",
1112                 "description": "",
1113                 "definition": self.expect_workflow,
1114             }
1115         }
1116         stubs.api.workflows().create.assert_called_with(
1117             body=JsonDiffMatcher(body))
1118
1119         self.assertEqual(capture_stdout.getvalue(),
1120                          stubs.expect_workflow_uuid + '\n')
1121
1122     @stubs
1123     def test_incompatible_api(self, stubs):
1124         capture_stderr = cStringIO.StringIO()
1125         logging.getLogger('arvados.cwl-runner').addHandler(
1126             logging.StreamHandler(capture_stderr))
1127
1128         exited = arvados_cwl.main(
1129             ["--update-workflow", self.existing_workflow_uuid,
1130              "--api=jobs",
1131              "--debug",
1132              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1133             sys.stderr, sys.stderr, api_client=stubs.api)
1134         self.assertEqual(exited, 1)
1135         self.assertRegexpMatches(
1136             capture_stderr.getvalue(),
1137             "--update-workflow arg '{}' uses 'containers' API, but --api='jobs' specified".format(self.existing_workflow_uuid))
1138
1139     @stubs
1140     def test_update(self, stubs):
1141         capture_stdout = cStringIO.StringIO()
1142
1143         exited = arvados_cwl.main(
1144             ["--update-workflow", self.existing_workflow_uuid,
1145              "--debug",
1146              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1147             capture_stdout, sys.stderr, api_client=stubs.api)
1148         self.assertEqual(exited, 0)
1149
1150         body = {
1151             "workflow": {
1152                 "name": "submit_wf.cwl",
1153                 "description": "",
1154                 "definition": self.expect_workflow,
1155             }
1156         }
1157         stubs.api.workflows().update.assert_called_with(
1158             uuid=self.existing_workflow_uuid,
1159             body=JsonDiffMatcher(body))
1160         self.assertEqual(capture_stdout.getvalue(),
1161                          self.existing_workflow_uuid + '\n')
1162
1163
1164     @stubs
1165     def test_update_name(self, stubs):
1166         capture_stdout = cStringIO.StringIO()
1167
1168         exited = arvados_cwl.main(
1169             ["--update-workflow", self.existing_workflow_uuid,
1170              "--debug", "--name", "testing 123",
1171              "tests/wf/submit_wf.cwl", "tests/submit_test_job.json"],
1172             capture_stdout, sys.stderr, api_client=stubs.api)
1173         self.assertEqual(exited, 0)
1174
1175         body = {
1176             "workflow": {
1177                 "name": "testing 123",
1178                 "description": "",
1179                 "definition": self.expect_workflow,
1180             }
1181         }
1182         stubs.api.workflows().update.assert_called_with(
1183             uuid=self.existing_workflow_uuid,
1184             body=JsonDiffMatcher(body))
1185         self.assertEqual(capture_stdout.getvalue(),
1186                          self.existing_workflow_uuid + '\n')
1187
1188
1189     @stubs
1190     def test_create_collection_per_tool(self, stubs):
1191         project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
1192
1193         capture_stdout = cStringIO.StringIO()
1194
1195         exited = arvados_cwl.main(
1196             ["--create-workflow", "--debug",
1197              "--api=containers",
1198              "--project-uuid", project_uuid,
1199              "tests/collection_per_tool/collection_per_tool.cwl"],
1200             capture_stdout, sys.stderr, api_client=stubs.api)
1201         self.assertEqual(exited, 0)
1202
1203         toolfile = "tests/collection_per_tool/collection_per_tool_packed.cwl"
1204         expect_workflow = StripYAMLComments(open(toolfile).read())
1205
1206         body = {
1207             "workflow": {
1208                 "owner_uuid": project_uuid,
1209                 "name": "collection_per_tool.cwl",
1210                 "description": "",
1211                 "definition": expect_workflow,
1212             }
1213         }
1214         stubs.api.workflows().create.assert_called_with(
1215             body=JsonDiffMatcher(body))
1216
1217         self.assertEqual(capture_stdout.getvalue(),
1218                          stubs.expect_workflow_uuid + '\n')
1219
1220 class TestTemplateInputs(unittest.TestCase):
1221     expect_template = {
1222         "components": {
1223             "inputs_test.cwl": {
1224                 'runtime_constraints': {
1225                     'docker_image': 'arvados/jobs:'+arvados_cwl.__version__,
1226                     'min_ram_mb_per_node': 1024
1227                 },
1228                 'script_parameters': {
1229                     'cwl:tool':
1230                     '6c5ee1cd606088106d9f28367cde1e41+60/workflow.cwl#main',
1231                     'optionalFloatInput': None,
1232                     'fileInput': {
1233                         'type': 'File',
1234                         'dataclass': 'File',
1235                         'required': True,
1236                         'title': "It's a file; we expect to find some characters in it.",
1237                         'description': 'If there were anything further to say, it would be said here,\nor here.'
1238                     },
1239                     'floatInput': {
1240                         'type': 'float',
1241                         'dataclass': 'number',
1242                         'required': True,
1243                         'title': 'Floats like a duck',
1244                         'default': 0.1,
1245                         'value': 0.1,
1246                     },
1247                     'optionalFloatInput': {
1248                         'type': ['null', 'float'],
1249                         'dataclass': 'number',
1250                         'required': False,
1251                     },
1252                     'boolInput': {
1253                         'type': 'boolean',
1254                         'dataclass': 'boolean',
1255                         'required': True,
1256                         'title': 'True or false?',
1257                     },
1258                 },
1259                 'repository': 'arvados',
1260                 'script_version': 'master',
1261                 'minimum_script_version': '570509ab4d2ef93d870fd2b1f2eab178afb1bad9',
1262                 'script': 'cwl-runner',
1263             },
1264         },
1265         "name": "inputs_test.cwl",
1266     }
1267
1268     @stubs
1269     def test_inputs_empty(self, stubs):
1270         exited = arvados_cwl.main(
1271             ["--create-template",
1272              "tests/wf/inputs_test.cwl", "tests/order/empty_order.json"],
1273             cStringIO.StringIO(), sys.stderr, api_client=stubs.api)
1274         self.assertEqual(exited, 0)
1275
1276         stubs.api.pipeline_templates().create.assert_called_with(
1277             body=JsonDiffMatcher(self.expect_template), ensure_unique_name=True)
1278
1279     @stubs
1280     def test_inputs(self, stubs):
1281         exited = arvados_cwl.main(
1282             ["--create-template",
1283              "tests/wf/inputs_test.cwl", "tests/order/inputs_test_order.json"],
1284             cStringIO.StringIO(), sys.stderr, api_client=stubs.api)
1285         self.assertEqual(exited, 0)
1286
1287         expect_template = copy.deepcopy(self.expect_template)
1288         params = expect_template[
1289             "components"]["inputs_test.cwl"]["script_parameters"]
1290         params["fileInput"]["value"] = '169f39d466a5438ac4a90e779bf750c7+53/blorp.txt'
1291         params["cwl:tool"] = '6c5ee1cd606088106d9f28367cde1e41+60/workflow.cwl#main'
1292         params["floatInput"]["value"] = 1.234
1293         params["boolInput"]["value"] = True
1294
1295         stubs.api.pipeline_templates().create.assert_called_with(
1296             body=JsonDiffMatcher(expect_template), ensure_unique_name=True)