1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: Apache-2.0
5 from builtins import str
6 from builtins import object
9 import arvados_cwl.context
10 import arvados_cwl.util
11 from arvados_cwl.arvdocker import arv_docker_clear_cache
19 import cwltool.process
20 import cwltool.secrets
21 from schema_salad.ref_resolver import Loader
22 from schema_salad.sourceline import cmap
24 from .matcher import JsonDiffMatcher
25 from .mock_discovery import get_rootDesc
27 if not os.getenv('ARVADOS_DEBUG'):
28 logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
29 logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
31 class CollectionMock(object):
32 def __init__(self, vwdmock, *args, **kwargs):
33 self.vwdmock = vwdmock
36 def open(self, *args, **kwargs):
38 return self.vwdmock.open(*args, **kwargs)
40 def copy(self, *args, **kwargs):
42 self.vwdmock.copy(*args, **kwargs)
44 def save_new(self, *args, **kwargs):
50 def portable_data_hash(self):
52 return arvados.config.EMPTY_BLOCK_LOCATOR
54 return "99999999999999999999999999999996+99"
57 class TestContainer(unittest.TestCase):
59 def helper(self, runner, enable_reuse=True):
60 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
62 make_fs_access=functools.partial(arvados_cwl.CollectionFsAccess,
63 collection_cache=arvados_cwl.CollectionCache(runner.api, None, 0))
64 loadingContext = arvados_cwl.context.ArvLoadingContext(
65 {"avsc_names": avsc_names,
67 "make_fs_access": make_fs_access,
69 "metadata": {"cwlVersion": "v1.0"}})
70 runtimeContext = arvados_cwl.context.ArvRuntimeContext(
71 {"work_api": "containers",
73 "name": "test_run_"+str(enable_reuse),
74 "make_fs_access": make_fs_access,
76 "enable_reuse": enable_reuse,
78 "project_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
81 return loadingContext, runtimeContext
83 # Helper function to set up the ArvCwlExecutor to use the containers api
84 # and test that the RuntimeStatusLoggingHandler is set up correctly
85 def setup_and_test_container_executor_and_logging(self, gcc_mock) :
86 api = mock.MagicMock()
87 api._rootDesc = copy.deepcopy(get_rootDesc())
88 del api._rootDesc.get('resources')['jobs']['methods']['create']
90 # Make sure ArvCwlExecutor thinks it's running inside a container so it
91 # adds the logging handler that will call runtime_status_update() mock
92 self.assertFalse(gcc_mock.called)
93 runner = arvados_cwl.ArvCwlExecutor(api)
94 self.assertEqual(runner.work_api, 'containers')
95 root_logger = logging.getLogger('')
96 handlerClasses = [h.__class__ for h in root_logger.handlers]
97 self.assertTrue(arvados_cwl.RuntimeStatusLoggingHandler in handlerClasses)
100 # The test passes no builder.resources
101 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
102 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
103 def test_run(self, keepdocker):
104 for enable_reuse in (True, False):
105 arv_docker_clear_cache()
107 runner = mock.MagicMock()
108 runner.ignore_docker_for_reuse = False
109 runner.intermediate_output_ttl = 0
110 runner.secret_store = cwltool.secrets.SecretStore()
112 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
113 runner.api.collections().get().execute.return_value = {
114 "portable_data_hash": "99999999999999999999999999999993+99"}
120 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
122 "class": "CommandLineTool"
125 loadingContext, runtimeContext = self.helper(runner, enable_reuse)
127 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
128 arvtool.formatgraph = None
130 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
131 j.run(runtimeContext)
132 runner.api.container_requests().create.assert_called_with(
133 body=JsonDiffMatcher({
135 'HOME': '/var/spool/cwl',
138 'name': 'test_run_'+str(enable_reuse),
139 'runtime_constraints': {
143 'use_existing': enable_reuse,
146 '/tmp': {'kind': 'tmp',
147 "capacity": 1073741824
149 '/var/spool/cwl': {'kind': 'tmp',
150 "capacity": 1073741824 }
152 'state': 'Committed',
153 'output_name': 'Output for step test_run_'+str(enable_reuse),
154 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
155 'output_path': '/var/spool/cwl',
157 'container_image': '99999999999999999999999999999993+99',
158 'command': ['ls', '/var/spool/cwl'],
159 'cwd': '/var/spool/cwl',
160 'scheduling_parameters': {},
165 # The test passes some fields in builder.resources
166 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
167 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
168 def test_resource_requirements(self, keepdocker):
169 arv_docker_clear_cache()
170 runner = mock.MagicMock()
171 runner.ignore_docker_for_reuse = False
172 runner.intermediate_output_ttl = 3600
173 runner.secret_store = cwltool.secrets.SecretStore()
175 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
176 runner.api.collections().get().execute.return_value = {
177 "portable_data_hash": "99999999999999999999999999999993+99"}
183 "class": "ResourceRequirement",
189 "class": "http://arvados.org/cwl#RuntimeConstraints",
192 "class": "http://arvados.org/cwl#APIRequirement",
194 "class": "http://arvados.org/cwl#PartitionRequirement",
197 "class": "http://arvados.org/cwl#IntermediateOutput",
200 "class": "http://arvados.org/cwl#ReuseRequirement",
205 "class": "CommandLineTool"
208 loadingContext, runtimeContext = self.helper(runner)
209 runtimeContext.name = "test_resource_requirements"
211 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
212 arvtool.formatgraph = None
213 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
214 j.run(runtimeContext)
216 call_args, call_kwargs = runner.api.container_requests().create.call_args
218 call_body_expected = {
220 'HOME': '/var/spool/cwl',
223 'name': 'test_resource_requirements',
224 'runtime_constraints': {
227 'keep_cache_ram': 536870912,
230 'use_existing': False,
233 '/tmp': {'kind': 'tmp',
234 "capacity": 4194304000 },
235 '/var/spool/cwl': {'kind': 'tmp',
236 "capacity": 5242880000 }
238 'state': 'Committed',
239 'output_name': 'Output for step test_resource_requirements',
240 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
241 'output_path': '/var/spool/cwl',
243 'container_image': '99999999999999999999999999999993+99',
245 'cwd': '/var/spool/cwl',
246 'scheduling_parameters': {
247 'partitions': ['blurb']
253 call_body = call_kwargs.get('body', None)
254 self.assertNotEqual(None, call_body)
255 for key in call_body:
256 self.assertEqual(call_body_expected.get(key), call_body.get(key))
259 # The test passes some fields in builder.resources
260 # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
261 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
262 @mock.patch("arvados.collection.Collection")
263 def test_initial_work_dir(self, collection_mock, keepdocker):
264 arv_docker_clear_cache()
265 runner = mock.MagicMock()
266 runner.ignore_docker_for_reuse = False
267 runner.intermediate_output_ttl = 0
268 runner.secret_store = cwltool.secrets.SecretStore()
270 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
271 runner.api.collections().get().execute.return_value = {
272 "portable_data_hash": "99999999999999999999999999999993+99"}
274 sourcemock = mock.MagicMock()
275 def get_collection_mock(p):
277 return (sourcemock, p.split("/", 1)[1])
279 return (sourcemock, "")
280 runner.fs_access.get_collection.side_effect = get_collection_mock
282 vwdmock = mock.MagicMock()
283 collection_mock.side_effect = lambda *args, **kwargs: CollectionMock(vwdmock, *args, **kwargs)
289 "class": "InitialWorkDirRequirement",
293 "location": "keep:99999999999999999999999999999995+99/bar"
296 "class": "Directory",
298 "location": "keep:99999999999999999999999999999995+99"
302 "basename": "filename",
303 "location": "keep:99999999999999999999999999999995+99/baz/filename"
306 "class": "Directory",
307 "basename": "subdir",
308 "location": "keep:99999999999999999999999999999995+99/subdir"
313 "class": "CommandLineTool"
316 loadingContext, runtimeContext = self.helper(runner)
317 runtimeContext.name = "test_initial_work_dir"
319 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
320 arvtool.formatgraph = None
321 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
322 j.run(runtimeContext)
324 call_args, call_kwargs = runner.api.container_requests().create.call_args
326 vwdmock.copy.assert_has_calls([mock.call('bar', 'foo', source_collection=sourcemock)])
327 vwdmock.copy.assert_has_calls([mock.call('', 'foo2', source_collection=sourcemock)])
328 vwdmock.copy.assert_has_calls([mock.call('baz/filename', 'filename', source_collection=sourcemock)])
329 vwdmock.copy.assert_has_calls([mock.call('subdir', 'subdir', source_collection=sourcemock)])
331 call_body_expected = {
333 'HOME': '/var/spool/cwl',
336 'name': 'test_initial_work_dir',
337 'runtime_constraints': {
341 'use_existing': True,
344 '/tmp': {'kind': 'tmp',
345 "capacity": 1073741824 },
346 '/var/spool/cwl': {'kind': 'tmp',
347 "capacity": 1073741824 },
348 '/var/spool/cwl/foo': {
349 'kind': 'collection',
351 'portable_data_hash': '99999999999999999999999999999996+99'
353 '/var/spool/cwl/foo2': {
354 'kind': 'collection',
356 'portable_data_hash': '99999999999999999999999999999996+99'
358 '/var/spool/cwl/filename': {
359 'kind': 'collection',
361 'portable_data_hash': '99999999999999999999999999999996+99'
363 '/var/spool/cwl/subdir': {
364 'kind': 'collection',
366 'portable_data_hash': '99999999999999999999999999999996+99'
369 'state': 'Committed',
370 'output_name': 'Output for step test_initial_work_dir',
371 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
372 'output_path': '/var/spool/cwl',
374 'container_image': '99999999999999999999999999999993+99',
376 'cwd': '/var/spool/cwl',
377 'scheduling_parameters': {
383 call_body = call_kwargs.get('body', None)
384 self.assertNotEqual(None, call_body)
385 for key in call_body:
386 self.assertEqual(call_body_expected.get(key), call_body.get(key))
389 # Test redirecting stdin/stdout/stderr
390 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
391 def test_redirects(self, keepdocker):
392 arv_docker_clear_cache()
394 runner = mock.MagicMock()
395 runner.ignore_docker_for_reuse = False
396 runner.intermediate_output_ttl = 0
397 runner.secret_store = cwltool.secrets.SecretStore()
399 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
400 runner.api.collections().get().execute.return_value = {
401 "portable_data_hash": "99999999999999999999999999999993+99"}
403 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
409 "stdout": "stdout.txt",
410 "stderr": "stderr.txt",
411 "stdin": "/keep/99999999999999999999999999999996+99/file.txt",
412 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
414 "class": "CommandLineTool"
417 loadingContext, runtimeContext = self.helper(runner)
418 runtimeContext.name = "test_run_redirect"
420 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
421 arvtool.formatgraph = None
422 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
423 j.run(runtimeContext)
424 runner.api.container_requests().create.assert_called_with(
425 body=JsonDiffMatcher({
427 'HOME': '/var/spool/cwl',
430 'name': 'test_run_redirect',
431 'runtime_constraints': {
435 'use_existing': True,
438 '/tmp': {'kind': 'tmp',
439 "capacity": 1073741824 },
440 '/var/spool/cwl': {'kind': 'tmp',
441 "capacity": 1073741824 },
444 "path": "/var/spool/cwl/stderr.txt"
447 "kind": "collection",
449 "portable_data_hash": "99999999999999999999999999999996+99"
453 "path": "/var/spool/cwl/stdout.txt"
456 'state': 'Committed',
457 "output_name": "Output for step test_run_redirect",
458 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
459 'output_path': '/var/spool/cwl',
461 'container_image': '99999999999999999999999999999993+99',
462 'command': ['ls', '/var/spool/cwl'],
463 'cwd': '/var/spool/cwl',
464 'scheduling_parameters': {},
469 @mock.patch("arvados.collection.Collection")
470 def test_done(self, col):
471 api = mock.MagicMock()
473 runner = mock.MagicMock()
475 runner.num_retries = 0
476 runner.ignore_docker_for_reuse = False
477 runner.intermediate_output_ttl = 0
478 runner.secret_store = cwltool.secrets.SecretStore()
480 runner.api.containers().get().execute.return_value = {"state":"Complete",
484 col().open.return_value = []
486 loadingContext, runtimeContext = self.helper(runner)
488 arvjob = arvados_cwl.ArvadosContainer(runner,
496 arvjob.output_callback = mock.MagicMock()
497 arvjob.collect_outputs = mock.MagicMock()
498 arvjob.successCodes = [0]
499 arvjob.outdir = "/var/spool/cwl"
500 arvjob.output_ttl = 3600
502 arvjob.collect_outputs.return_value = {"out": "stuff"}
506 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
507 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
508 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
509 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
510 "modified_at": "2017-05-26T12:01:22Z"
513 self.assertFalse(api.collections().create.called)
514 self.assertFalse(runner.runtime_status_error.called)
516 arvjob.collect_outputs.assert_called_with("keep:abc+123")
517 arvjob.output_callback.assert_called_with({"out": "stuff"}, "success")
518 runner.add_intermediate_output.assert_called_with("zzzzz-4zz18-zzzzzzzzzzzzzz2")
520 # Test to make sure we dont call runtime_status_update if we already did
521 # some where higher up in the call stack
522 @mock.patch("arvados_cwl.util.get_current_container")
523 def test_recursive_runtime_status_update(self, gcc_mock):
524 self.setup_and_test_container_executor_and_logging(gcc_mock)
525 root_logger = logging.getLogger('')
527 # get_current_container is invoked when we call runtime_status_update
528 # so try and log again!
529 gcc_mock.side_effect = lambda *args: root_logger.error("Second Error")
531 root_logger.error("First Error")
533 self.fail("RuntimeStatusLoggingHandler should not be called recursively")
535 @mock.patch("arvados_cwl.ArvCwlExecutor.runtime_status_update")
536 @mock.patch("arvados_cwl.util.get_current_container")
537 @mock.patch("arvados.collection.CollectionReader")
538 @mock.patch("arvados.collection.Collection")
539 def test_child_failure(self, col, reader, gcc_mock, rts_mock):
540 runner = self.setup_and_test_container_executor_and_logging(gcc_mock)
542 gcc_mock.return_value = {"uuid" : "zzzzz-dz642-zzzzzzzzzzzzzzz"}
543 self.assertTrue(gcc_mock.called)
545 runner.num_retries = 0
546 runner.ignore_docker_for_reuse = False
547 runner.intermediate_output_ttl = 0
548 runner.secret_store = cwltool.secrets.SecretStore()
549 runner.label = mock.MagicMock()
550 runner.label.return_value = '[container testjob]'
552 runner.api.containers().get().execute.return_value = {
559 col().open.return_value = []
561 loadingContext, runtimeContext = self.helper(runner)
563 arvjob = arvados_cwl.ArvadosContainer(runner,
571 arvjob.output_callback = mock.MagicMock()
572 arvjob.collect_outputs = mock.MagicMock()
573 arvjob.successCodes = [0]
574 arvjob.outdir = "/var/spool/cwl"
575 arvjob.output_ttl = 3600
576 arvjob.collect_outputs.return_value = {"out": "stuff"}
580 "log_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz1",
581 "output_uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2",
582 "uuid": "zzzzz-xvhdp-zzzzzzzzzzzzzzz",
583 "container_uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz",
584 "modified_at": "2017-05-26T12:01:22Z"
587 rts_mock.assert_called_with(
589 'arvados.cwl-runner: [container testjob] (zzzzz-xvhdp-zzzzzzzzzzzzzzz) error log:',
590 ' ** log is empty **'
592 arvjob.output_callback.assert_called_with({"out": "stuff"}, "permanentFail")
594 # The test passes no builder.resources
595 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
596 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
597 def test_mounts(self, keepdocker):
598 arv_docker_clear_cache()
600 runner = mock.MagicMock()
601 runner.ignore_docker_for_reuse = False
602 runner.intermediate_output_ttl = 0
603 runner.secret_store = cwltool.secrets.SecretStore()
605 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
606 runner.api.collections().get().execute.return_value = {
607 "portable_data_hash": "99999999999999999999999999999994+99",
608 "manifest_text": ". 99999999999999999999999999999994+99 0:0:file1 0:0:file2"}
610 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
619 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
621 "class": "CommandLineTool"
624 loadingContext, runtimeContext = self.helper(runner)
625 runtimeContext.name = "test_run_mounts"
627 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
628 arvtool.formatgraph = None
631 "class": "Directory",
632 "location": "keep:99999999999999999999999999999994+44",
636 "location": "keep:99999999999999999999999999999994+44/file1",
640 "location": "keep:99999999999999999999999999999994+44/file2",
645 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
646 j.run(runtimeContext)
647 runner.api.container_requests().create.assert_called_with(
648 body=JsonDiffMatcher({
650 'HOME': '/var/spool/cwl',
653 'name': 'test_run_mounts',
654 'runtime_constraints': {
658 'use_existing': True,
661 "/keep/99999999999999999999999999999994+44": {
662 "kind": "collection",
663 "portable_data_hash": "99999999999999999999999999999994+44"
665 '/tmp': {'kind': 'tmp',
666 "capacity": 1073741824 },
667 '/var/spool/cwl': {'kind': 'tmp',
668 "capacity": 1073741824 }
670 'state': 'Committed',
671 'output_name': 'Output for step test_run_mounts',
672 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
673 'output_path': '/var/spool/cwl',
675 'container_image': '99999999999999999999999999999994+99',
676 'command': ['ls', '/var/spool/cwl'],
677 'cwd': '/var/spool/cwl',
678 'scheduling_parameters': {},
683 # The test passes no builder.resources
684 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
685 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
686 def test_secrets(self, keepdocker):
687 arv_docker_clear_cache()
689 runner = mock.MagicMock()
690 runner.ignore_docker_for_reuse = False
691 runner.intermediate_output_ttl = 0
692 runner.secret_store = cwltool.secrets.SecretStore()
694 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
695 runner.api.collections().get().execute.return_value = {
696 "portable_data_hash": "99999999999999999999999999999993+99"}
698 document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("v1.0")
700 tool = cmap({"arguments": ["md5sum", "example.conf"],
701 "class": "CommandLineTool",
704 "class": "http://commonwl.org/cwltool#Secrets",
710 "id": "#secret_job.cwl",
713 "id": "#secret_job.cwl/pw",
721 "class": "InitialWorkDirRequirement",
724 "entry": "username: user\npassword: $(inputs.pw)\n",
725 "entryname": "example.conf"
731 loadingContext, runtimeContext = self.helper(runner)
732 runtimeContext.name = "test_secrets"
734 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
735 arvtool.formatgraph = None
737 job_order = {"pw": "blorp"}
738 runner.secret_store.store(["pw"], job_order)
740 for j in arvtool.job(job_order, mock.MagicMock(), runtimeContext):
741 j.run(runtimeContext)
742 runner.api.container_requests().create.assert_called_with(
743 body=JsonDiffMatcher({
745 'HOME': '/var/spool/cwl',
748 'name': 'test_secrets',
749 'runtime_constraints': {
753 'use_existing': True,
756 '/tmp': {'kind': 'tmp',
757 "capacity": 1073741824
759 '/var/spool/cwl': {'kind': 'tmp',
760 "capacity": 1073741824 }
762 'state': 'Committed',
763 'output_name': 'Output for step test_secrets',
764 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
765 'output_path': '/var/spool/cwl',
767 'container_image': '99999999999999999999999999999993+99',
768 'command': ['md5sum', 'example.conf'],
769 'cwd': '/var/spool/cwl',
770 'scheduling_parameters': {},
773 "/var/spool/cwl/example.conf": {
774 "content": "username: user\npassword: blorp\n",
780 # The test passes no builder.resources
781 # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
782 @mock.patch("arvados.commands.keepdocker.list_images_in_arv")
783 def test_timelimit(self, keepdocker):
784 arv_docker_clear_cache()
786 runner = mock.MagicMock()
787 runner.ignore_docker_for_reuse = False
788 runner.intermediate_output_ttl = 0
789 runner.secret_store = cwltool.secrets.SecretStore()
791 keepdocker.return_value = [("zzzzz-4zz18-zzzzzzzzzzzzzz3", "")]
792 runner.api.collections().get().execute.return_value = {
793 "portable_data_hash": "99999999999999999999999999999993+99"}
799 "arguments": [{"valueFrom": "$(runtime.outdir)"}],
801 "class": "CommandLineTool",
804 "class": "http://commonwl.org/cwltool#TimeLimit",
810 loadingContext, runtimeContext = self.helper(runner)
811 runtimeContext.name = "test_timelimit"
813 arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, loadingContext)
814 arvtool.formatgraph = None
816 for j in arvtool.job({}, mock.MagicMock(), runtimeContext):
817 j.run(runtimeContext)
819 _, kwargs = runner.api.container_requests().create.call_args
820 self.assertEqual(42, kwargs['body']['scheduling_parameters'].get('max_run_time'))