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}"