Merge branch '19844-paging' refs #19844
[arvados.git] / sdk / cwl / tests / test_make_output.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 from future import standard_library
6 standard_library.install_aliases()
7
8 import functools
9 import json
10 import logging
11 import mock
12 import os
13 import io
14 import unittest
15
16 import arvados
17 import arvados_cwl
18 import arvados_cwl.executor
19 from .mock_discovery import get_rootDesc
20
21 class TestMakeOutput(unittest.TestCase):
22     def setUp(self):
23         self.api = mock.MagicMock()
24         self.api._rootDesc = get_rootDesc()
25
26     def tearDown(self):
27         root_logger = logging.getLogger('')
28
29         # Remove existing RuntimeStatusLoggingHandlers if they exist
30         handlers = [h for h in root_logger.handlers if not isinstance(h, arvados_cwl.executor.RuntimeStatusLoggingHandler)]
31         root_logger.handlers = handlers
32
33     @mock.patch("arvados.collection.Collection")
34     @mock.patch("arvados.collection.CollectionReader")
35     def test_make_output_collection(self, reader, col):
36         keep_client = mock.MagicMock()
37         runner = arvados_cwl.executor.ArvCwlExecutor(self.api, keep_client=keep_client)
38         runner.project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
39
40         final = mock.MagicMock()
41         col.return_value = final
42         readermock = mock.MagicMock()
43         reader.return_value = readermock
44
45         final_uuid = final.manifest_locator()
46         num_retries = runner.num_retries
47
48         cwlout = io.StringIO()
49         openmock = mock.MagicMock()
50         final.open.return_value = openmock
51         openmock.__enter__.return_value = cwlout
52
53         _, runner.final_output_collection = runner.make_output_collection("Test output", ["foo"], "tag0,tag1,tag2", {}, {
54             "foo": {
55                 "class": "File",
56                 "location": "keep:99999999999999999999999999999991+99/foo.txt",
57                 "size": 3,
58                 "basename": "foo.txt"
59             },
60             "bar": {
61                 "class": "File",
62                 "location": "keep:99999999999999999999999999999992+99/bar.txt",
63                 "basename": "baz.txt",
64                 "size": 4
65             }
66         })
67
68         final.copy.assert_has_calls([mock.call('bar.txt', 'baz.txt', overwrite=False, source_collection=readermock)])
69         final.copy.assert_has_calls([mock.call('foo.txt', 'foo.txt', overwrite=False, source_collection=readermock)])
70         final.save_new.assert_has_calls([mock.call(ensure_unique_name=True, name='Test output', owner_uuid='zzzzz-j7d0g-zzzzzzzzzzzzzzz', properties={}, storage_classes=['foo'])])
71         self.assertEqual("""{
72     "bar": {
73         "basename": "baz.txt",
74         "class": "File",
75         "location": "baz.txt",
76         "size": 4
77     },
78     "foo": {
79         "basename": "foo.txt",
80         "class": "File",
81         "location": "foo.txt",
82         "size": 3
83     }
84 }""", cwlout.getvalue())
85
86         self.assertIs(final, runner.final_output_collection)
87         self.assertIs(final_uuid, runner.final_output_collection.manifest_locator())
88         self.api.links().create.assert_has_calls([mock.call(body={"head_uuid": final_uuid, "link_class": "tag", "name": "tag0"}), mock.call().execute(num_retries=num_retries)])
89         self.api.links().create.assert_has_calls([mock.call(body={"head_uuid": final_uuid, "link_class": "tag", "name": "tag1"}), mock.call().execute(num_retries=num_retries)])
90         self.api.links().create.assert_has_calls([mock.call(body={"head_uuid": final_uuid, "link_class": "tag", "name": "tag2"}), mock.call().execute(num_retries=num_retries)])
91
92     @mock.patch("arvados.collection.Collection")
93     @mock.patch("arvados.collection.CollectionReader")
94     def test_make_output_for_multiple_file_targets(self, reader, col):
95         keep_client = mock.MagicMock()
96         runner = arvados_cwl.executor.ArvCwlExecutor(self.api, keep_client=keep_client)
97         runner.project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
98
99         final = mock.MagicMock()
100         col.return_value = final
101         readermock = mock.MagicMock()
102         reader.return_value = readermock
103
104         # This output describes a single file listed in 2 different directories
105         _, runner.final_output_collection = runner.make_output_collection("Test output", ["foo"], "", {}, { 'out': [
106         {
107             'basename': 'testdir1',
108             'listing': [
109                 {
110                     'basename': 'test.txt',
111                     'nameroot': 'test',
112                     'nameext': '.txt',
113                     'location': 'keep:99999999999999999999999999999991+99/test.txt',
114                     'class': 'File',
115                     'size': 16
116                 }
117             ],
118             'location': '_:99999999999999999999999999999992+99',
119             'class': 'Directory'
120         },
121         {
122             'basename': 'testdir2',
123             'listing': [
124                 {
125                     'basename': 'test.txt',
126                     'nameroot': 'test',
127                     'nameext': '.txt',
128                     'location': 'keep:99999999999999999999999999999991+99/test.txt',
129                     'class':
130                     'File',
131                     'size': 16
132                 }
133             ],
134             'location': '_:99999999999999999999999999999993+99',
135             'class': 'Directory'
136         }]})
137
138         # Check that copy is called on the collection for both locations
139         final.copy.assert_any_call("test.txt", "testdir1/test.txt", source_collection=mock.ANY, overwrite=mock.ANY)
140         final.copy.assert_any_call("test.txt", "testdir2/test.txt", source_collection=mock.ANY, overwrite=mock.ANY)
141
142     @mock.patch("arvados.collection.Collection")
143     @mock.patch("arvados.collection.CollectionReader")
144     def test_make_output_for_literal_name_conflicts(self, reader, col):
145         keep_client = mock.MagicMock()
146         runner = arvados_cwl.executor.ArvCwlExecutor(self.api, keep_client=keep_client)
147         runner.project_uuid = 'zzzzz-j7d0g-zzzzzzzzzzzzzzz'
148
149         final = mock.MagicMock()
150         col.return_value = final
151         readermock = mock.MagicMock()
152         reader.return_value = readermock
153
154         # This output describes two literals with the same basename
155         _, runner.final_output_collection = runner.make_output_collection("Test output", ["foo"], "",  {}, [
156         {
157             'lit':
158             {
159                 'basename': 'a_file',
160                 'nameext': '',
161                 'nameroot': 'a_file',
162                 'location': '_:f168fc0c-4291-40aa-a04e-366d57390560',
163                 'class': 'File',
164                 'contents': 'Hello file literal.'
165             }
166         },
167         {
168             'lit':
169             {
170                 'basename': 'a_file',
171                 'nameext': '',
172                 'nameroot': 'a_file',
173                 'location': '_:1728da8f-c64e-4a3e-b2e2-1ee356be7bc8',
174                 'class': 'File',
175                 'contents': 'Hello file literal.'
176             }
177         }])
178
179         # Check that the file name conflict is resolved and open is called for both
180         final.open.assert_any_call("a_file", "wb")
181         final.open.assert_any_call("a_file_2", "wb")