1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
6 import arvados_cwl.context
7 import arvados_cwl.util
8 from arvados_cwl.arvdocker import arv_docker_clear_cache
16 import cwltool.process
17 import cwltool.secrets
18 from schema_salad.ref_resolver import Loader
19 from schema_salad.sourceline import cmap
21 from .matcher import JsonDiffMatcher
22 from .mock_discovery import get_rootDesc
24 if not os.getenv('ARVADOS_DEBUG'):
25 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
26 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
28 class CollectionMock(object):
29 def __init__(self, vwdmock, *args, **kwargs):
30 self.vwdmock = vwdmock
33 def open(self, *args, **kwargs):
35 return self.vwdmock.open(*args, **kwargs)
37 def copy(self, *args, **kwargs):
39 self.vwdmock.copy(*args, **kwargs)
41 def save_new(self, *args, **kwargs):
47 def portable_data_hash(self):
49 return arvados.config.EMPTY_BLOCK_LOCATOR
51 return "99999999999999999999999999999996+99"
54 class TestContainer(unittest.TestCase):
56 def helper(self, runner, enable_reuse=True):
57 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
59 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
60 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
61 loadingContext = arvados_cwl.context.ArvLoadingContext(
62 {"avsc_names": avsc_names,
64 "make_fs_access": make_fs_access,
66 "metadata": {"cwlVersion": "v1.0"}})
67 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
68 {"work_api": "containers",
70 "name": "test_run_"+str(enable_reuse),
71 "make_fs_access": make_fs_access,
73 "enable_reuse": enable_reuse,
76 return loadingContext, runtimeContext
78 # The test passes no builder.resources
79 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
80 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
81 def test_run(self, keepdocker):
82 for enable_reuse in (True, False):
83 arv_docker_clear_cache()
85 runner = mock.MagicMock()
86 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
87 runner.ignore_docker_for_reuse = False
88 runner.intermediate_output_ttl = 0
89 runner.secret_store = cwltool.secrets.SecretStore()
91 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
92 runner.api.collections().get().execute.return_value = {
93 "portable_data_hash": "99999999999999999999999999999993+99"}
99 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
101 "class": "CommandLineTool"
104 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
106 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
107 arvtool.formatgraph = None
109 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
110 j.run(runtimeContext)
111 runner.api.container_requests().create.assert_called_with(
112 body=JsonDiffMatcher({
114 'HOME': '/var/spool/cwl',
117 'name': 'test_run_'+str(enable_reuse),
118 'runtime_constraints': {
122 'use_existing': enable_reuse,
125 '/tmp': {'kind': 'tmp',
126 "capacity": 1073741824
128 '/var/spool/cwl': {'kind': 'tmp',
129 "capacity": 1073741824 }
131 'state': 'Committed',
132 'output_name': 'Output for step test_run_'+str(enable_reuse),
133 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
134 'output_path': '/var/spool/cwl',
136 'container_image': '99999999999999999999999999999993+99',
137 'command': ['ls', '/var/spool/cwl'],
138 'cwd': '/var/spool/cwl',
139 'scheduling_parameters': {},
144 # The test passes some fields in builder.resources
145 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
146 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
147 def test_resource_requirements(self, keepdocker):
148 arv_docker_clear_cache()
149 runner = mock.MagicMock()
150 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
151 runner.ignore_docker_for_reuse = False
152 runner.intermediate_output_ttl = 3600
153 runner.secret_store = cwltool.secrets.SecretStore()
155 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
156 runner.api.collections().get().execute.return_value = {
157 "portable_data_hash": "99999999999999999999999999999993+99"}
163 "class": "ResourceRequirement",
169 "class": "http://arvados.org/cwl#RuntimeConstraints",
172 "class": "http://arvados.org/cwl#APIRequirement",
174 "class": "http://arvados.org/cwl#PartitionRequirement",
177 "class": "http://arvados.org/cwl#IntermediateOutput",
180 "class": "http://arvados.org/cwl#ReuseRequirement",
185 "class": "CommandLineTool"
188 loadingContext, runtimeContext = self.helper(runner)
189 runtimeContext.name = "test_resource_requirements"
191 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
192 arvtool.formatgraph = None
193 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
194 j.run(runtimeContext)
196 call_args, call_kwargs = runner.api.container_requests().create.call_args
198 call_body_expected = {
200 'HOME': '/var/spool/cwl',
203 'name': 'test_resource_requirements',
204 'runtime_constraints': {
207 'keep_cache_ram': 536870912,
210 'use_existing': False,
213 '/tmp': {'kind': 'tmp',
214 "capacity": 4194304000 },
215 '/var/spool/cwl': {'kind': 'tmp',
216 "capacity": 5242880000 }
218 'state': 'Committed',
219 'output_name': 'Output for step test_resource_requirements',
220 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
221 'output_path': '/var/spool/cwl',
223 'container_image': '99999999999999999999999999999993+99',
225 'cwd': '/var/spool/cwl',
226 'scheduling_parameters': {
227 'partitions': ['blurb']
233 call_body = call_kwargs.get('body', None)
234 self.assertNotEqual(None, call_body)
235 for key in call_body:
236 self.assertEqual(call_body_expected.get(key), call_body.get(key))
239 # The test passes some fields in builder.resources
240 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
241 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
242 @mock.patch("arvados.collection.Collection")
243 def test_initial_work_dir(self, collection_mock, keepdocker):
244 arv_docker_clear_cache()
245 runner = mock.MagicMock()
246 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
247 runner.ignore_docker_for_reuse = False
248 runner.intermediate_output_ttl = 0
249 runner.secret_store = cwltool.secrets.SecretStore()
251 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
252 runner.api.collections().get().execute.return_value = {
253 "portable_data_hash": "99999999999999999999999999999993+99"}
255 sourcemock = mock.MagicMock()
256 def get_collection_mock(p):
258 return (sourcemock, p.split("/", 1)[1])
260 return (sourcemock, "")
261 runner.fs_access.get_collection.side_effect = get_collection_mock
263 vwdmock = mock.MagicMock()
264 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
270 "class": "InitialWorkDirRequirement",
274 "location": "keep:99999999999999999999999999999995+99/bar"
277 "class": "Directory",
279 "location": "keep:99999999999999999999999999999995+99"
283 "basename": "filename",
284 "location": "keep:99999999999999999999999999999995+99/baz/filename"
287 "class": "Directory",
288 "basename": "subdir",
289 "location": "keep:99999999999999999999999999999995+99/subdir"
294 "class": "CommandLineTool"
297 loadingContext, runtimeContext = self.helper(runner)
298 runtimeContext.name = "test_initial_work_dir"
300 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
301 arvtool.formatgraph = None
302 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
303 j.run(runtimeContext)
305 call_args, call_kwargs = runner.api.container_requests().create.call_args
307 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
308 vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
309 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
310 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
312 call_body_expected = {
314 'HOME': '/var/spool/cwl',
317 'name': 'test_initial_work_dir',
318 'runtime_constraints': {
322 'use_existing': True,
325 '/tmp': {'kind': 'tmp',
326 "capacity": 1073741824 },
327 '/var/spool/cwl': {'kind': 'tmp',
328 "capacity": 1073741824 },
329 '/var/spool/cwl/foo': {
330 'kind': 'collection',
332 'portable_data_hash': '99999999999999999999999999999996+99'
334 '/var/spool/cwl/foo2': {
335 'kind': 'collection',
337 'portable_data_hash': '99999999999999999999999999999996+99'
339 '/var/spool/cwl/filename': {
340 'kind': 'collection',
342 'portable_data_hash': '99999999999999999999999999999996+99'
344 '/var/spool/cwl/subdir': {
345 'kind': 'collection',
347 'portable_data_hash': '99999999999999999999999999999996+99'
350 'state': 'Committed',
351 'output_name': 'Output for step test_initial_work_dir',
352 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
353 'output_path': '/var/spool/cwl',
355 'container_image': '99999999999999999999999999999993+99',
357 'cwd': '/var/spool/cwl',
358 'scheduling_parameters': {
364 call_body = call_kwargs.get('body', None)
365 self.assertNotEqual(None, call_body)
366 for key in call_body:
367 self.assertEqual(call_body_expected.get(key), call_body.get(key))
370 # Test redirecting stdin/stdout/stderr
371 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
372 def test_redirects(self, keepdocker):
373 arv_docker_clear_cache()
375 runner = mock.MagicMock()
376 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
377 runner.ignore_docker_for_reuse = False
378 runner.intermediate_output_ttl = 0
379 runner.secret_store = cwltool.secrets.SecretStore()
381 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
382 runner.api.collections().get().execute.return_value = {
383 "portable_data_hash": "99999999999999999999999999999993+99"}
385 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
391 "stdout": "stdout.txt",
392 "stderr": "stderr.txt",
393 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
394 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
396 "class": "CommandLineTool"
399 loadingContext, runtimeContext = self.helper(runner)
400 runtimeContext.name = "test_run_redirect"
402 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
403 arvtool.formatgraph = None
404 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
405 j.run(runtimeContext)
406 runner.api.container_requests().create.assert_called_with(
407 body=JsonDiffMatcher({
409 'HOME': '/var/spool/cwl',
412 'name': 'test_run_redirect',
413 'runtime_constraints': {
417 'use_existing': True,
420 '/tmp': {'kind': 'tmp',
421 "capacity": 1073741824 },
422 '/var/spool/cwl': {'kind': 'tmp',
423 "capacity": 1073741824 },
426 "path": "/var/spool/cwl/stderr.txt"
429 "kind": "collection",
431 "portable_data_hash": "99999999999999999999999999999996+99"
435 "path": "/var/spool/cwl/stdout.txt"
438 'state': 'Committed',
439 "output_name": "Output for step test_run_redirect",
440 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
441 'output_path': '/var/spool/cwl',
443 'container_image': '99999999999999999999999999999993+99',
444 'command': ['ls', '/var/spool/cwl'],
445 'cwd': '/var/spool/cwl',
446 'scheduling_parameters': {},
451 @mock.patch("arvados.collection.Collection")
452 def test_done(self, col):
453 api = mock.MagicMock()
455 runner = mock.MagicMock()
457 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
458 runner.num_retries = 0
459 runner.ignore_docker_for_reuse = False
460 runner.intermediate_output_ttl = 0
461 runner.secret_store = cwltool.secrets.SecretStore()
463 runner.api.containers().get().execute.return_value = {"state":"Complete",
467 col().open.return_value = []
469 loadingContext, runtimeContext = self.helper(runner)
471 arvjob = arvados_cwl.ArvadosContainer(runner,
479 arvjob.output_callback = mock.MagicMock()
480 arvjob.collect_outputs = mock.MagicMock()
481 arvjob.successCodes = [0]
482 arvjob.outdir = "/var/spool/cwl"
483 arvjob.output_ttl = 3600
485 arvjob.collect_outputs.return_value = {"out": "stuff"}
489 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
490 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
491 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
492 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
493 "modified_at": "2017-05-26T12:01:22Z"
496 self.assertFalse(api.collections().create.called)
497 self.assertFalse(runner.runtime_status_error.called)
499 arvjob.collect_outputs.assert_called_with("keep:abc+123")
500 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
501 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
503 @mock.patch("arvados_cwl.util.get_current_container")
504 @mock.patch("arvados.collection.CollectionReader")
505 @mock.patch("arvados.collection.Collection")
506 def test_child_failure(self, col, reader, gcc_mock):
507 api = mock.MagicMock()
508 api._rootDesc = copy.deepcopy(get_rootDesc())
509 del api._rootDesc.get('resources')['jobs']['methods']['create']
511 # Set up runner with mocked runtime_status_update()
512 self.assertFalse(gcc_mock.called)
513 runtime_status_update = mock.MagicMock()
514 arvados_cwl.ArvCwlExecutor.runtime_status_update = runtime_status_update
515 runner = arvados_cwl.ArvCwlExecutor(api)
516 self.assertEqual(runner.work_api, 'containers')
518 # Make sure ArvCwlExecutor thinks it's running inside a container so it
519 # adds the logging handler that will call runtime_status_update() mock
520 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
521 self.assertTrue(gcc_mock.called)
522 root_logger = logging.getLogger('')
523 handlerClasses = [h.__class__ for h in root_logger.handlers]
524 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
526 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
527 runner.num_retries = 0
528 runner.ignore_docker_for_reuse = False
529 runner.intermediate_output_ttl = 0
530 runner.secret_store = cwltool.secrets.SecretStore()
531 runner.label = mock.MagicMock()
532 runner.label.return_value = '[container testjob]'
534 runner.api.containers().get().execute.return_value = {
541 col().open.return_value = []
543 loadingContext, runtimeContext = self.helper(runner)
545 arvjob = arvados_cwl.ArvadosContainer(runner,
553 arvjob.output_callback = mock.MagicMock()
554 arvjob.collect_outputs = mock.MagicMock()
555 arvjob.successCodes = [0]
556 arvjob.outdir = "/var/spool/cwl"
557 arvjob.output_ttl = 3600
558 arvjob.collect_outputs.return_value = {"out": "stuff"}
562 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
563 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
564 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
565 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
566 "modified_at": "2017-05-26T12:01:22Z"
569 runtime_status_update.assert_called_with(
571 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
572 ' ** log is empty **'
574 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
576 # The test passes no builder.resources
577 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
578 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
579 def test_mounts(self, keepdocker):
580 arv_docker_clear_cache()
582 runner = mock.MagicMock()
583 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
584 runner.ignore_docker_for_reuse = False
585 runner.intermediate_output_ttl = 0
586 runner.secret_store = cwltool.secrets.SecretStore()
588 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
589 runner.api.collections().get().execute.return_value = {
590 "portable_data_hash": "99999999999999999999999999999994+99",
591 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
593 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
602 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
604 "class": "CommandLineTool"
607 loadingContext, runtimeContext = self.helper(runner)
608 runtimeContext.name = "test_run_mounts"
610 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
611 arvtool.formatgraph = None
614 "class": "Directory",
615 "location": "keep:99999999999999999999999999999994+44",
619 "location": "keep:99999999999999999999999999999994+44/file1",
623 "location": "keep:99999999999999999999999999999994+44/file2",
628 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
629 j.run(runtimeContext)
630 runner.api.container_requests().create.assert_called_with(
631 body=JsonDiffMatcher({
633 'HOME': '/var/spool/cwl',
636 'name': 'test_run_mounts',
637 'runtime_constraints': {
641 'use_existing': True,
644 "/keep/99999999999999999999999999999994+44": {
645 "kind": "collection",
646 "portable_data_hash": "99999999999999999999999999999994+44"
648 '/tmp': {'kind': 'tmp',
649 "capacity": 1073741824 },
650 '/var/spool/cwl': {'kind': 'tmp',
651 "capacity": 1073741824 }
653 'state': 'Committed',
654 'output_name': 'Output for step test_run_mounts',
655 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
656 'output_path': '/var/spool/cwl',
658 'container_image': '99999999999999999999999999999994+99',
659 'command': ['ls', '/var/spool/cwl'],
660 'cwd': '/var/spool/cwl',
661 'scheduling_parameters': {},
666 # The test passes no builder.resources
667 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
668 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
669 def test_secrets(self, keepdocker):
670 arv_docker_clear_cache()
672 runner = mock.MagicMock()
673 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
674 runner.ignore_docker_for_reuse = False
675 runner.intermediate_output_ttl = 0
676 runner.secret_store = cwltool.secrets.SecretStore()
678 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
679 runner.api.collections().get().execute.return_value = {
680 "portable_data_hash": "99999999999999999999999999999993+99"}
682 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
684 tool = cmap({"arguments": ["md5sum", "example.conf"],
685 "class": "CommandLineTool",
688 "class": "http://commonwl.org/cwltool#Secrets",
694 "id": "#secret_job.cwl",
697 "id": "#secret_job.cwl/pw",
705 "class": "InitialWorkDirRequirement",
708 "entry": "username: user\npassword: $(inputs.pw)\n",
709 "entryname": "example.conf"
715 loadingContext, runtimeContext = self.helper(runner)
716 runtimeContext.name = "test_secrets"
718 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
719 arvtool.formatgraph = None
721 job_order = {"pw": "blorp"}
722 runner.secret_store.store(["pw"], job_order)
724 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
725 j.run(runtimeContext)
726 runner.api.container_requests().create.assert_called_with(
727 body=JsonDiffMatcher({
729 'HOME': '/var/spool/cwl',
732 'name': 'test_secrets',
733 'runtime_constraints': {
737 'use_existing': True,
740 '/tmp': {'kind': 'tmp',
741 "capacity": 1073741824
743 '/var/spool/cwl': {'kind': 'tmp',
744 "capacity": 1073741824 }
746 'state': 'Committed',
747 'output_name': 'Output for step test_secrets',
748 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
749 'output_path': '/var/spool/cwl',
751 'container_image': '99999999999999999999999999999993+99',
752 'command': ['md5sum', 'example.conf'],
753 'cwd': '/var/spool/cwl',
754 'scheduling_parameters': {},
757 "/var/spool/cwl/example.conf": {
758 "content": "username: user\npassword: blorp\n",
764 # The test passes no builder.resources
765 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
766 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
767 def test_timelimit(self, keepdocker):
768 arv_docker_clear_cache()
770 runner = mock.MagicMock()
771 runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
772 runner.ignore_docker_for_reuse = False
773 runner.intermediate_output_ttl = 0
774 runner.secret_store = cwltool.secrets.SecretStore()
776 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
777 runner.api.collections().get().execute.return_value = {
778 "portable_data_hash": "99999999999999999999999999999993+99"}
784 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
786 "class": "CommandLineTool",
789 "class": "http://commonwl.org/cwltool#TimeLimit",
795 loadingContext, runtimeContext = self.helper(runner)
796 runtimeContext.name = "test_timelimit"
798 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
799 arvtool.formatgraph = None
801 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
802 j.run(runtimeContext)
804 _, kwargs = runner.api.container_requests().create.call_args
805 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))