Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/cwltool/tests/test_examples.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4f3585e2f14b |
---|---|
1 import json | |
2 import logging | |
3 import os | |
4 import stat | |
5 import subprocess | |
6 import sys | |
7 from io import StringIO | |
8 from pathlib import Path | |
9 from typing import Any, Dict, List, Union, cast | |
10 from urllib.parse import urlparse | |
11 | |
12 import pydot # type: ignore | |
13 import pytest | |
14 from ruamel.yaml.comments import CommentedMap, CommentedSeq | |
15 from schema_salad.exceptions import ValidationException | |
16 | |
17 import cwltool.checker | |
18 import cwltool.expression as expr | |
19 import cwltool.factory | |
20 import cwltool.pathmapper | |
21 import cwltool.process | |
22 import cwltool.workflow | |
23 from cwltool.checker import can_assign_src_to_sink | |
24 from cwltool.context import RuntimeContext | |
25 from cwltool.errors import WorkflowException | |
26 from cwltool.main import main | |
27 from cwltool.process import CWL_IANA | |
28 from cwltool.sandboxjs import JavascriptException | |
29 from cwltool.utils import CWLObjectType, dedup, onWindows | |
30 | |
31 from .util import ( | |
32 get_data, | |
33 get_main_output, | |
34 get_windows_safe_factory, | |
35 needs_docker, | |
36 windows_needs_docker, | |
37 ) | |
38 | |
39 sys.argv = [""] | |
40 | |
41 expression_match = [ | |
42 ("(foo)", True), | |
43 ("(foo.bar)", True), | |
44 ("(foo['bar'])", True), | |
45 ('(foo["bar"])', True), | |
46 ("(foo.bar.baz)", True), | |
47 ("(foo['bar'].baz)", True), | |
48 ("(foo['bar']['baz'])", True), | |
49 ("(foo['b\\'ar']['baz'])", True), | |
50 ("(foo['b ar']['baz'])", True), | |
51 ("(foo_bar)", True), | |
52 ('(foo.["bar"])', False), | |
53 ('(.foo["bar"])', False), | |
54 ('(foo ["bar"])', False), | |
55 ('( foo["bar"])', False), | |
56 ("(foo[bar].baz)", False), | |
57 ("(foo['bar\"].baz)", False), | |
58 ("(foo['bar].baz)", False), | |
59 ("{foo}", False), | |
60 ("(foo.bar", False), | |
61 ("foo.bar)", False), | |
62 ("foo.b ar)", False), | |
63 ("foo.b'ar)", False), | |
64 ("(foo+bar", False), | |
65 ("(foo bar", False), | |
66 ] | |
67 | |
68 | |
69 @pytest.mark.parametrize("expression,expected", expression_match) | |
70 def test_expression_match(expression: str, expected: bool) -> None: | |
71 match = expr.param_re.match(expression) | |
72 assert (match is not None) == expected | |
73 | |
74 | |
75 interpolate_input = { | |
76 "foo": { | |
77 "bar": {"baz": "zab1"}, | |
78 "b ar": {"baz": 2}, | |
79 "b'ar": {"baz": True}, | |
80 'b"ar': {"baz": None}, | |
81 }, | |
82 "lst": ["A", "B"], | |
83 } # type: Dict[str, Any] | |
84 | |
85 interpolate_parameters = [ | |
86 ("$(foo)", interpolate_input["foo"]), | |
87 ("$(foo.bar)", interpolate_input["foo"]["bar"]), | |
88 ("$(foo['bar'])", interpolate_input["foo"]["bar"]), | |
89 ('$(foo["bar"])', interpolate_input["foo"]["bar"]), | |
90 ("$(foo.bar.baz)", interpolate_input["foo"]["bar"]["baz"]), | |
91 ("$(foo['bar'].baz)", interpolate_input["foo"]["bar"]["baz"]), | |
92 ("$(foo['bar'][\"baz\"])", interpolate_input["foo"]["bar"]["baz"]), | |
93 ("$(foo.bar['baz'])", interpolate_input["foo"]["bar"]["baz"]), | |
94 ("$(foo['b\\'ar'].baz)", True), | |
95 ('$(foo["b\'ar"].baz)', True), | |
96 ("$(foo['b\\\"ar'].baz)", None), | |
97 ("$(lst[0])", "A"), | |
98 ("$(lst[1])", "B"), | |
99 ("$(lst.length)", 2), | |
100 ("$(lst['length'])", 2), | |
101 ("-$(foo.bar)", """-{"baz": "zab1"}"""), | |
102 ("-$(foo['bar'])", """-{"baz": "zab1"}"""), | |
103 ('-$(foo["bar"])', """-{"baz": "zab1"}"""), | |
104 ("-$(foo.bar.baz)", "-zab1"), | |
105 ("-$(foo['bar'].baz)", "-zab1"), | |
106 ("-$(foo['bar'][\"baz\"])", "-zab1"), | |
107 ("-$(foo.bar['baz'])", "-zab1"), | |
108 ("-$(foo['b ar'].baz)", "-2"), | |
109 ("-$(foo['b\\'ar'].baz)", "-true"), | |
110 ('-$(foo["b\\\'ar"].baz)', "-true"), | |
111 ("-$(foo['b\\\"ar'].baz)", "-null"), | |
112 ("$(foo.bar) $(foo.bar)", """{"baz": "zab1"} {"baz": "zab1"}"""), | |
113 ("$(foo['bar']) $(foo['bar'])", """{"baz": "zab1"} {"baz": "zab1"}"""), | |
114 ('$(foo["bar"]) $(foo["bar"])', """{"baz": "zab1"} {"baz": "zab1"}"""), | |
115 ("$(foo.bar.baz) $(foo.bar.baz)", "zab1 zab1"), | |
116 ("$(foo['bar'].baz) $(foo['bar'].baz)", "zab1 zab1"), | |
117 ("$(foo['bar'][\"baz\"]) $(foo['bar'][\"baz\"])", "zab1 zab1"), | |
118 ("$(foo.bar['baz']) $(foo.bar['baz'])", "zab1 zab1"), | |
119 ("$(foo['b ar'].baz) $(foo['b ar'].baz)", "2 2"), | |
120 ("$(foo['b\\'ar'].baz) $(foo['b\\'ar'].baz)", "true true"), | |
121 ('$(foo["b\\\'ar"].baz) $(foo["b\\\'ar"].baz)', "true true"), | |
122 ("$(foo['b\\\"ar'].baz) $(foo['b\\\"ar'].baz)", "null null"), | |
123 ] | |
124 | |
125 | |
126 @pytest.mark.parametrize("pattern,expected", interpolate_parameters) | |
127 def test_expression_interpolate(pattern: str, expected: Any) -> None: | |
128 assert expr.interpolate(pattern, interpolate_input) == expected | |
129 | |
130 | |
131 parameter_to_expressions = [ | |
132 ( | |
133 "-$(foo)", | |
134 r"""-{"bar":{"baz":"zab1"},"b ar":{"baz":2},"b'ar":{"baz":true},"b\"ar":{"baz":null}}""", | |
135 ), | |
136 ("-$(foo.bar)", """-{"baz":"zab1"}"""), | |
137 ("-$(foo['bar'])", """-{"baz":"zab1"}"""), | |
138 ('-$(foo["bar"])', """-{"baz":"zab1"}"""), | |
139 ("-$(foo.bar.baz)", "-zab1"), | |
140 ("-$(foo['bar'].baz)", "-zab1"), | |
141 ("-$(foo['bar'][\"baz\"])", "-zab1"), | |
142 ("-$(foo.bar['baz'])", "-zab1"), | |
143 ("-$(foo['b ar'].baz)", "-2"), | |
144 ("-$(foo['b\\'ar'].baz)", "-true"), | |
145 ('-$(foo["b\\\'ar"].baz)', "-true"), | |
146 ("-$(foo['b\\\"ar'].baz)", "-null"), | |
147 ("$(foo.bar) $(foo.bar)", """{"baz":"zab1"} {"baz":"zab1"}"""), | |
148 ("$(foo['bar']) $(foo['bar'])", """{"baz":"zab1"} {"baz":"zab1"}"""), | |
149 ('$(foo["bar"]) $(foo["bar"])', """{"baz":"zab1"} {"baz":"zab1"}"""), | |
150 ("$(foo.bar.baz) $(foo.bar.baz)", "zab1 zab1"), | |
151 ("$(foo['bar'].baz) $(foo['bar'].baz)", "zab1 zab1"), | |
152 ("$(foo['bar'][\"baz\"]) $(foo['bar'][\"baz\"])", "zab1 zab1"), | |
153 ("$(foo.bar['baz']) $(foo.bar['baz'])", "zab1 zab1"), | |
154 ("$(foo['b ar'].baz) $(foo['b ar'].baz)", "2 2"), | |
155 ("$(foo['b\\'ar'].baz) $(foo['b\\'ar'].baz)", "true true"), | |
156 ('$(foo["b\\\'ar"].baz) $(foo["b\\\'ar"].baz)', "true true"), | |
157 ("$(foo['b\\\"ar'].baz) $(foo['b\\\"ar'].baz)", "null null"), | |
158 ] | |
159 | |
160 | |
161 @pytest.mark.parametrize("pattern,expected", parameter_to_expressions) | |
162 def test_parameter_to_expression(pattern: str, expected: Any) -> None: | |
163 """Test the interpolate convert_to_expression feature.""" | |
164 expression = expr.interpolate(pattern, {}, convert_to_expression=True) | |
165 assert isinstance(expression, str) | |
166 assert ( | |
167 expr.interpolate( | |
168 expression, | |
169 {}, | |
170 jslib=expr.jshead([], interpolate_input), | |
171 fullJS=True, | |
172 debug=True, | |
173 ) | |
174 == expected | |
175 ) | |
176 | |
177 | |
178 param_to_expr_interpolate_escapebehavior = ( | |
179 ("\\$(foo.bar.baz)", "$(foo.bar.baz)", 1), | |
180 ("\\\\$(foo.bar.baz)", "\\zab1", 1), | |
181 ("\\\\\\$(foo.bar.baz)", "\\$(foo.bar.baz)", 1), | |
182 ("\\\\\\\\$(foo.bar.baz)", "\\\\zab1", 1), | |
183 ("\\$foo", "$foo", 1), | |
184 ("\\foo", "foo", 1), | |
185 ("\\x", "x", 1), | |
186 ("\\\\x", "\\x", 1), | |
187 ("\\\\\\x", "\\x", 1), | |
188 ("\\\\\\\\x", "\\\\x", 1), | |
189 ("\\$(foo.bar.baz)", "$(foo.bar.baz)", 2), | |
190 ("\\\\$(foo.bar.baz)", "\\zab1", 2), | |
191 ("\\\\\\$(foo.bar.baz)", "\\$(foo.bar.baz)", 2), | |
192 ("\\\\\\\\$(foo.bar.baz)", "\\\\zab1", 2), | |
193 ("\\$foo", "\\$foo", 2), | |
194 ("\\foo", "\\foo", 2), | |
195 ("\\x", "\\x", 2), | |
196 ("\\\\x", "\\x", 2), | |
197 ("\\\\\\x", "\\\\x", 2), | |
198 ("\\\\\\\\x", "\\\\x", 2), | |
199 ) | |
200 | |
201 | |
202 @pytest.mark.parametrize( | |
203 "pattern,expected,behavior", param_to_expr_interpolate_escapebehavior | |
204 ) | |
205 def test_parameter_to_expression_interpolate_escapebehavior( | |
206 pattern: str, expected: str, behavior: int | |
207 ) -> None: | |
208 """Test escaping behavior in an convert_to_expression context.""" | |
209 expression = expr.interpolate( | |
210 pattern, {}, escaping_behavior=behavior, convert_to_expression=True | |
211 ) | |
212 assert isinstance(expression, str) | |
213 assert ( | |
214 expr.interpolate( | |
215 expression, | |
216 {}, | |
217 jslib=expr.jshead([], interpolate_input), | |
218 fullJS=True, | |
219 debug=True, | |
220 ) | |
221 == expected | |
222 ) | |
223 | |
224 | |
225 interpolate_bad_parameters = [ | |
226 ("$(fooz)"), | |
227 ("$(foo.barz)"), | |
228 ("$(foo['barz'])"), | |
229 ('$(foo["barz"])'), | |
230 ("$(foo.bar.bazz)"), | |
231 ("$(foo['bar'].bazz)"), | |
232 ("$(foo['bar'][\"bazz\"])"), | |
233 ("$(foo.bar['bazz'])"), | |
234 ("$(foo['b\\'ar'].bazz)"), | |
235 ('$(foo["b\'ar"].bazz)'), | |
236 ("$(foo['b\\\"ar'].bazz)"), | |
237 ("$(lst[O])"), # not "0" the number, but the letter O | |
238 ("$(lst[2])"), | |
239 ("$(lst.lengthz)"), | |
240 ("$(lst['lengthz'])"), | |
241 ("-$(foo.barz)"), | |
242 ("-$(foo['barz'])"), | |
243 ('-$(foo["barz"])'), | |
244 ("-$(foo.bar.bazz)"), | |
245 ("-$(foo['bar'].bazz)"), | |
246 ("-$(foo['bar'][\"bazz\"])"), | |
247 ("-$(foo.bar['bazz'])"), | |
248 ("-$(foo['b ar'].bazz)"), | |
249 ("-$(foo['b\\'ar'].bazz)"), | |
250 ('-$(foo["b\\\'ar"].bazz)'), | |
251 ("-$(foo['b\\\"ar'].bazz)"), | |
252 ] | |
253 | |
254 | |
255 @pytest.mark.parametrize("pattern", interpolate_bad_parameters) | |
256 def test_expression_interpolate_failures(pattern: str) -> None: | |
257 result = None | |
258 with pytest.raises(JavascriptException): | |
259 result = expr.interpolate(pattern, interpolate_input) | |
260 | |
261 | |
262 interpolate_escapebehavior = ( | |
263 ("\\$(foo.bar.baz)", "$(foo.bar.baz)", 1), | |
264 ("\\\\$(foo.bar.baz)", "\\zab1", 1), | |
265 ("\\\\\\$(foo.bar.baz)", "\\$(foo.bar.baz)", 1), | |
266 ("\\\\\\\\$(foo.bar.baz)", "\\\\zab1", 1), | |
267 ("\\$foo", "$foo", 1), | |
268 ("\\foo", "foo", 1), | |
269 ("\\x", "x", 1), | |
270 ("\\\\x", "\\x", 1), | |
271 ("\\\\\\x", "\\x", 1), | |
272 ("\\\\\\\\x", "\\\\x", 1), | |
273 ("\\$(foo.bar.baz)", "$(foo.bar.baz)", 2), | |
274 ("\\\\$(foo.bar.baz)", "\\zab1", 2), | |
275 ("\\\\\\$(foo.bar.baz)", "\\$(foo.bar.baz)", 2), | |
276 ("\\\\\\\\$(foo.bar.baz)", "\\\\zab1", 2), | |
277 ("\\$foo", "\\$foo", 2), | |
278 ("\\foo", "\\foo", 2), | |
279 ("\\x", "\\x", 2), | |
280 ("\\\\x", "\\x", 2), | |
281 ("\\\\\\x", "\\\\x", 2), | |
282 ("\\\\\\\\x", "\\\\x", 2), | |
283 ) | |
284 | |
285 | |
286 @pytest.mark.parametrize("pattern,expected,behavior", interpolate_escapebehavior) | |
287 def test_expression_interpolate_escapebehavior( | |
288 pattern: str, expected: str, behavior: int | |
289 ) -> None: | |
290 """Test escaping behavior in an interpolation context.""" | |
291 assert ( | |
292 expr.interpolate(pattern, interpolate_input, escaping_behavior=behavior) | |
293 == expected | |
294 ) | |
295 | |
296 | |
297 @windows_needs_docker | |
298 def test_factory() -> None: | |
299 factory = get_windows_safe_factory() | |
300 echo = factory.make(get_data("tests/echo.cwl")) | |
301 | |
302 assert echo(inp="foo") == {"out": "foo\n"} | |
303 | |
304 | |
305 def test_factory_bad_outputs() -> None: | |
306 factory = cwltool.factory.Factory() | |
307 | |
308 with pytest.raises(ValidationException): | |
309 factory.make(get_data("tests/echo_broken_outputs.cwl")) | |
310 | |
311 | |
312 def test_factory_default_args() -> None: | |
313 factory = cwltool.factory.Factory() | |
314 | |
315 assert factory.runtime_context.use_container is True | |
316 assert factory.runtime_context.on_error == "stop" | |
317 | |
318 | |
319 def test_factory_redefined_args() -> None: | |
320 runtime_context = RuntimeContext() | |
321 runtime_context.use_container = False | |
322 runtime_context.on_error = "continue" | |
323 factory = cwltool.factory.Factory(runtime_context=runtime_context) | |
324 | |
325 assert factory.runtime_context.use_container is False | |
326 assert factory.runtime_context.on_error == "continue" | |
327 | |
328 | |
329 def test_factory_partial_scatter() -> None: | |
330 runtime_context = RuntimeContext() | |
331 runtime_context.on_error = "continue" | |
332 factory = cwltool.factory.Factory(runtime_context=runtime_context) | |
333 | |
334 with pytest.raises(cwltool.factory.WorkflowStatus) as err_info: | |
335 factory.make(get_data("tests/wf/scatterfail.cwl"))() | |
336 | |
337 result = err_info.value.out | |
338 assert isinstance(result, dict) | |
339 assert ( | |
340 result["out"][0]["checksum"] == "sha1$e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e" | |
341 ) | |
342 assert result["out"][1] is None | |
343 assert ( | |
344 result["out"][2]["checksum"] == "sha1$a3db5c13ff90a36963278c6a39e4ee3c22e2a436" | |
345 ) | |
346 | |
347 | |
348 def test_factory_partial_output() -> None: | |
349 runtime_context = RuntimeContext() | |
350 runtime_context.on_error = "continue" | |
351 factory = cwltool.factory.Factory(runtime_context=runtime_context) | |
352 | |
353 with pytest.raises(cwltool.factory.WorkflowStatus) as err_info: | |
354 factory.make(get_data("tests/wf/wffail.cwl"))() | |
355 | |
356 result = err_info.value.out | |
357 assert isinstance(result, dict) | |
358 assert result["out1"]["checksum"] == "sha1$e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e" | |
359 assert result["out2"] is None | |
360 | |
361 | |
362 def test_scandeps() -> None: | |
363 obj: CWLObjectType = { | |
364 "id": "file:///example/foo.cwl", | |
365 "steps": [ | |
366 { | |
367 "id": "file:///example/foo.cwl#step1", | |
368 "inputs": [ | |
369 { | |
370 "id": "file:///example/foo.cwl#input1", | |
371 "default": { | |
372 "class": "File", | |
373 "location": "file:///example/data.txt", | |
374 }, | |
375 } | |
376 ], | |
377 "run": { | |
378 "id": "file:///example/bar.cwl", | |
379 "inputs": [ | |
380 { | |
381 "id": "file:///example/bar.cwl#input2", | |
382 "default": { | |
383 "class": "Directory", | |
384 "location": "file:///example/data2", | |
385 "listing": [ | |
386 { | |
387 "class": "File", | |
388 "location": "file:///example/data3.txt", | |
389 "secondaryFiles": [ | |
390 { | |
391 "class": "File", | |
392 "location": "file:///example/data5.txt", | |
393 } | |
394 ], | |
395 } | |
396 ], | |
397 }, | |
398 }, | |
399 { | |
400 "id": "file:///example/bar.cwl#input3", | |
401 "default": { | |
402 "class": "Directory", | |
403 "listing": [ | |
404 { | |
405 "class": "File", | |
406 "location": "file:///example/data4.txt", | |
407 } | |
408 ], | |
409 }, | |
410 }, | |
411 { | |
412 "id": "file:///example/bar.cwl#input4", | |
413 "default": {"class": "File", "contents": "file literal"}, | |
414 }, | |
415 ], | |
416 }, | |
417 } | |
418 ], | |
419 } | |
420 | |
421 def loadref(base: str, p: str) -> Union[CommentedMap, CommentedSeq, str, None]: | |
422 if isinstance(p, dict): | |
423 return p | |
424 raise Exception("test case can't load things") | |
425 | |
426 scanned_deps = cast( | |
427 List[Dict[str, Any]], | |
428 cwltool.process.scandeps( | |
429 cast(str, obj["id"]), | |
430 obj, | |
431 {"$import", "run"}, | |
432 {"$include", "$schemas", "location"}, | |
433 loadref, | |
434 ), | |
435 ) | |
436 | |
437 scanned_deps.sort(key=lambda k: cast(str, k["basename"])) | |
438 | |
439 expected_deps = [ | |
440 { | |
441 "basename": "bar.cwl", | |
442 "nameroot": "bar", | |
443 "class": "File", | |
444 "format": CWL_IANA, | |
445 "nameext": ".cwl", | |
446 "location": "file:///example/bar.cwl", | |
447 }, | |
448 { | |
449 "basename": "data.txt", | |
450 "nameroot": "data", | |
451 "class": "File", | |
452 "nameext": ".txt", | |
453 "location": "file:///example/data.txt", | |
454 }, | |
455 { | |
456 "basename": "data2", | |
457 "class": "Directory", | |
458 "location": "file:///example/data2", | |
459 "listing": [ | |
460 { | |
461 "basename": "data3.txt", | |
462 "nameroot": "data3", | |
463 "class": "File", | |
464 "nameext": ".txt", | |
465 "location": "file:///example/data3.txt", | |
466 "secondaryFiles": [ | |
467 { | |
468 "class": "File", | |
469 "basename": "data5.txt", | |
470 "location": "file:///example/data5.txt", | |
471 "nameext": ".txt", | |
472 "nameroot": "data5", | |
473 } | |
474 ], | |
475 } | |
476 ], | |
477 }, | |
478 { | |
479 "basename": "data4.txt", | |
480 "nameroot": "data4", | |
481 "class": "File", | |
482 "nameext": ".txt", | |
483 "location": "file:///example/data4.txt", | |
484 }, | |
485 ] | |
486 | |
487 assert scanned_deps == expected_deps | |
488 | |
489 scanned_deps2 = cast( | |
490 List[Dict[str, Any]], | |
491 cwltool.process.scandeps( | |
492 cast(str, obj["id"]), | |
493 obj, | |
494 set( | |
495 ("run"), | |
496 ), | |
497 set(), | |
498 loadref, | |
499 ), | |
500 ) | |
501 | |
502 scanned_deps2.sort(key=lambda k: cast(str, k["basename"])) | |
503 | |
504 expected_deps = [ | |
505 { | |
506 "basename": "bar.cwl", | |
507 "nameroot": "bar", | |
508 "format": CWL_IANA, | |
509 "class": "File", | |
510 "nameext": ".cwl", | |
511 "location": "file:///example/bar.cwl", | |
512 } | |
513 ] | |
514 | |
515 assert scanned_deps2 == expected_deps | |
516 | |
517 | |
518 def test_trick_scandeps() -> None: | |
519 stream = StringIO() | |
520 | |
521 main( | |
522 ["--print-deps", "--debug", get_data("tests/wf/trick_defaults.cwl")], | |
523 stdout=stream, | |
524 ) | |
525 assert json.loads(stream.getvalue())["secondaryFiles"][0]["location"][:2] != "_:" | |
526 | |
527 | |
528 def test_scandeps_defaults_with_secondaryfiles() -> None: | |
529 stream = StringIO() | |
530 | |
531 main( | |
532 [ | |
533 "--print-deps", | |
534 "--relative-deps=cwd", | |
535 "--debug", | |
536 get_data("tests/wf/trick_defaults2.cwl"), | |
537 ], | |
538 stdout=stream, | |
539 ) | |
540 assert json.loads(stream.getvalue())["secondaryFiles"][0]["secondaryFiles"][0][ | |
541 "location" | |
542 ].endswith(os.path.join("tests", "wf", "indir1")) | |
543 | |
544 | |
545 def test_input_deps() -> None: | |
546 stream = StringIO() | |
547 | |
548 main( | |
549 [ | |
550 "--print-input-deps", | |
551 get_data("tests/wf/count-lines1-wf.cwl"), | |
552 get_data("tests/wf/wc-job.json"), | |
553 ], | |
554 stdout=stream, | |
555 ) | |
556 | |
557 expected = { | |
558 "class": "File", | |
559 "location": "wc-job.json", | |
560 "format": CWL_IANA, | |
561 "secondaryFiles": [ | |
562 { | |
563 "class": "File", | |
564 "location": "whale.txt", | |
565 "basename": "whale.txt", | |
566 "nameroot": "whale", | |
567 "nameext": ".txt", | |
568 } | |
569 ], | |
570 } | |
571 assert json.loads(stream.getvalue()) == expected | |
572 | |
573 | |
574 def test_input_deps_cmdline_opts() -> None: | |
575 stream = StringIO() | |
576 | |
577 main( | |
578 [ | |
579 "--print-input-deps", | |
580 get_data("tests/wf/count-lines1-wf.cwl"), | |
581 "--file1", | |
582 get_data("tests/wf/whale.txt"), | |
583 ], | |
584 stdout=stream, | |
585 ) | |
586 expected = { | |
587 "class": "File", | |
588 "location": "", | |
589 "format": CWL_IANA, | |
590 "secondaryFiles": [ | |
591 { | |
592 "class": "File", | |
593 "location": "whale.txt", | |
594 "basename": "whale.txt", | |
595 "nameroot": "whale", | |
596 "nameext": ".txt", | |
597 } | |
598 ], | |
599 } | |
600 assert json.loads(stream.getvalue()) == expected | |
601 | |
602 | |
603 def test_input_deps_cmdline_opts_relative_deps_cwd() -> None: | |
604 stream = StringIO() | |
605 | |
606 data_path = get_data("tests/wf/whale.txt") | |
607 main( | |
608 [ | |
609 "--print-input-deps", | |
610 "--relative-deps", | |
611 "cwd", | |
612 get_data("tests/wf/count-lines1-wf.cwl"), | |
613 "--file1", | |
614 data_path, | |
615 ], | |
616 stdout=stream, | |
617 ) | |
618 | |
619 goal = { | |
620 "class": "File", | |
621 "location": "", | |
622 "format": CWL_IANA, | |
623 "secondaryFiles": [ | |
624 { | |
625 "class": "File", | |
626 "location": str(Path(os.path.relpath(data_path, os.path.curdir))), | |
627 "basename": "whale.txt", | |
628 "nameroot": "whale", | |
629 "nameext": ".txt", | |
630 } | |
631 ], | |
632 } | |
633 assert json.loads(stream.getvalue()) == goal | |
634 | |
635 | |
636 def test_dedupe() -> None: | |
637 not_deduped = [ | |
638 {"class": "File", "location": "file:///example/a"}, | |
639 {"class": "File", "location": "file:///example/a"}, | |
640 {"class": "File", "location": "file:///example/d"}, | |
641 { | |
642 "class": "Directory", | |
643 "location": "file:///example/c", | |
644 "listing": [{"class": "File", "location": "file:///example/d"}], | |
645 }, | |
646 ] # type: List[CWLObjectType] | |
647 | |
648 expected = [ | |
649 {"class": "File", "location": "file:///example/a"}, | |
650 { | |
651 "class": "Directory", | |
652 "location": "file:///example/c", | |
653 "listing": [{"class": "File", "location": "file:///example/d"}], | |
654 }, | |
655 ] | |
656 | |
657 assert dedup(not_deduped) == expected | |
658 | |
659 | |
660 record = { | |
661 "fields": [ | |
662 { | |
663 "type": {"items": "string", "type": "array"}, | |
664 "name": "file:///home/chapmanb/drive/work/cwl/test_bcbio_cwl/run_info-cwl-workflow/wf-variantcall.cwl#vc_rec/vc_rec/description", | |
665 }, | |
666 { | |
667 "type": {"items": "File", "type": "array"}, | |
668 "name": "file:///home/chapmanb/drive/work/cwl/test_bcbio_cwl/run_info-cwl-workflow/wf-variantcall.cwl#vc_rec/vc_rec/vrn_file", | |
669 }, | |
670 ], | |
671 "type": "record", | |
672 "name": "file:///home/chapmanb/drive/work/cwl/test_bcbio_cwl/run_info-cwl-workflow/wf-variantcall.cwl#vc_rec/vc_rec", | |
673 } | |
674 | |
675 source_to_sink = [ | |
676 ( | |
677 "0", | |
678 {"items": ["string", "null"], "type": "array"}, | |
679 {"items": ["string", "null"], "type": "array"}, | |
680 True, | |
681 ), | |
682 ( | |
683 "1", | |
684 {"items": ["string"], "type": "array"}, | |
685 {"items": ["string", "null"], "type": "array"}, | |
686 True, | |
687 ), | |
688 ( | |
689 "2", | |
690 {"items": ["string", "null"], "type": "array"}, | |
691 {"items": ["string"], "type": "array"}, | |
692 True, | |
693 ), | |
694 ( | |
695 "3", | |
696 {"items": ["string"], "type": "array"}, | |
697 {"items": ["int"], "type": "array"}, | |
698 False, | |
699 ), | |
700 ("record 0", record, record, True), | |
701 ("record 1", record, {"items": "string", "type": "array"}, False), | |
702 ] | |
703 | |
704 | |
705 @pytest.mark.parametrize("name, source, sink, expected", source_to_sink) | |
706 def test_compare_types( | |
707 name: str, source: Dict[str, Any], sink: Dict[str, Any], expected: bool | |
708 ) -> None: | |
709 assert can_assign_src_to_sink(source, sink) == expected, name | |
710 | |
711 | |
712 source_to_sink_strict = [ | |
713 ("0", ["string", "null"], ["string", "null"], True), | |
714 ("1", ["string"], ["string", "null"], True), | |
715 ("2", ["string", "int"], ["string", "null"], False), | |
716 ( | |
717 "3", | |
718 {"items": ["string"], "type": "array"}, | |
719 {"items": ["string", "null"], "type": "array"}, | |
720 True, | |
721 ), | |
722 ( | |
723 "4", | |
724 {"items": ["string", "int"], "type": "array"}, | |
725 {"items": ["string", "null"], "type": "array"}, | |
726 False, | |
727 ), | |
728 ] | |
729 | |
730 | |
731 @pytest.mark.parametrize("name, source, sink, expected", source_to_sink_strict) | |
732 def test_compare_types_strict( | |
733 name: str, source: Dict[str, Any], sink: Dict[str, Any], expected: bool | |
734 ) -> None: | |
735 assert can_assign_src_to_sink(source, sink, strict=True) == expected, name | |
736 | |
737 | |
738 typechecks = [ | |
739 (["string", "int"], ["string", "int", "null"], None, None, "pass"), | |
740 (["string", "int"], ["string", "null"], None, None, "warning"), | |
741 (["File", "int"], ["string", "null"], None, None, "exception"), | |
742 ( | |
743 {"items": ["string", "int"], "type": "array"}, | |
744 {"items": ["string", "int", "null"], "type": "array"}, | |
745 None, | |
746 None, | |
747 "pass", | |
748 ), | |
749 ( | |
750 {"items": ["string", "int"], "type": "array"}, | |
751 {"items": ["string", "null"], "type": "array"}, | |
752 None, | |
753 None, | |
754 "warning", | |
755 ), | |
756 ( | |
757 {"items": ["File", "int"], "type": "array"}, | |
758 {"items": ["string", "null"], "type": "array"}, | |
759 None, | |
760 None, | |
761 "exception", | |
762 ), | |
763 # check linkMerge when sinktype is not an array | |
764 (["string", "int"], ["string", "int", "null"], "merge_nested", None, "exception"), | |
765 # check linkMerge: merge_nested | |
766 ( | |
767 ["string", "int"], | |
768 {"items": ["string", "int", "null"], "type": "array"}, | |
769 "merge_nested", | |
770 None, | |
771 "pass", | |
772 ), | |
773 ( | |
774 ["string", "int"], | |
775 {"items": ["string", "null"], "type": "array"}, | |
776 "merge_nested", | |
777 None, | |
778 "warning", | |
779 ), | |
780 ( | |
781 ["File", "int"], | |
782 {"items": ["string", "null"], "type": "array"}, | |
783 "merge_nested", | |
784 None, | |
785 "exception", | |
786 ), | |
787 # check linkMerge: merge_nested and sinktype is "Any" | |
788 (["string", "int"], "Any", "merge_nested", None, "pass"), | |
789 # check linkMerge: merge_flattened | |
790 ( | |
791 ["string", "int"], | |
792 {"items": ["string", "int", "null"], "type": "array"}, | |
793 "merge_flattened", | |
794 None, | |
795 "pass", | |
796 ), | |
797 ( | |
798 ["string", "int"], | |
799 {"items": ["string", "null"], "type": "array"}, | |
800 "merge_flattened", | |
801 None, | |
802 "warning", | |
803 ), | |
804 ( | |
805 ["File", "int"], | |
806 {"items": ["string", "null"], "type": "array"}, | |
807 "merge_flattened", | |
808 None, | |
809 "exception", | |
810 ), | |
811 ( | |
812 {"items": ["string", "int"], "type": "array"}, | |
813 {"items": ["string", "int", "null"], "type": "array"}, | |
814 "merge_flattened", | |
815 None, | |
816 "pass", | |
817 ), | |
818 ( | |
819 {"items": ["string", "int"], "type": "array"}, | |
820 {"items": ["string", "null"], "type": "array"}, | |
821 "merge_flattened", | |
822 None, | |
823 "warning", | |
824 ), | |
825 ( | |
826 {"items": ["File", "int"], "type": "array"}, | |
827 {"items": ["string", "null"], "type": "array"}, | |
828 "merge_flattened", | |
829 None, | |
830 "exception", | |
831 ), | |
832 # check linkMerge: merge_flattened and sinktype is "Any" | |
833 (["string", "int"], "Any", "merge_flattened", None, "pass"), | |
834 ( | |
835 {"items": ["string", "int"], "type": "array"}, | |
836 "Any", | |
837 "merge_flattened", | |
838 None, | |
839 "pass", | |
840 ), | |
841 # check linkMerge: merge_flattened when srctype is a list | |
842 ( | |
843 [{"items": "string", "type": "array"}], | |
844 {"items": "string", "type": "array"}, | |
845 "merge_flattened", | |
846 None, | |
847 "pass", | |
848 ), | |
849 # check valueFrom | |
850 ( | |
851 {"items": ["File", "int"], "type": "array"}, | |
852 {"items": ["string", "null"], "type": "array"}, | |
853 "merge_flattened", | |
854 "special value", | |
855 "pass", | |
856 ), | |
857 ] | |
858 | |
859 | |
860 @pytest.mark.parametrize( | |
861 "src_type,sink_type,link_merge,value_from,expected_type", typechecks | |
862 ) | |
863 def test_typechecking( | |
864 src_type: Any, sink_type: Any, link_merge: str, value_from: Any, expected_type: str | |
865 ) -> None: | |
866 assert ( | |
867 cwltool.checker.check_types( | |
868 src_type, sink_type, linkMerge=link_merge, valueFrom=value_from | |
869 ) | |
870 == expected_type | |
871 ) | |
872 | |
873 | |
874 def test_lifting() -> None: | |
875 # check that lifting the types of the process outputs to the workflow step | |
876 # fails if the step 'out' doesn't match. | |
877 factory = cwltool.factory.Factory() | |
878 with pytest.raises(ValidationException): | |
879 echo = factory.make(get_data("tests/test_bad_outputs_wf.cwl")) | |
880 assert echo(inp="foo") == {"out": "foo\n"} | |
881 | |
882 | |
883 def test_malformed_outputs() -> None: | |
884 # check that tool validation fails if one of the outputs is not a valid CWL type | |
885 factory = cwltool.factory.Factory() | |
886 with pytest.raises(ValidationException): | |
887 factory.make(get_data("tests/wf/malformed_outputs.cwl"))() | |
888 | |
889 | |
890 def test_separate_without_prefix() -> None: | |
891 # check that setting 'separate = false' on an inputBinding without prefix fails the workflow | |
892 factory = cwltool.factory.Factory() | |
893 with pytest.raises(WorkflowException): | |
894 factory.make(get_data("tests/wf/separate_without_prefix.cwl"))() | |
895 | |
896 | |
897 def test_static_checker() -> None: | |
898 # check that the static checker raises exception when a source type | |
899 # mismatches its sink type. | |
900 factory = cwltool.factory.Factory() | |
901 | |
902 with pytest.raises(ValidationException): | |
903 factory.make(get_data("tests/checker_wf/broken-wf.cwl")) | |
904 | |
905 with pytest.raises(ValidationException): | |
906 factory.make(get_data("tests/checker_wf/broken-wf2.cwl")) | |
907 | |
908 with pytest.raises(ValidationException): | |
909 factory.make(get_data("tests/checker_wf/broken-wf3.cwl")) | |
910 | |
911 | |
912 def test_var_spool_cwl_checker1() -> None: | |
913 """Confirm that references to /var/spool/cwl are caught.""" | |
914 stream = StringIO() | |
915 streamhandler = logging.StreamHandler(stream) | |
916 _logger = logging.getLogger("cwltool") | |
917 _logger.addHandler(streamhandler) | |
918 | |
919 factory = cwltool.factory.Factory() | |
920 try: | |
921 factory.make(get_data("tests/non_portable.cwl")) | |
922 assert ( | |
923 "non_portable.cwl:18:4: Non-portable reference to /var/spool/cwl detected" | |
924 in stream.getvalue() | |
925 ) | |
926 finally: | |
927 _logger.removeHandler(streamhandler) | |
928 | |
929 | |
930 def test_var_spool_cwl_checker2() -> None: | |
931 """Confirm that references to /var/spool/cwl are caught.""" | |
932 stream = StringIO() | |
933 streamhandler = logging.StreamHandler(stream) | |
934 _logger = logging.getLogger("cwltool") | |
935 _logger.addHandler(streamhandler) | |
936 | |
937 factory = cwltool.factory.Factory() | |
938 try: | |
939 factory.make(get_data("tests/non_portable2.cwl")) | |
940 assert ( | |
941 "non_portable2.cwl:19:4: Non-portable reference to /var/spool/cwl detected" | |
942 in stream.getvalue() | |
943 ) | |
944 finally: | |
945 _logger.removeHandler(streamhandler) | |
946 | |
947 | |
948 def test_var_spool_cwl_checker3() -> None: | |
949 """Confirm that references to /var/spool/cwl are caught.""" | |
950 stream = StringIO() | |
951 streamhandler = logging.StreamHandler(stream) | |
952 _logger = logging.getLogger("cwltool") | |
953 _logger.addHandler(streamhandler) | |
954 | |
955 factory = cwltool.factory.Factory() | |
956 try: | |
957 factory.make(get_data("tests/portable.cwl")) | |
958 assert ( | |
959 "Non-portable reference to /var/spool/cwl detected" not in stream.getvalue() | |
960 ) | |
961 finally: | |
962 _logger.removeHandler(streamhandler) | |
963 | |
964 | |
965 def test_print_dot() -> None: | |
966 # print Workflow | |
967 cwl_path = get_data("tests/wf/revsort.cwl") | |
968 cwl_posix_path = Path(cwl_path).as_posix() | |
969 expected_dot = pydot.graph_from_dot_data( | |
970 """ | |
971 digraph {{ | |
972 graph [bgcolor="#eeeeee", | |
973 clusterrank=local, | |
974 labeljust=right, | |
975 labelloc=bottom | |
976 ]; | |
977 subgraph cluster_inputs {{ | |
978 graph [label="Workflow Inputs", | |
979 rank=same, | |
980 style=dashed | |
981 ]; | |
982 "file://{cwl_posix_path}#workflow_input" [fillcolor="#94DDF4", | |
983 label=workflow_input, | |
984 style=filled]; | |
985 "file://{cwl_posix_path}#reverse_sort" [fillcolor="#94DDF4", | |
986 label=reverse_sort, | |
987 style=filled]; | |
988 }} | |
989 subgraph cluster_outputs {{ | |
990 graph [label="Workflow Outputs", | |
991 labelloc=b, | |
992 rank=same, | |
993 style=dashed | |
994 ]; | |
995 "file://{cwl_posix_path}#sorted_output" [fillcolor="#94DDF4", | |
996 label=sorted_output, | |
997 style=filled]; | |
998 }} | |
999 "file://{cwl_posix_path}#rev" [fillcolor=lightgoldenrodyellow, | |
1000 label=rev, | |
1001 style=filled]; | |
1002 "file://{cwl_posix_path}#sorted" [fillcolor=lightgoldenrodyellow, | |
1003 label=sorted, | |
1004 style=filled]; | |
1005 "file://{cwl_posix_path}#rev" -> "file://{cwl_posix_path}#sorted"; | |
1006 "file://{cwl_posix_path}#sorted" -> "file://{cwl_posix_path}#sorted_output"; | |
1007 "file://{cwl_posix_path}#workflow_input" -> "file://{cwl_posix_path}#rev"; | |
1008 "file://{cwl_posix_path}#reverse_sort" -> "file://{cwl_posix_path}#sorted"; | |
1009 }} | |
1010 """.format( | |
1011 cwl_posix_path=cwl_posix_path | |
1012 ) | |
1013 )[0] | |
1014 stdout = StringIO() | |
1015 assert main(["--print-dot", cwl_path], stdout=stdout) == 0 | |
1016 computed_dot = pydot.graph_from_dot_data(stdout.getvalue())[0] | |
1017 computed_edges = sorted( | |
1018 [ | |
1019 (urlparse(source).fragment, urlparse(target).fragment) | |
1020 for source, target in computed_dot.obj_dict["edges"] | |
1021 ] | |
1022 ) | |
1023 expected_edges = sorted( | |
1024 [ | |
1025 (urlparse(source).fragment, urlparse(target).fragment) | |
1026 for source, target in expected_dot.obj_dict["edges"] | |
1027 ] | |
1028 ) | |
1029 assert computed_edges == expected_edges | |
1030 | |
1031 # print CommandLineTool | |
1032 cwl_path = get_data("tests/wf/echo.cwl") | |
1033 stdout = StringIO() | |
1034 assert main(["--debug", "--print-dot", cwl_path], stdout=stdout) == 1 | |
1035 | |
1036 | |
1037 test_factors = [(""), ("--parallel"), ("--debug"), ("--parallel --debug")] | |
1038 | |
1039 | |
1040 @pytest.mark.parametrize("factor", test_factors) | |
1041 def test_js_console_cmd_line_tool(factor: str) -> None: | |
1042 for test_file in ("js_output.cwl", "js_output_workflow.cwl"): | |
1043 commands = factor.split() | |
1044 commands.extend( | |
1045 ["--js-console", "--no-container", get_data("tests/wf/" + test_file)] | |
1046 ) | |
1047 error_code, _, stderr = get_main_output(commands) | |
1048 | |
1049 assert "[log] Log message" in stderr | |
1050 assert "[err] Error message" in stderr | |
1051 | |
1052 assert error_code == 0, stderr | |
1053 | |
1054 | |
1055 @pytest.mark.parametrize("factor", test_factors) | |
1056 def test_no_js_console(factor: str) -> None: | |
1057 for test_file in ("js_output.cwl", "js_output_workflow.cwl"): | |
1058 commands = factor.split() | |
1059 commands.extend(["--no-container", get_data("tests/wf/" + test_file)]) | |
1060 _, _, stderr = get_main_output(commands) | |
1061 | |
1062 assert "[log] Log message" not in stderr | |
1063 assert "[err] Error message" not in stderr | |
1064 | |
1065 | |
1066 @needs_docker | |
1067 @pytest.mark.parametrize("factor", test_factors) | |
1068 def test_cid_file_dir(tmp_path: Path, factor: str) -> None: | |
1069 """Test --cidfile-dir option works.""" | |
1070 test_file = "cache_test_workflow.cwl" | |
1071 cwd = Path.cwd() | |
1072 os.chdir(tmp_path) | |
1073 commands = factor.split() | |
1074 commands.extend(["--cidfile-dir", str(tmp_path), get_data("tests/wf/" + test_file)]) | |
1075 error_code, stdout, stderr = get_main_output(commands) | |
1076 assert "completed success" in stderr | |
1077 assert error_code == 0 | |
1078 cidfiles_count = sum(1 for _ in tmp_path.glob("**/*")) | |
1079 assert cidfiles_count == 2 | |
1080 os.chdir(cwd) | |
1081 | |
1082 | |
1083 @needs_docker | |
1084 @pytest.mark.parametrize("factor", test_factors) | |
1085 def test_cid_file_dir_arg_is_file_instead_of_dir(tmp_path: Path, factor: str) -> None: | |
1086 """Test --cidfile-dir with a file produces the correct error.""" | |
1087 test_file = "cache_test_workflow.cwl" | |
1088 bad_cidfile_dir = tmp_path / "cidfile-dir-actually-a-file" | |
1089 bad_cidfile_dir.touch() | |
1090 commands = factor.split() | |
1091 commands.extend( | |
1092 ["--cidfile-dir", str(bad_cidfile_dir), get_data("tests/wf/" + test_file)] | |
1093 ) | |
1094 error_code, _, stderr = get_main_output(commands) | |
1095 assert "is not a directory, please check it first" in stderr, stderr | |
1096 assert error_code == 2 or error_code == 1, stderr | |
1097 | |
1098 | |
1099 @needs_docker | |
1100 @pytest.mark.parametrize("factor", test_factors) | |
1101 def test_cid_file_non_existing_dir(tmp_path: Path, factor: str) -> None: | |
1102 """Test that --cachedir with a bad path should produce a specific error.""" | |
1103 test_file = "cache_test_workflow.cwl" | |
1104 bad_cidfile_dir = tmp_path / "cidfile-dir-badpath" | |
1105 commands = factor.split() | |
1106 commands.extend( | |
1107 [ | |
1108 "--record-container-id", | |
1109 "--cidfile-dir", | |
1110 str(bad_cidfile_dir), | |
1111 get_data("tests/wf/" + test_file), | |
1112 ] | |
1113 ) | |
1114 error_code, _, stderr = get_main_output(commands) | |
1115 assert "directory doesn't exist, please create it first" in stderr, stderr | |
1116 assert error_code == 2 or error_code == 1, stderr | |
1117 | |
1118 | |
1119 @needs_docker | |
1120 @pytest.mark.parametrize("factor", test_factors) | |
1121 def test_cid_file_w_prefix(tmp_path: Path, factor: str) -> None: | |
1122 """Test that --cidfile-prefix works.""" | |
1123 test_file = "cache_test_workflow.cwl" | |
1124 cwd = Path.cwd() | |
1125 os.chdir(tmp_path) | |
1126 try: | |
1127 commands = factor.split() | |
1128 commands.extend( | |
1129 [ | |
1130 "--record-container-id", | |
1131 "--cidfile-prefix=pytestcid", | |
1132 get_data("tests/wf/" + test_file), | |
1133 ] | |
1134 ) | |
1135 error_code, stdout, stderr = get_main_output(commands) | |
1136 finally: | |
1137 listing = tmp_path.iterdir() | |
1138 os.chdir(cwd) | |
1139 cidfiles_count = sum(1 for _ in tmp_path.glob("**/pytestcid*")) | |
1140 assert "completed success" in stderr | |
1141 assert error_code == 0 | |
1142 assert cidfiles_count == 2, "{}/n{}".format(list(listing), stderr) | |
1143 | |
1144 | |
1145 @needs_docker | |
1146 @pytest.mark.parametrize("factor", test_factors) | |
1147 def test_secondary_files_v1_1(factor: str) -> None: | |
1148 test_file = "secondary-files.cwl" | |
1149 test_job_file = "secondary-files-job.yml" | |
1150 try: | |
1151 old_umask = os.umask(stat.S_IWOTH) # test run with umask 002 | |
1152 commands = factor.split() | |
1153 commands.extend( | |
1154 [ | |
1155 "--enable-dev", | |
1156 get_data(os.path.join("tests", test_file)), | |
1157 get_data(os.path.join("tests", test_job_file)), | |
1158 ] | |
1159 ) | |
1160 error_code, _, stderr = get_main_output(commands) | |
1161 finally: | |
1162 # 664 in octal, '-rw-rw-r--' | |
1163 assert stat.S_IMODE(os.stat("lsout").st_mode) == 436 | |
1164 os.umask(old_umask) # revert back to original umask | |
1165 assert "completed success" in stderr | |
1166 assert error_code == 0 | |
1167 | |
1168 | |
1169 @needs_docker | |
1170 @pytest.mark.parametrize("factor", test_factors) | |
1171 def test_secondary_files_v1_0(factor: str) -> None: | |
1172 test_file = "secondary-files-string-v1.cwl" | |
1173 test_job_file = "secondary-files-job.yml" | |
1174 try: | |
1175 old_umask = os.umask(stat.S_IWOTH) # test run with umask 002 | |
1176 commands = factor.split() | |
1177 commands.extend( | |
1178 [ | |
1179 get_data(os.path.join("tests", test_file)), | |
1180 get_data(os.path.join("tests", test_job_file)), | |
1181 ] | |
1182 ) | |
1183 error_code, _, stderr = get_main_output(commands) | |
1184 finally: | |
1185 # 664 in octal, '-rw-rw-r--' | |
1186 assert stat.S_IMODE(os.stat("lsout").st_mode) == 436 | |
1187 os.umask(old_umask) # revert back to original umask | |
1188 assert "completed success" in stderr | |
1189 assert error_code == 0 | |
1190 | |
1191 | |
1192 @needs_docker | |
1193 @pytest.mark.parametrize("factor", test_factors) | |
1194 def test_wf_without_container(tmp_path: Path, factor: str) -> None: | |
1195 """Confirm that we can run a workflow without a container.""" | |
1196 test_file = "hello-workflow.cwl" | |
1197 cache_dir = str(tmp_path / "cwltool_cache") | |
1198 commands = factor.split() | |
1199 commands.extend( | |
1200 [ | |
1201 "--cachedir", | |
1202 cache_dir, | |
1203 "--outdir", | |
1204 str(tmp_path / "outdir"), | |
1205 get_data("tests/wf/" + test_file), | |
1206 "--usermessage", | |
1207 "hello", | |
1208 ] | |
1209 ) | |
1210 error_code, _, stderr = get_main_output(commands) | |
1211 | |
1212 assert "completed success" in stderr | |
1213 assert error_code == 0 | |
1214 | |
1215 | |
1216 @needs_docker | |
1217 @pytest.mark.parametrize("factor", test_factors) | |
1218 def test_issue_740_fixed(tmp_path: Path, factor: str) -> None: | |
1219 """Confirm that re-running a particular workflow with caching suceeds.""" | |
1220 test_file = "cache_test_workflow.cwl" | |
1221 cache_dir = str(tmp_path / "cwltool_cache") | |
1222 commands = factor.split() | |
1223 commands.extend(["--cachedir", cache_dir, get_data("tests/wf/" + test_file)]) | |
1224 error_code, _, stderr = get_main_output(commands) | |
1225 | |
1226 assert "completed success" in stderr | |
1227 assert error_code == 0 | |
1228 | |
1229 commands = factor.split() | |
1230 commands.extend(["--cachedir", cache_dir, get_data("tests/wf/" + test_file)]) | |
1231 error_code, _, stderr = get_main_output(commands) | |
1232 | |
1233 assert "Output of job will be cached in" not in stderr | |
1234 assert error_code == 0, stderr | |
1235 | |
1236 | |
1237 @needs_docker | |
1238 def test_compute_checksum() -> None: | |
1239 runtime_context = RuntimeContext() | |
1240 runtime_context.compute_checksum = True | |
1241 runtime_context.use_container = onWindows() | |
1242 factory = cwltool.factory.Factory(runtime_context=runtime_context) | |
1243 echo = factory.make(get_data("tests/wf/cat-tool.cwl")) | |
1244 output = echo( | |
1245 file1={"class": "File", "location": get_data("tests/wf/whale.txt")}, | |
1246 reverse=False, | |
1247 ) | |
1248 assert isinstance(output, dict) | |
1249 result = output["output"] | |
1250 assert isinstance(result, dict) | |
1251 assert result["checksum"] == "sha1$327fc7aedf4f6b69a42a7c8b808dc5a7aff61376" | |
1252 | |
1253 | |
1254 @needs_docker | |
1255 @pytest.mark.parametrize("factor", test_factors) | |
1256 def test_no_compute_chcksum(tmp_path: Path, factor: str) -> None: | |
1257 test_file = "tests/wf/wc-tool.cwl" | |
1258 job_file = "tests/wf/wc-job.json" | |
1259 commands = factor.split() | |
1260 commands.extend( | |
1261 [ | |
1262 "--no-compute-checksum", | |
1263 "--outdir", | |
1264 str(tmp_path), | |
1265 get_data(test_file), | |
1266 get_data(job_file), | |
1267 ] | |
1268 ) | |
1269 error_code, stdout, stderr = get_main_output(commands) | |
1270 assert "completed success" in stderr | |
1271 assert error_code == 0 | |
1272 assert "checksum" not in stdout | |
1273 | |
1274 | |
1275 @pytest.mark.skipif(onWindows(), reason="udocker is Linux/macOS only") | |
1276 @pytest.mark.parametrize("factor", test_factors) | |
1277 def test_bad_userspace_runtime(factor: str) -> None: | |
1278 test_file = "tests/wf/wc-tool.cwl" | |
1279 job_file = "tests/wf/wc-job.json" | |
1280 commands = factor.split() | |
1281 commands.extend( | |
1282 [ | |
1283 "--user-space-docker-cmd=quaquioN", | |
1284 "--default-container=debian", | |
1285 get_data(test_file), | |
1286 get_data(job_file), | |
1287 ] | |
1288 ) | |
1289 error_code, stdout, stderr = get_main_output(commands) | |
1290 assert "or quaquioN is missing or broken" in stderr, stderr | |
1291 assert error_code == 1 | |
1292 | |
1293 | |
1294 @windows_needs_docker | |
1295 @pytest.mark.parametrize("factor", test_factors) | |
1296 def test_bad_basecommand(factor: str) -> None: | |
1297 test_file = "tests/wf/missing-tool.cwl" | |
1298 commands = factor.split() | |
1299 commands.extend([get_data(test_file)]) | |
1300 error_code, stdout, stderr = get_main_output(commands) | |
1301 assert "'neenooGo' not found" in stderr, stderr | |
1302 assert error_code == 1 | |
1303 | |
1304 | |
1305 @needs_docker | |
1306 @pytest.mark.parametrize("factor", test_factors) | |
1307 def test_bad_basecommand_docker(factor: str) -> None: | |
1308 test_file = "tests/wf/missing-tool.cwl" | |
1309 commands = factor.split() | |
1310 commands.extend(["--debug", "--default-container", "debian", get_data(test_file)]) | |
1311 error_code, stdout, stderr = get_main_output(commands) | |
1312 assert "permanentFail" in stderr, stderr | |
1313 assert error_code == 1 | |
1314 | |
1315 | |
1316 @pytest.mark.parametrize("factor", test_factors) | |
1317 def test_v1_0_position_expression(factor: str) -> None: | |
1318 test_file = "tests/echo-position-expr.cwl" | |
1319 test_job = "tests/echo-position-expr-job.yml" | |
1320 commands = factor.split() | |
1321 commands.extend(["--debug", get_data(test_file), get_data(test_job)]) | |
1322 error_code, stdout, stderr = get_main_output(commands) | |
1323 assert "is not int" in stderr, stderr | |
1324 assert error_code == 1 | |
1325 | |
1326 | |
1327 @windows_needs_docker | |
1328 @pytest.mark.parametrize("factor", test_factors) | |
1329 def test_optional_numeric_output_0(factor: str) -> None: | |
1330 test_file = "tests/wf/optional-numerical-output-0.cwl" | |
1331 commands = factor.split() | |
1332 commands.extend([get_data(test_file)]) | |
1333 error_code, stdout, stderr = get_main_output(commands) | |
1334 | |
1335 assert "completed success" in stderr | |
1336 assert error_code == 0 | |
1337 assert json.loads(stdout)["out"] == 0 | |
1338 | |
1339 | |
1340 @pytest.mark.parametrize("factor", test_factors) | |
1341 @windows_needs_docker | |
1342 def test_env_filtering(factor: str) -> None: | |
1343 test_file = "tests/env.cwl" | |
1344 commands = factor.split() | |
1345 commands.extend([get_data(test_file)]) | |
1346 error_code, stdout, stderr = get_main_output(commands) | |
1347 | |
1348 process = subprocess.Popen( | |
1349 [ | |
1350 "sh", | |
1351 "-c", | |
1352 r"""getTrueShellExeName() { | |
1353 local trueExe nextTarget 2>/dev/null | |
1354 trueExe=$(ps -o comm= $$) || return 1 | |
1355 [ "${trueExe#-}" = "$trueExe" ] || trueExe=${trueExe#-} | |
1356 [ "${trueExe#/}" != "$trueExe" ] || trueExe=$([ -n "$ZSH_VERSION" ] && which -p "$trueExe" || which "$trueExe") | |
1357 while nextTarget=$(readlink "$trueExe"); do trueExe=$nextTarget; done | |
1358 printf '%s\n' "$(basename "$trueExe")" | |
1359 } ; getTrueShellExeName""", | |
1360 ], | |
1361 stdout=subprocess.PIPE, | |
1362 stderr=subprocess.PIPE, | |
1363 env=None, | |
1364 ) | |
1365 sh_name_b, sh_name_err = process.communicate() | |
1366 assert sh_name_b | |
1367 sh_name = sh_name_b.decode("utf-8").strip() | |
1368 | |
1369 assert "completed success" in stderr, (error_code, stdout, stderr) | |
1370 assert error_code == 0, (error_code, stdout, stderr) | |
1371 if onWindows(): | |
1372 target = 5 | |
1373 elif sh_name == "dash": | |
1374 target = 4 | |
1375 else: # bash adds "SHLVL" and "_" environment variables | |
1376 target = 6 | |
1377 result = json.loads(stdout)["env_count"] | |
1378 details = "" | |
1379 if result != target: | |
1380 _, details, _ = get_main_output(["--quiet", get_data("tests/env2.cwl")]) | |
1381 print(sh_name) | |
1382 print(sh_name_err) | |
1383 print(details) | |
1384 assert result == target, (error_code, sh_name, sh_name_err, details, stdout, stderr) | |
1385 | |
1386 | |
1387 @windows_needs_docker | |
1388 def test_v1_0_arg_empty_prefix_separate_false() -> None: | |
1389 test_file = "tests/arg-empty-prefix-separate-false.cwl" | |
1390 error_code, stdout, stderr = get_main_output( | |
1391 ["--debug", get_data(test_file), "--echo"] | |
1392 ) | |
1393 assert "completed success" in stderr | |
1394 assert error_code == 0 | |
1395 | |
1396 | |
1397 def test_scatter_output_filenames(tmp_path: Path) -> None: | |
1398 """If a scatter step produces identically named output then confirm that the final output is renamed correctly.""" | |
1399 cwd = Path.cwd() | |
1400 os.chdir(tmp_path) | |
1401 rtc = RuntimeContext() | |
1402 rtc.outdir = str(cwd) | |
1403 factory = cwltool.factory.Factory(runtime_context=rtc) | |
1404 output_names = ["output.txt", "output.txt_2", "output.txt_3"] | |
1405 scatter_workflow = factory.make(get_data("tests/scatter_numbers.cwl")) | |
1406 result = scatter_workflow(range=3) | |
1407 assert isinstance(result, dict) | |
1408 assert "output" in result | |
1409 | |
1410 locations = sorted([element["location"] for element in result["output"]]) | |
1411 | |
1412 assert ( | |
1413 locations[0].endswith("output.txt") | |
1414 and locations[1].endswith("output.txt_2") | |
1415 and locations[2].endswith("output.txt_3") | |
1416 ), f"Locations {locations} do not end with {output_names}" |