13822: Don't call list_sizes() in cloud client constructor.
[arvados.git] / sdk / cwl / tests / test_job.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 import functools
6 import json
7 import logging
8 import mock
9 import os
10 import unittest
11 import copy
12 import StringIO
13
14 import arvados
15 import arvados_cwl
16 import cwltool.process
17 from arvados.errors import ApiError
18 from schema_salad.ref_resolver import Loader
19 from schema_salad.sourceline import cmap
20 from .mock_discovery import get_rootDesc
21 from .matcher import JsonDiffMatcher, StripYAMLComments
22
23 if not os.getenv('ARVADOS_DEBUG'):
24     logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
25     logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
26
27 class TestJob(unittest.TestCase):
28
29     # The test passes no builder.resources
30     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
31     @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
32     def test_run(self, list_images_in_arv):
33         for enable_reuse in (True, False):
34             runner = mock.MagicMock()
35             runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
36             runner.ignore_docker_for_reuse = False
37             runner.num_retries = 0
38             document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
39
40             list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
41             runner.api.collections().get().execute.return_value = {"portable_data_hash": "99999999999999999999999999999993+99"}
42             # Simulate reused job from another project so that we can check is a can_read
43             # link is added.
44             runner.api.jobs().create().execute.return_value = {
45                 'state': 'Complete' if enable_reuse else 'Queued',
46                 'owner_uuid': 'zzzzz-tpzed-yyyyyyyyyyyyyyy' if enable_reuse else 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
47                 'uuid': 'zzzzz-819sb-yyyyyyyyyyyyyyy',
48                 'output': None,
49             }
50
51             tool = cmap({
52                 "inputs": [],
53                 "outputs": [],
54                 "baseCommand": "ls",
55                 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
56                 "id": "#",
57                 "class": "CommandLineTool"
58             })
59             make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
60                                          collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
61             arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names,
62                                                      basedir="", make_fs_access=make_fs_access, loader=Loader({}))
63             arvtool.formatgraph = None
64             for j in arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access):
65                 j.run(enable_reuse=enable_reuse)
66                 runner.api.jobs().create.assert_called_with(
67                     body=JsonDiffMatcher({
68                         'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
69                         'runtime_constraints': {},
70                         'script_parameters': {
71                             'tasks': [{
72                                 'task.env': {'HOME': '$(task.outdir)', 'TMPDIR': '$(task.tmpdir)'},
73                                 'command': ['ls', '$(task.outdir)']
74                             }],
75                         },
76                         'script_version': 'master',
77                         'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
78                         'repository': 'arvados',
79                         'script': 'crunchrunner',
80                         'runtime_constraints': {
81                             'docker_image': 'arvados/jobs',
82                             'min_cores_per_node': 1,
83                             'min_ram_mb_per_node': 1024,
84                             'min_scratch_mb_per_node': 2048 # tmpdirSize + outdirSize
85                         }
86                     }),
87                     find_or_create=enable_reuse,
88                     filters=[['repository', '=', 'arvados'],
89                              ['script', '=', 'crunchrunner'],
90                              ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
91                              ['docker_image_locator', 'in docker', 'arvados/jobs']]
92                 )
93                 if enable_reuse:
94                     runner.api.links().create.assert_called_with(
95                         body=JsonDiffMatcher({
96                             'link_class': 'permission',
97                             'name': 'can_read',
98                             "tail_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
99                             "head_uuid": "zzzzz-819sb-yyyyyyyyyyyyyyy",
100                         })
101                     )
102                     # Simulate an API excepction when trying to create a
103                     # sharing link on the job
104                     runner.api.links().create.side_effect = ApiError(
105                         mock.MagicMock(return_value={'status': 403}),
106                         'Permission denied')
107                     j.run(enable_reuse=enable_reuse)
108                     j.output_callback.assert_called_with({}, 'success')
109                 else:
110                     assert not runner.api.links().create.called
111
112     # The test passes some fields in builder.resources
113     # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
114     @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
115     def test_resource_requirements(self, list_images_in_arv):
116         runner = mock.MagicMock()
117         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
118         runner.ignore_docker_for_reuse = False
119         runner.num_retries = 0
120         arvados_cwl.add_arv_hints()
121
122         list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
123         runner.api.collections().get().execute.return_vaulue = {"portable_data_hash": "99999999999999999999999999999993+99"}
124
125         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
126
127
128         tool = {
129             "inputs": [],
130             "outputs": [],
131             "hints": [{
132                 "class": "ResourceRequirement",
133                 "coresMin": 3,
134                 "ramMin": 3000,
135                 "tmpdirMin": 4000
136             }, {
137                 "class": "http://arvados.org/cwl#RuntimeConstraints",
138                 "keep_cache": 512,
139                 "outputDirType": "keep_output_dir"
140             }, {
141                 "class": "http://arvados.org/cwl#APIRequirement",
142             },
143             {
144                 "class": "http://arvados.org/cwl#ReuseRequirement",
145                 "enableReuse": False
146             }],
147             "baseCommand": "ls",
148             "id": "#",
149             "class": "CommandLineTool"
150         }
151         make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
152                                          collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
153         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names,
154                                                  make_fs_access=make_fs_access, loader=Loader({}))
155         arvtool.formatgraph = None
156         for j in arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access):
157             j.run(enable_reuse=True)
158         runner.api.jobs().create.assert_called_with(
159             body=JsonDiffMatcher({
160                 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
161                 'runtime_constraints': {},
162                 'script_parameters': {
163                     'tasks': [{
164                         'task.env': {'HOME': '$(task.outdir)', 'TMPDIR': '$(task.tmpdir)'},
165                         'task.keepTmpOutput': True,
166                         'command': ['ls']
167                     }]
168             },
169             'script_version': 'master',
170                 'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
171                 'repository': 'arvados',
172                 'script': 'crunchrunner',
173                 'runtime_constraints': {
174                     'docker_image': 'arvados/jobs',
175                     'min_cores_per_node': 3,
176                     'min_ram_mb_per_node': 3512,     # ramMin + keep_cache
177                     'min_scratch_mb_per_node': 5024, # tmpdirSize + outdirSize
178                     'keep_cache_mb_per_task': 512
179                 }
180             }),
181             find_or_create=False,
182             filters=[['repository', '=', 'arvados'],
183                      ['script', '=', 'crunchrunner'],
184                      ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
185                      ['docker_image_locator', 'in docker', 'arvados/jobs']])
186
187     @mock.patch("arvados.collection.CollectionReader")
188     def test_done(self, reader):
189         api = mock.MagicMock()
190
191         runner = mock.MagicMock()
192         runner.api = api
193         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
194         runner.num_retries = 0
195         runner.ignore_docker_for_reuse = False
196
197         reader().open.return_value = StringIO.StringIO(
198             """2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.tmpdir)=/tmp/crunch-job-task-work/compute3.1/tmpdir
199 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.outdir)=/tmp/crunch-job-task-work/compute3.1/outdir
200 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.keep)=/keep
201         """)
202         api.collections().list().execute.side_effect = ({"items": []},
203                                                         {"items": [{"manifest_text": "XYZ"}]},
204                                                         {"items": []},
205                                                         {"items": [{"manifest_text": "ABC"}]})
206
207         arvjob = arvados_cwl.ArvadosJob(runner)
208         arvjob.name = "testjob"
209         arvjob.builder = mock.MagicMock()
210         arvjob.output_callback = mock.MagicMock()
211         arvjob.collect_outputs = mock.MagicMock()
212         arvjob.collect_outputs.return_value = {"out": "stuff"}
213
214         arvjob.done({
215             "state": "Complete",
216             "output": "99999999999999999999999999999993+99",
217             "log": "99999999999999999999999999999994+99",
218             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
219         })
220
221         api.collections().list.assert_has_calls([
222             mock.call(),
223             # Output collection check
224             mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
225                           ['portable_data_hash', '=', '99999999999999999999999999999993+99'],
226                           ['name', '=', 'Output 9999999 of testjob']]),
227             mock.call().execute(num_retries=0),
228             mock.call(limit=1, filters=[['portable_data_hash', '=', '99999999999999999999999999999993+99']],
229                  select=['manifest_text']),
230             mock.call().execute(num_retries=0),
231             # Log collection's turn
232             mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
233                           ['portable_data_hash', '=', '99999999999999999999999999999994+99'],
234                           ['name', '=', 'Log of zzzzz-8i9sb-zzzzzzzzzzzzzzz']]),
235             mock.call().execute(num_retries=0),
236             mock.call(limit=1, filters=[['portable_data_hash', '=', '99999999999999999999999999999994+99']],
237                  select=['manifest_text']),
238             mock.call().execute(num_retries=0)])
239
240         api.collections().create.assert_has_calls([
241             mock.call(ensure_unique_name=True,
242                       body={'portable_data_hash': '99999999999999999999999999999993+99',
243                             'manifest_text': 'XYZ',
244                             'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
245                             'name': 'Output 9999999 of testjob'}),
246             mock.call().execute(num_retries=0),
247             mock.call(ensure_unique_name=True,
248                       body={'portable_data_hash': '99999999999999999999999999999994+99',
249                             'manifest_text': 'ABC',
250                             'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
251                             'name': 'Log of zzzzz-8i9sb-zzzzzzzzzzzzzzz'}),
252             mock.call().execute(num_retries=0),
253         ])
254
255         arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
256
257     @mock.patch("arvados.collection.CollectionReader")
258     def test_done_use_existing_collection(self, reader):
259         api = mock.MagicMock()
260
261         runner = mock.MagicMock()
262         runner.api = api
263         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
264         runner.num_retries = 0
265
266         reader().open.return_value = StringIO.StringIO(
267             """2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.tmpdir)=/tmp/crunch-job-task-work/compute3.1/tmpdir
268 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.outdir)=/tmp/crunch-job-task-work/compute3.1/outdir
269 2016-11-02_23:12:18 c97qk-8i9sb-cryqw2blvzy4yaj 13358 0 stderr 2016/11/02 23:12:18 crunchrunner: $(task.keep)=/keep
270         """)
271
272         api.collections().list().execute.side_effect = (
273             {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2"}]},
274             {"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2"}]},
275         )
276
277         arvjob = arvados_cwl.ArvadosJob(runner)
278         arvjob.name = "testjob"
279         arvjob.builder = mock.MagicMock()
280         arvjob.output_callback = mock.MagicMock()
281         arvjob.collect_outputs = mock.MagicMock()
282         arvjob.collect_outputs.return_value = {"out": "stuff"}
283
284         arvjob.done({
285             "state": "Complete",
286             "output": "99999999999999999999999999999993+99",
287             "log": "99999999999999999999999999999994+99",
288             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
289         })
290
291         api.collections().list.assert_has_calls([
292             mock.call(),
293             # Output collection
294             mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
295                                ['portable_data_hash', '=', '99999999999999999999999999999993+99'],
296                                ['name', '=', 'Output 9999999 of testjob']]),
297             mock.call().execute(num_retries=0),
298             # Log collection
299             mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
300                                ['portable_data_hash', '=', '99999999999999999999999999999994+99'],
301                                ['name', '=', 'Log of zzzzz-8i9sb-zzzzzzzzzzzzzzz']]),
302             mock.call().execute(num_retries=0)
303         ])
304
305         self.assertFalse(api.collections().create.called)
306
307         arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
308
309
310 class TestWorkflow(unittest.TestCase):
311     # The test passes no builder.resources
312     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
313     @mock.patch("arvados.collection.CollectionReader")
314     @mock.patch("arvados.collection.Collection")
315     @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
316     def test_run(self, list_images_in_arv, mockcollection, mockcollectionreader):
317         arvados_cwl.add_arv_hints()
318
319         api = mock.MagicMock()
320         api._rootDesc = get_rootDesc()
321
322         runner = arvados_cwl.ArvCwlRunner(api)
323         self.assertEqual(runner.work_api, 'jobs')
324
325         list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
326         runner.api.collections().get().execute.return_vaulue = {"portable_data_hash": "99999999999999999999999999999993+99"}
327         runner.api.collections().list().execute.return_vaulue = {"items": [{"portable_data_hash": "99999999999999999999999999999993+99"}]}
328
329         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
330         runner.ignore_docker_for_reuse = False
331         runner.num_retries = 0
332         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
333
334         make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
335                                          collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
336         document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=api, fs_access=make_fs_access(""))
337         document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
338         document_loader.fetch_text = document_loader.fetcher.fetch_text
339         document_loader.check_exists = document_loader.fetcher.check_exists
340
341         tool, metadata = document_loader.resolve_ref("tests/wf/scatter2.cwl")
342         metadata["cwlVersion"] = tool["cwlVersion"]
343
344         mockcollection().portable_data_hash.return_value = "99999999999999999999999999999999+118"
345
346         arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, work_api="jobs", avsc_names=avsc_names,
347                                               basedir="", make_fs_access=make_fs_access, loader=document_loader,
348                                               makeTool=runner.arv_make_tool, metadata=metadata)
349         arvtool.formatgraph = None
350         it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access)
351         it.next().run()
352         it.next().run()
353
354         with open("tests/wf/scatter2_subwf.cwl") as f:
355             subwf = StripYAMLComments(f.read())
356
357         runner.api.jobs().create.assert_called_with(
358             body=JsonDiffMatcher({
359                 'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
360                 'repository': 'arvados',
361                 'script_version': 'master',
362                 'script': 'crunchrunner',
363                 'script_parameters': {
364                     'tasks': [{'task.env': {
365                         'HOME': '$(task.outdir)',
366                         'TMPDIR': '$(task.tmpdir)'},
367                                'task.vwd': {
368                                    'workflow.cwl': '$(task.keep)/99999999999999999999999999999999+118/workflow.cwl',
369                                    'cwl.input.yml': '$(task.keep)/99999999999999999999999999999999+118/cwl.input.yml'
370                                },
371                     'command': [u'cwltool', u'--no-container', u'--move-outputs', u'--preserve-entire-environment', u'workflow.cwl#main', u'cwl.input.yml'],
372                     'task.stdout': 'cwl.output.json'}]},
373                 'runtime_constraints': {
374                     'min_scratch_mb_per_node': 2048,
375                     'min_cores_per_node': 1,
376                     'docker_image': 'arvados/jobs',
377                     'min_ram_mb_per_node': 1024
378                 },
379                 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}),
380             filters=[['repository', '=', 'arvados'],
381                      ['script', '=', 'crunchrunner'],
382                      ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
383                      ['docker_image_locator', 'in docker', 'arvados/jobs']],
384             find_or_create=True)
385
386         mockcollection().open().__enter__().write.assert_has_calls([mock.call(subwf)])
387         mockcollection().open().__enter__().write.assert_has_calls([mock.call(
388 '''{
389   "fileblub": {
390     "basename": "token.txt",
391     "class": "File",
392     "location": "/keep/99999999999999999999999999999999+118/token.txt"
393   },
394   "sleeptime": 5
395 }''')])
396
397     # The test passes no builder.resources
398     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
399     @mock.patch("arvados.collection.CollectionReader")
400     @mock.patch("arvados.collection.Collection")
401     @mock.patch('arvados.commands.keepdocker.list_images_in_arv')
402     def test_overall_resource_singlecontainer(self, list_images_in_arv, mockcollection, mockcollectionreader):
403         arvados_cwl.add_arv_hints()
404
405         api = mock.MagicMock()
406         api._rootDesc = get_rootDesc()
407
408         runner = arvados_cwl.ArvCwlRunner(api)
409         self.assertEqual(runner.work_api, 'jobs')
410
411         list_images_in_arv.return_value = [["zzzzz-4zz18-zzzzzzzzzzzzzzz"]]
412         runner.api.collections().get().execute.return_vaulue = {"portable_data_hash": "99999999999999999999999999999993+99"}
413         runner.api.collections().list().execute.return_vaulue = {"items": [{"portable_data_hash": "99999999999999999999999999999993+99"}]}
414
415         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
416         runner.ignore_docker_for_reuse = False
417         runner.num_retries = 0
418         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
419
420         make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
421                                          collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
422         document_loader.fetcher_constructor = functools.partial(arvados_cwl.CollectionFetcher, api_client=api, fs_access=make_fs_access(""))
423         document_loader.fetcher = document_loader.fetcher_constructor(document_loader.cache, document_loader.session)
424         document_loader.fetch_text = document_loader.fetcher.fetch_text
425         document_loader.check_exists = document_loader.fetcher.check_exists
426
427         tool, metadata = document_loader.resolve_ref("tests/wf/echo-wf.cwl")
428         metadata["cwlVersion"] = tool["cwlVersion"]
429
430         mockcollection().portable_data_hash.return_value = "99999999999999999999999999999999+118"
431
432         arvtool = arvados_cwl.ArvadosWorkflow(runner, tool, work_api="jobs", avsc_names=avsc_names,
433                                               basedir="", make_fs_access=make_fs_access, loader=document_loader,
434                                               makeTool=runner.arv_make_tool, metadata=metadata)
435         arvtool.formatgraph = None
436         it = arvtool.job({}, mock.MagicMock(), basedir="", make_fs_access=make_fs_access)
437         it.next().run()
438         it.next().run()
439
440         with open("tests/wf/echo-subwf.cwl") as f:
441             subwf = StripYAMLComments(f.read())
442
443         runner.api.jobs().create.assert_called_with(
444             body=JsonDiffMatcher({
445                 'minimum_script_version': 'a3f2cb186e437bfce0031b024b2157b73ed2717d',
446                 'repository': 'arvados',
447                 'script_version': 'master',
448                 'script': 'crunchrunner',
449                 'script_parameters': {
450                     'tasks': [{'task.env': {
451                         'HOME': '$(task.outdir)',
452                         'TMPDIR': '$(task.tmpdir)'},
453                                'task.vwd': {
454                                    'workflow.cwl': '$(task.keep)/99999999999999999999999999999999+118/workflow.cwl',
455                                    'cwl.input.yml': '$(task.keep)/99999999999999999999999999999999+118/cwl.input.yml'
456                                },
457                     'command': [u'cwltool', u'--no-container', u'--move-outputs', u'--preserve-entire-environment', u'workflow.cwl#main', u'cwl.input.yml'],
458                     'task.stdout': 'cwl.output.json'}]},
459                 'runtime_constraints': {
460                     'min_scratch_mb_per_node': 4096,
461                     'min_cores_per_node': 3,
462                     'docker_image': 'arvados/jobs',
463                     'min_ram_mb_per_node': 1024
464                 },
465                 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'}),
466             filters=[['repository', '=', 'arvados'],
467                      ['script', '=', 'crunchrunner'],
468                      ['script_version', 'in git', 'a3f2cb186e437bfce0031b024b2157b73ed2717d'],
469                      ['docker_image_locator', 'in docker', 'arvados/jobs']],
470             find_or_create=True)
471
472     def test_default_work_api(self):
473         arvados_cwl.add_arv_hints()
474
475         api = mock.MagicMock()
476         api._rootDesc = copy.deepcopy(get_rootDesc())
477         del api._rootDesc.get('resources')['jobs']['methods']['create']
478         runner = arvados_cwl.ArvCwlRunner(api)
479         self.assertEqual(runner.work_api, 'containers')