Merge branch '9511-cwl-type-checking' closes #9511
[arvados.git] / sdk / cwl / tests / test_job.py
1 import arvados_cwl
2 import logging
3 import mock
4 import unittest
5 import os
6 import cwltool.process
7
8 if not os.getenv('ARVADOS_DEBUG'):
9     logging.getLogger('arvados.cwl-runner').setLevel(logging.WARN)
10     logging.getLogger('arvados.arv-run').setLevel(logging.WARN)
11
12
13 class TestJob(unittest.TestCase):
14
15     # The test passes no builder.resources
16     # Hence the default resources will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
17     def test_run(self):
18         runner = mock.MagicMock()
19         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
20         runner.ignore_docker_for_reuse = False
21         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("draft-3")
22
23         tool = {
24             "inputs": [],
25             "outputs": [],
26             "baseCommand": "ls",
27             "arguments": [{"valueFrom": "$(runtime.outdir)"}]
28         }
29         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names, basedir="")
30         arvtool.formatgraph = None
31         for j in arvtool.job({}, mock.MagicMock(), basedir=""):
32             j.run()
33             runner.api.jobs().create.assert_called_with(
34                 body={
35                     'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
36                     'runtime_constraints': {},
37                     'script_parameters': {
38                         'tasks': [{
39                             'task.env': {'TMPDIR': '$(task.tmpdir)'},
40                             'command': ['ls', '$(task.outdir)']
41                         }],
42                     },
43                     'script_version': 'master',
44                     'minimum_script_version': '9e5b98e8f5f4727856b53447191f9c06e3da2ba6',
45                     'repository': 'arvados',
46                     'script': 'crunchrunner',
47                     'runtime_constraints': {
48                         'docker_image': 'arvados/jobs',
49                         'min_cores_per_node': 1,
50                         'min_ram_mb_per_node': 1024,
51                         'min_scratch_mb_per_node': 2048 # tmpdirSize + outdirSize
52                     }
53                 },
54                 find_or_create=True,
55                 filters=[['repository', '=', 'arvados'],
56                          ['script', '=', 'crunchrunner'],
57                          ['script_version', 'in git', '9e5b98e8f5f4727856b53447191f9c06e3da2ba6'],
58                          ['docker_image_locator', 'in docker', 'arvados/jobs']]
59             )
60
61     # The test passes some fields in builder.resources
62     # For the remaining fields, the defaults will apply: {'cores': 1, 'ram': 1024, 'outdirSize': 1024, 'tmpdirSize': 1024}
63     def test_resource_requirements(self):
64         runner = mock.MagicMock()
65         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
66         runner.ignore_docker_for_reuse = False
67         document_loader, avsc_names, schema_metadata, metaschema_loader = cwltool.process.get_schema("draft-3")
68
69         tool = {
70             "inputs": [],
71             "outputs": [],
72             "hints": [{
73                 "class": "ResourceRequirement",
74                 "coresMin": 3,
75                 "ramMin": 3000,
76                 "tmpdirMin": 4000
77             }],
78             "baseCommand": "ls"
79         }
80         arvtool = arvados_cwl.ArvadosCommandTool(runner, tool, work_api="jobs", avsc_names=avsc_names)
81         arvtool.formatgraph = None
82         for j in arvtool.job({}, mock.MagicMock(), basedir=""):
83             j.run()
84         runner.api.jobs().create.assert_called_with(
85             body={
86                 'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
87                 'runtime_constraints': {},
88                 'script_parameters': {
89                     'tasks': [{
90                         'task.env': {'TMPDIR': '$(task.tmpdir)'},
91                         'command': ['ls']
92                     }]
93             },
94             'script_version': 'master',
95                 'minimum_script_version': '9e5b98e8f5f4727856b53447191f9c06e3da2ba6',
96                 'repository': 'arvados',
97                 'script': 'crunchrunner',
98                 'runtime_constraints': {
99                     'docker_image': 'arvados/jobs',
100                     'min_cores_per_node': 3,
101                     'min_ram_mb_per_node': 3000,
102                     'min_scratch_mb_per_node': 5024 # tmpdirSize + outdirSize
103                 }
104             },
105             find_or_create=True,
106             filters=[['repository', '=', 'arvados'],
107                      ['script', '=', 'crunchrunner'],
108                      ['script_version', 'in git', '9e5b98e8f5f4727856b53447191f9c06e3da2ba6'],
109                      ['docker_image_locator', 'in docker', 'arvados/jobs']])
110
111     @mock.patch("arvados.collection.Collection")
112     def test_done(self, col):
113         api = mock.MagicMock()
114
115         runner = mock.MagicMock()
116         runner.api = api
117         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
118         runner.num_retries = 0
119         runner.ignore_docker_for_reuse = False
120
121         col().open.return_value = []
122         api.collections().list().execute.side_effect = ({"items": []},
123                                                         {"items": [{"manifest_text": "XYZ"}]})
124
125         arvjob = arvados_cwl.ArvadosJob(runner)
126         arvjob.name = "testjob"
127         arvjob.builder = mock.MagicMock()
128         arvjob.output_callback = mock.MagicMock()
129         arvjob.collect_outputs = mock.MagicMock()
130
131         arvjob.done({
132             "state": "Complete",
133             "output": "99999999999999999999999999999993+99",
134             "log": "99999999999999999999999999999994+99",
135             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
136         })
137
138         api.collections().list.assert_has_calls([
139             mock.call(),
140             mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
141                           ['portable_data_hash', '=', '99999999999999999999999999999993+99'],
142                           ['name', '=', 'Output 9999999 of testjob']]),
143             mock.call().execute(num_retries=0),
144             mock.call(limit=1, filters=[['portable_data_hash', '=', '99999999999999999999999999999993+99']],
145                  select=['manifest_text']),
146             mock.call().execute(num_retries=0)])
147
148         api.collections().create.assert_called_with(
149             ensure_unique_name=True,
150             body={'portable_data_hash': '99999999999999999999999999999993+99',
151                   'manifest_text': 'XYZ',
152                   'owner_uuid': 'zzzzz-8i9sb-zzzzzzzzzzzzzzz',
153                   'name': 'Output 9999999 of testjob'})
154
155     @mock.patch("arvados.collection.Collection")
156     def test_done_use_existing_collection(self, col):
157         api = mock.MagicMock()
158
159         runner = mock.MagicMock()
160         runner.api = api
161         runner.project_uuid = "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
162         runner.num_retries = 0
163
164         col().open.return_value = []
165         api.collections().list().execute.side_effect = ({"items": [{"uuid": "zzzzz-4zz18-zzzzzzzzzzzzzz2"}]},)
166
167         arvjob = arvados_cwl.ArvadosJob(runner)
168         arvjob.name = "testjob"
169         arvjob.builder = mock.MagicMock()
170         arvjob.output_callback = mock.MagicMock()
171         arvjob.collect_outputs = mock.MagicMock()
172
173         arvjob.done({
174             "state": "Complete",
175             "output": "99999999999999999999999999999993+99",
176             "log": "99999999999999999999999999999994+99",
177             "uuid": "zzzzz-8i9sb-zzzzzzzzzzzzzzz"
178         })
179
180         api.collections().list.assert_has_calls([
181             mock.call(),
182             mock.call(filters=[['owner_uuid', '=', 'zzzzz-8i9sb-zzzzzzzzzzzzzzz'],
183                                ['portable_data_hash', '=', '99999999999999999999999999999993+99'],
184                                ['name', '=', 'Output 9999999 of testjob']]),
185             mock.call().execute(num_retries=0)])
186
187         self.assertFalse(api.collections().create.called)