toil 8.1.0b1__py3-none-any.whl → 8.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toil/__init__.py +0 -35
- toil/batchSystems/abstractBatchSystem.py +1 -1
- toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
- toil/batchSystems/awsBatch.py +1 -1
- toil/batchSystems/cleanup_support.py +1 -1
- toil/batchSystems/kubernetes.py +53 -7
- toil/batchSystems/local_support.py +1 -1
- toil/batchSystems/mesos/batchSystem.py +13 -8
- toil/batchSystems/mesos/test/__init__.py +3 -2
- toil/batchSystems/singleMachine.py +1 -1
- toil/batchSystems/slurm.py +27 -26
- toil/bus.py +5 -3
- toil/common.py +39 -11
- toil/cwl/cwltoil.py +1 -1
- toil/job.py +64 -49
- toil/jobStores/abstractJobStore.py +24 -3
- toil/jobStores/fileJobStore.py +25 -1
- toil/jobStores/googleJobStore.py +104 -30
- toil/leader.py +9 -0
- toil/lib/accelerators.py +3 -1
- toil/lib/aws/utils.py.orig +504 -0
- toil/lib/bioio.py +1 -1
- toil/lib/docker.py +252 -91
- toil/lib/dockstore.py +11 -3
- toil/lib/exceptions.py +5 -3
- toil/lib/history.py +87 -13
- toil/lib/history_submission.py +23 -9
- toil/lib/io.py +34 -22
- toil/lib/misc.py +7 -1
- toil/lib/resources.py +2 -1
- toil/lib/threading.py +11 -10
- toil/options/common.py +8 -0
- toil/options/wdl.py +11 -0
- toil/server/api_spec/LICENSE +201 -0
- toil/server/api_spec/README.rst +5 -0
- toil/server/cli/wes_cwl_runner.py +2 -1
- toil/test/__init__.py +275 -115
- toil/test/batchSystems/batchSystemTest.py +227 -205
- toil/test/batchSystems/test_slurm.py +27 -0
- toil/test/cactus/pestis.tar.gz +0 -0
- toil/test/conftest.py +7 -0
- toil/test/cwl/2.fasta +11 -0
- toil/test/cwl/2.fastq +12 -0
- toil/test/cwl/conftest.py +1 -1
- toil/test/cwl/cwlTest.py +999 -867
- toil/test/cwl/directory/directory/file.txt +15 -0
- toil/test/cwl/download_directory_file.json +4 -0
- toil/test/cwl/download_directory_s3.json +4 -0
- toil/test/cwl/download_file.json +6 -0
- toil/test/cwl/download_http.json +6 -0
- toil/test/cwl/download_https.json +6 -0
- toil/test/cwl/download_s3.json +6 -0
- toil/test/cwl/download_subdirectory_file.json +5 -0
- toil/test/cwl/download_subdirectory_s3.json +5 -0
- toil/test/cwl/empty.json +1 -0
- toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
- toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
- toil/test/cwl/optional-file-exists.json +6 -0
- toil/test/cwl/optional-file-missing.json +6 -0
- toil/test/cwl/preemptible_expression.json +1 -0
- toil/test/cwl/revsort-job-missing.json +6 -0
- toil/test/cwl/revsort-job.json +6 -0
- toil/test/cwl/s3_secondary_file.json +16 -0
- toil/test/cwl/seqtk_seq_job.json +6 -0
- toil/test/cwl/stream.json +6 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
- toil/test/cwl/whale.txt +16 -0
- toil/test/docs/scripts/example_alwaysfail.py +38 -0
- toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
- toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
- toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
- toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
- toil/test/docs/scripts/tutorial_arguments.py +23 -0
- toil/test/docs/scripts/tutorial_debugging.patch +12 -0
- toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
- toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
- toil/test/docs/scripts/tutorial_docker.py +20 -0
- toil/test/docs/scripts/tutorial_dynamic.py +24 -0
- toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
- toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
- toil/test/docs/scripts/tutorial_helloworld.py +15 -0
- toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
- toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
- toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
- toil/test/docs/scripts/tutorial_managing.py +29 -0
- toil/test/docs/scripts/tutorial_managing2.py +56 -0
- toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
- toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
- toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
- toil/test/docs/scripts/tutorial_promises.py +25 -0
- toil/test/docs/scripts/tutorial_promises2.py +30 -0
- toil/test/docs/scripts/tutorial_quickstart.py +22 -0
- toil/test/docs/scripts/tutorial_requirements.py +44 -0
- toil/test/docs/scripts/tutorial_services.py +45 -0
- toil/test/docs/scripts/tutorial_staging.py +45 -0
- toil/test/docs/scripts/tutorial_stats.py +64 -0
- toil/test/lib/aws/test_iam.py +3 -1
- toil/test/lib/dockerTest.py +205 -122
- toil/test/lib/test_history.py +101 -77
- toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
- toil/test/provisioners/clusterTest.py +4 -4
- toil/test/provisioners/gceProvisionerTest.py +16 -14
- toil/test/sort/sort.py +4 -1
- toil/test/src/busTest.py +17 -17
- toil/test/src/deferredFunctionTest.py +145 -132
- toil/test/src/importExportFileTest.py +71 -63
- toil/test/src/jobEncapsulationTest.py +27 -28
- toil/test/src/jobServiceTest.py +149 -133
- toil/test/src/jobTest.py +219 -211
- toil/test/src/miscTests.py +66 -60
- toil/test/src/promisedRequirementTest.py +163 -169
- toil/test/src/regularLogTest.py +24 -24
- toil/test/src/resourceTest.py +82 -76
- toil/test/src/restartDAGTest.py +51 -47
- toil/test/src/resumabilityTest.py +24 -19
- toil/test/src/retainTempDirTest.py +60 -57
- toil/test/src/systemTest.py +17 -13
- toil/test/src/threadingTest.py +29 -32
- toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
- toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
- toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
- toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
- toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
- toil/test/utils/toilDebugTest.py +117 -102
- toil/test/utils/toilKillTest.py +54 -53
- toil/test/utils/utilsTest.py +303 -229
- toil/test/wdl/lint_error.wdl +9 -0
- toil/test/wdl/md5sum/empty_file.json +1 -0
- toil/test/wdl/md5sum/md5sum-gs.json +1 -0
- toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
- toil/test/wdl/md5sum/md5sum.input +1 -0
- toil/test/wdl/md5sum/md5sum.json +1 -0
- toil/test/wdl/md5sum/md5sum.wdl +25 -0
- toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
- toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
- toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
- toil/test/wdl/standard_library/as_map.json +16 -0
- toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
- toil/test/wdl/standard_library/as_pairs.json +7 -0
- toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
- toil/test/wdl/standard_library/ceil.json +3 -0
- toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
- toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
- toil/test/wdl/standard_library/collect_by_key.json +1 -0
- toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
- toil/test/wdl/standard_library/cross.json +11 -0
- toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
- toil/test/wdl/standard_library/flatten.json +7 -0
- toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
- toil/test/wdl/standard_library/floor.json +3 -0
- toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
- toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
- toil/test/wdl/standard_library/keys.json +8 -0
- toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
- toil/test/wdl/standard_library/length.json +7 -0
- toil/test/wdl/standard_library/length_as_input.wdl +16 -0
- toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
- toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
- toil/test/wdl/standard_library/length_invalid.json +3 -0
- toil/test/wdl/standard_library/range.json +3 -0
- toil/test/wdl/standard_library/range_0.json +3 -0
- toil/test/wdl/standard_library/range_as_input.wdl +17 -0
- toil/test/wdl/standard_library/range_invalid.json +3 -0
- toil/test/wdl/standard_library/read_boolean.json +3 -0
- toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_float.json +3 -0
- toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_int.json +3 -0
- toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_json.json +3 -0
- toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_lines.json +3 -0
- toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_map.json +3 -0
- toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_string.json +3 -0
- toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_tsv.json +3 -0
- toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
- toil/test/wdl/standard_library/round.json +3 -0
- toil/test/wdl/standard_library/round_as_command.wdl +16 -0
- toil/test/wdl/standard_library/round_as_input.wdl +16 -0
- toil/test/wdl/standard_library/size.json +3 -0
- toil/test/wdl/standard_library/size_as_command.wdl +17 -0
- toil/test/wdl/standard_library/size_as_output.wdl +36 -0
- toil/test/wdl/standard_library/stderr.json +3 -0
- toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
- toil/test/wdl/standard_library/stdout.json +3 -0
- toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
- toil/test/wdl/standard_library/sub.json +3 -0
- toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
- toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
- toil/test/wdl/standard_library/transpose.json +6 -0
- toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
- toil/test/wdl/standard_library/write_json.json +6 -0
- toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_lines.json +7 -0
- toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_map.json +6 -0
- toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_tsv.json +6 -0
- toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
- toil/test/wdl/standard_library/zip.json +12 -0
- toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
- toil/test/wdl/test.csv +3 -0
- toil/test/wdl/test.tsv +3 -0
- toil/test/wdl/testfiles/croo.wdl +38 -0
- toil/test/wdl/testfiles/drop_files.wdl +62 -0
- toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
- toil/test/wdl/testfiles/empty.txt +0 -0
- toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
- toil/test/wdl/testfiles/random.wdl +66 -0
- toil/test/wdl/testfiles/string_file_coercion.json +1 -0
- toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
- toil/test/wdl/testfiles/test.json +4 -0
- toil/test/wdl/testfiles/test_boolean.txt +1 -0
- toil/test/wdl/testfiles/test_float.txt +1 -0
- toil/test/wdl/testfiles/test_int.txt +1 -0
- toil/test/wdl/testfiles/test_lines.txt +5 -0
- toil/test/wdl/testfiles/test_map.txt +2 -0
- toil/test/wdl/testfiles/test_string.txt +1 -0
- toil/test/wdl/testfiles/url_to_file.wdl +13 -0
- toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
- toil/test/wdl/testfiles/vocab.json +1 -0
- toil/test/wdl/testfiles/vocab.wdl +66 -0
- toil/test/wdl/testfiles/wait.wdl +34 -0
- toil/test/wdl/wdl_specification/type_pair.json +23 -0
- toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
- toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
- toil/test/wdl/wdl_specification/v1_spec.json +1 -0
- toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
- toil/test/wdl/wdltoil_test.py +680 -407
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/version.py +9 -9
- toil/wdl/wdltoil.py +336 -123
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/METADATA +5 -4
- toil-8.2.0.dist-info/RECORD +439 -0
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
- toil-8.1.0b1.dist-info/RECORD +0 -259
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/top_level.txt +0 -0
toil/test/wdl/wdltoil_test.py
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
import pytest
|
|
5
4
|
import re
|
|
6
5
|
import shutil
|
|
7
6
|
import string
|
|
8
7
|
import subprocess
|
|
9
8
|
import unittest
|
|
10
|
-
from
|
|
9
|
+
from collections.abc import Generator
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional, Union, cast
|
|
11
12
|
from unittest.mock import patch
|
|
12
13
|
from uuid import uuid4
|
|
13
14
|
|
|
15
|
+
import pytest
|
|
14
16
|
import WDL.Error
|
|
15
17
|
import WDL.Expr
|
|
16
18
|
|
|
17
19
|
from toil.fileStores import FileID
|
|
18
20
|
from toil.test import (
|
|
19
21
|
ToilTest,
|
|
22
|
+
get_data,
|
|
20
23
|
needs_docker,
|
|
21
24
|
needs_docker_cuda,
|
|
22
25
|
needs_google_storage,
|
|
@@ -35,20 +38,6 @@ from toil.wdl.wdltoil import (
|
|
|
35
38
|
logger = logging.getLogger(__name__)
|
|
36
39
|
|
|
37
40
|
|
|
38
|
-
@needs_wdl
|
|
39
|
-
class BaseWDLTest(ToilTest):
|
|
40
|
-
"""Base test class for WDL tests."""
|
|
41
|
-
|
|
42
|
-
def setUp(self) -> None:
|
|
43
|
-
"""Runs anew before each test to create farm fresh temp dirs."""
|
|
44
|
-
self.output_dir = os.path.join("/tmp/", "toil-wdl-test-" + str(uuid4()))
|
|
45
|
-
os.makedirs(self.output_dir)
|
|
46
|
-
|
|
47
|
-
def tearDown(self) -> None:
|
|
48
|
-
if os.path.exists(self.output_dir):
|
|
49
|
-
shutil.rmtree(self.output_dir)
|
|
50
|
-
|
|
51
|
-
|
|
52
41
|
WDL_CONFORMANCE_TEST_REPO = "https://github.com/DataBiosphere/wdl-conformance-tests.git"
|
|
53
42
|
WDL_CONFORMANCE_TEST_COMMIT = "baf44bcc7e6f6927540adf77d91b26a5558ae4b7"
|
|
54
43
|
# These tests are known to require things not implemented by
|
|
@@ -71,31 +60,23 @@ WDL_UNIT_TESTS_UNSUPPORTED_BY_TOIL = [
|
|
|
71
60
|
69, # Same as 68
|
|
72
61
|
87, # MiniWDL does not handle metacharacters properly when running regex, https://github.com/chanzuckerberg/miniwdl/issues/709
|
|
73
62
|
97, # miniwdl bug, see https://github.com/chanzuckerberg/miniwdl/issues/701
|
|
74
|
-
105,
|
|
75
|
-
107,
|
|
76
|
-
108,
|
|
77
|
-
109,
|
|
78
|
-
110,
|
|
79
|
-
120,
|
|
80
|
-
131,
|
|
81
|
-
134,
|
|
82
|
-
144 # miniwdl and toil bug
|
|
63
|
+
105, # miniwdl (and toil) bug, unserializable json is serialized, see https://github.com/chanzuckerberg/miniwdl/issues/702
|
|
64
|
+
107, # object not supported
|
|
65
|
+
108, # object not supported
|
|
66
|
+
109, # object not supported
|
|
67
|
+
110, # object not supported
|
|
68
|
+
120, # miniwdl bug, see https://github.com/chanzuckerberg/miniwdl/issues/699
|
|
69
|
+
131, # miniwdl bug, evalerror, see https://github.com/chanzuckerberg/miniwdl/issues/700
|
|
70
|
+
134, # same as 131
|
|
71
|
+
144, # miniwdl and toil bug
|
|
83
72
|
]
|
|
84
73
|
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
WDL conformance tests for Toil.
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
wdl_dir = "wdl-conformance-tests"
|
|
93
|
-
|
|
94
|
-
@classmethod
|
|
95
|
-
def setUpClass(cls) -> None:
|
|
96
|
-
|
|
75
|
+
@pytest.fixture(scope="function")
|
|
76
|
+
def wdl_conformance_test_repo(tmp_path: Path) -> Generator[Path]:
|
|
77
|
+
try:
|
|
97
78
|
p = subprocess.Popen(
|
|
98
|
-
f"git clone {WDL_CONFORMANCE_TEST_REPO} {
|
|
79
|
+
f"git clone {WDL_CONFORMANCE_TEST_REPO} {str(tmp_path)} && cd {str(tmp_path)} && git checkout {WDL_CONFORMANCE_TEST_COMMIT}",
|
|
99
80
|
shell=True,
|
|
100
81
|
)
|
|
101
82
|
|
|
@@ -103,12 +84,17 @@ class WDLConformanceTests(BaseWDLTest):
|
|
|
103
84
|
|
|
104
85
|
if p.returncode > 0:
|
|
105
86
|
raise RuntimeError("Could not clone WDL conformance tests")
|
|
87
|
+
yield tmp_path
|
|
88
|
+
finally:
|
|
89
|
+
pass # no cleanup needed
|
|
106
90
|
|
|
107
|
-
os.chdir(cls.wdl_dir)
|
|
108
91
|
|
|
109
|
-
|
|
92
|
+
class TestWDLConformance:
|
|
93
|
+
"""
|
|
94
|
+
WDL conformance tests for Toil.
|
|
95
|
+
"""
|
|
110
96
|
|
|
111
|
-
def check(self, p: subprocess.CompletedProcess) -> None:
|
|
97
|
+
def check(self, p: "subprocess.CompletedProcess[bytes]") -> None:
|
|
112
98
|
"""
|
|
113
99
|
Make sure a call completed or explain why it failed.
|
|
114
100
|
"""
|
|
@@ -126,365 +112,653 @@ class WDLConformanceTests(BaseWDLTest):
|
|
|
126
112
|
p.check_returncode()
|
|
127
113
|
|
|
128
114
|
@slow
|
|
129
|
-
def test_unit_tests_v11(self):
|
|
115
|
+
def test_unit_tests_v11(self, wdl_conformance_test_repo: Path) -> None:
|
|
130
116
|
# There are still some bugs with the WDL spec, use a fixed version until
|
|
131
117
|
# See comments of https://github.com/openwdl/wdl/pull/669
|
|
118
|
+
os.chdir(wdl_conformance_test_repo)
|
|
132
119
|
repo_url = "https://github.com/stxue1/wdl.git"
|
|
133
120
|
repo_branch = "wdl-1.1.3-fixes"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
121
|
+
commands1 = [
|
|
122
|
+
exactPython,
|
|
123
|
+
"setup_unit_tests.py",
|
|
124
|
+
"-v",
|
|
125
|
+
"1.1",
|
|
126
|
+
"--extra-patch-data",
|
|
127
|
+
"unit_tests_patch_data.yaml",
|
|
128
|
+
"--repo",
|
|
129
|
+
repo_url,
|
|
130
|
+
"--branch",
|
|
131
|
+
repo_branch,
|
|
132
|
+
"--force-pull",
|
|
133
|
+
]
|
|
134
|
+
p1 = subprocess.run(commands1, capture_output=True)
|
|
135
|
+
self.check(p1)
|
|
136
|
+
commands2 = [
|
|
137
|
+
exactPython,
|
|
138
|
+
"run_unit.py",
|
|
139
|
+
"-r",
|
|
140
|
+
"toil-wdl-runner",
|
|
141
|
+
"-v",
|
|
142
|
+
"1.1",
|
|
143
|
+
"--progress",
|
|
144
|
+
"--exclude-numbers",
|
|
145
|
+
",".join([str(t) for t in WDL_UNIT_TESTS_UNSUPPORTED_BY_TOIL]),
|
|
146
|
+
]
|
|
147
|
+
p2 = subprocess.run(commands2, capture_output=True)
|
|
148
|
+
self.check(p2)
|
|
140
149
|
|
|
141
150
|
# estimated running time: 10 minutes
|
|
142
151
|
@slow
|
|
143
|
-
def test_conformance_tests_v10(self):
|
|
144
|
-
|
|
152
|
+
def test_conformance_tests_v10(self, wdl_conformance_test_repo: Path) -> None:
|
|
153
|
+
os.chdir(wdl_conformance_test_repo)
|
|
154
|
+
commands = [
|
|
155
|
+
exactPython,
|
|
156
|
+
"run.py",
|
|
157
|
+
"--runner",
|
|
158
|
+
"toil-wdl-runner",
|
|
159
|
+
"--conformance-file",
|
|
160
|
+
"conformance.yaml",
|
|
161
|
+
"-v",
|
|
162
|
+
"1.0",
|
|
163
|
+
]
|
|
145
164
|
if WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL:
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
commands.append("--exclude-numbers")
|
|
166
|
+
commands.append(
|
|
148
167
|
",".join([str(t) for t in WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL])
|
|
149
168
|
)
|
|
150
|
-
p = subprocess.run(
|
|
169
|
+
p = subprocess.run(commands, capture_output=True)
|
|
151
170
|
|
|
152
171
|
self.check(p)
|
|
153
172
|
|
|
154
173
|
# estimated running time: 10 minutes
|
|
155
174
|
@slow
|
|
156
|
-
def test_conformance_tests_v11(self):
|
|
157
|
-
|
|
175
|
+
def test_conformance_tests_v11(self, wdl_conformance_test_repo: Path) -> None:
|
|
176
|
+
os.chdir(wdl_conformance_test_repo)
|
|
177
|
+
commands = [
|
|
178
|
+
exactPython,
|
|
179
|
+
"run.py",
|
|
180
|
+
"--runner",
|
|
181
|
+
"toil-wdl-runner",
|
|
182
|
+
"--conformance-file",
|
|
183
|
+
"conformance.yaml",
|
|
184
|
+
"-v",
|
|
185
|
+
"1.1",
|
|
186
|
+
]
|
|
158
187
|
if WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL:
|
|
159
|
-
|
|
160
|
-
|
|
188
|
+
commands.append("--exclude-numbers")
|
|
189
|
+
commands.append(
|
|
161
190
|
",".join([str(t) for t in WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL])
|
|
162
191
|
)
|
|
163
|
-
p = subprocess.run(
|
|
192
|
+
p = subprocess.run(commands, capture_output=True)
|
|
164
193
|
|
|
165
194
|
self.check(p)
|
|
166
195
|
|
|
167
196
|
@slow
|
|
168
|
-
def test_conformance_tests_integration(
|
|
169
|
-
|
|
197
|
+
def test_conformance_tests_integration(
|
|
198
|
+
self, wdl_conformance_test_repo: Path
|
|
199
|
+
) -> None:
|
|
200
|
+
os.chdir(wdl_conformance_test_repo)
|
|
201
|
+
commands = [
|
|
202
|
+
exactPython,
|
|
203
|
+
"run.py",
|
|
204
|
+
"--runner",
|
|
205
|
+
"toil-wdl-runner",
|
|
206
|
+
"-v",
|
|
207
|
+
"1.0",
|
|
208
|
+
"--conformance-file",
|
|
209
|
+
"integration.yaml",
|
|
210
|
+
"--id",
|
|
211
|
+
"encode,tut01,tut02,tut03,tut04",
|
|
212
|
+
]
|
|
170
213
|
p = subprocess.run(
|
|
171
|
-
|
|
172
|
-
+ [
|
|
173
|
-
"-v",
|
|
174
|
-
"1.0",
|
|
175
|
-
"--conformance-file",
|
|
176
|
-
"integration.yaml",
|
|
177
|
-
"--id",
|
|
178
|
-
ids_to_run,
|
|
179
|
-
],
|
|
214
|
+
commands,
|
|
180
215
|
capture_output=True,
|
|
181
216
|
)
|
|
182
217
|
|
|
183
218
|
self.check(p)
|
|
184
219
|
|
|
185
|
-
@classmethod
|
|
186
|
-
def tearDownClass(cls) -> None:
|
|
187
|
-
upper_dir = os.path.dirname(os.getcwd())
|
|
188
|
-
os.chdir(upper_dir)
|
|
189
|
-
shutil.rmtree("wdl-conformance-tests")
|
|
190
220
|
|
|
191
|
-
|
|
192
|
-
class WDLTests(BaseWDLTest):
|
|
221
|
+
class TestWDL:
|
|
193
222
|
"""Tests for Toil's MiniWDL-based implementation."""
|
|
194
223
|
|
|
195
|
-
|
|
196
|
-
def setUpClass(cls) -> None:
|
|
197
|
-
"""Runs once for all tests."""
|
|
198
|
-
cls.base_command = [exactPython, "-m", "toil.wdl.wdltoil"]
|
|
224
|
+
base_command = [exactPython, "-m", "toil.wdl.wdltoil"]
|
|
199
225
|
|
|
200
226
|
# We inherit a testMD5sum but it is going to need Singularity or Docker
|
|
201
227
|
# now. And also needs to have a WDL 1.0+ WDL file. So we replace it.
|
|
202
228
|
@needs_singularity_or_docker
|
|
203
|
-
def test_MD5sum(self):
|
|
229
|
+
def test_MD5sum(self, tmp_path: Path) -> None:
|
|
204
230
|
"""Test if Toil produces the same outputs as known good outputs for WDL's
|
|
205
231
|
GATK tutorial #1."""
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
232
|
+
with get_data("test/wdl/md5sum/md5sum.1.0.wdl") as wdl:
|
|
233
|
+
with get_data("test/wdl/md5sum/md5sum.json") as json_file:
|
|
234
|
+
result_json = subprocess.check_output(
|
|
235
|
+
self.base_command
|
|
236
|
+
+ [
|
|
237
|
+
str(wdl),
|
|
238
|
+
str(json_file),
|
|
239
|
+
"-o",
|
|
240
|
+
str(tmp_path),
|
|
241
|
+
"--logDebug",
|
|
242
|
+
"--retryCount=0",
|
|
243
|
+
]
|
|
244
|
+
)
|
|
245
|
+
result = json.loads(result_json)
|
|
246
|
+
|
|
247
|
+
assert "ga4ghMd5.value" in result
|
|
248
|
+
assert isinstance(result["ga4ghMd5.value"], str)
|
|
249
|
+
assert os.path.exists(result["ga4ghMd5.value"])
|
|
250
|
+
assert os.path.basename(result["ga4ghMd5.value"]) == "md5sum.txt"
|
|
251
|
+
|
|
252
|
+
def test_url_to_file(self, tmp_path: Path) -> None:
|
|
221
253
|
"""
|
|
222
254
|
Test if web URL strings can be coerced to usable Files.
|
|
223
255
|
"""
|
|
224
|
-
|
|
256
|
+
with get_data("test/wdl/testfiles/url_to_file.wdl") as wdl:
|
|
257
|
+
result_json = subprocess.check_output(
|
|
258
|
+
self.base_command
|
|
259
|
+
+ [str(wdl), "-o", str(tmp_path), "--logInfo", "--retryCount=0"]
|
|
260
|
+
)
|
|
261
|
+
result = json.loads(result_json)
|
|
225
262
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
result = json.loads(result_json)
|
|
263
|
+
assert "url_to_file.first_line" in result
|
|
264
|
+
assert isinstance(result["url_to_file.first_line"], str)
|
|
265
|
+
assert result["url_to_file.first_line"] == "chr1\t248387328"
|
|
231
266
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
267
|
+
def test_string_file_coercion(self, tmp_path: Path) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Test if input Files can be coerced to string and back.
|
|
270
|
+
"""
|
|
271
|
+
with get_data("test/wdl/testfiles/string_file_coercion.wdl") as wdl:
|
|
272
|
+
with get_data("test/wdl/testfiles/string_file_coercion.json") as json_file:
|
|
273
|
+
result_json = subprocess.check_output(
|
|
274
|
+
self.base_command
|
|
275
|
+
+ [
|
|
276
|
+
str(wdl),
|
|
277
|
+
str(json_file),
|
|
278
|
+
"-o",
|
|
279
|
+
str(tmp_path),
|
|
280
|
+
"--logInfo",
|
|
281
|
+
"--retryCount=0"
|
|
282
|
+
]
|
|
283
|
+
)
|
|
284
|
+
result = json.loads(result_json)
|
|
285
|
+
|
|
286
|
+
assert "StringFileCoercion.output_file" in result
|
|
235
287
|
|
|
236
288
|
@needs_docker
|
|
237
|
-
def test_wait(self):
|
|
289
|
+
def test_wait(self, tmp_path: Path) -> None:
|
|
238
290
|
"""
|
|
239
291
|
Test if Bash "wait" works in WDL scripts.
|
|
240
292
|
"""
|
|
241
|
-
|
|
293
|
+
with get_data("test/wdl/testfiles/wait.wdl") as wdl:
|
|
294
|
+
result_json = subprocess.check_output(
|
|
295
|
+
self.base_command
|
|
296
|
+
+ [
|
|
297
|
+
str(wdl),
|
|
298
|
+
"-o",
|
|
299
|
+
str(tmp_path),
|
|
300
|
+
"--logInfo",
|
|
301
|
+
"--retryCount=0",
|
|
302
|
+
"--wdlContainer=docker",
|
|
303
|
+
]
|
|
304
|
+
)
|
|
305
|
+
result = json.loads(result_json)
|
|
242
306
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
)
|
|
254
|
-
|
|
307
|
+
assert "wait.result" in result
|
|
308
|
+
assert isinstance(result["wait.result"], str)
|
|
309
|
+
assert result["wait.result"] == "waited"
|
|
310
|
+
|
|
311
|
+
@needs_singularity_or_docker
|
|
312
|
+
def test_workflow_file_deletion(self, tmp_path: Path) -> None:
|
|
313
|
+
"""
|
|
314
|
+
Test if Toil can delete non-output outputs at the end of a workflow.
|
|
315
|
+
"""
|
|
316
|
+
# Keep a job store around to inspect for files.
|
|
317
|
+
(tmp_path / "jobStore").mkdir()
|
|
318
|
+
job_store = tmp_path / "jobStore" / "tree"
|
|
319
|
+
|
|
320
|
+
# Make a working directory to run in
|
|
321
|
+
work_dir = tmp_path / "workDir"
|
|
322
|
+
work_dir.mkdir()
|
|
323
|
+
# Make the file that will be imported from a string in the workflow
|
|
324
|
+
referenced_file = work_dir / "localfile.txt"
|
|
325
|
+
with referenced_file.open("w") as f:
|
|
326
|
+
f.write("This file is imported by local path in the workflow")
|
|
327
|
+
# Make the file to pass as input
|
|
328
|
+
sent_in_file = work_dir / "sent_in.txt"
|
|
329
|
+
with sent_in_file.open("w") as f:
|
|
330
|
+
f.write("This file is sent in as input")
|
|
331
|
+
|
|
332
|
+
with get_data("test/wdl/testfiles/drop_files.wdl") as wdl:
|
|
333
|
+
result_json = subprocess.check_output(
|
|
334
|
+
self.base_command
|
|
335
|
+
+ [
|
|
336
|
+
str(wdl),
|
|
337
|
+
"-o",
|
|
338
|
+
str(tmp_path / "output"),
|
|
339
|
+
"--jobStore",
|
|
340
|
+
job_store,
|
|
341
|
+
"--clean=never",
|
|
342
|
+
"--logInfo",
|
|
343
|
+
"--retryCount=0",
|
|
344
|
+
'--input={"file_in": "' + str(sent_in_file) + '"}',
|
|
345
|
+
],
|
|
346
|
+
cwd=work_dir,
|
|
347
|
+
)
|
|
348
|
+
result = json.loads(result_json)
|
|
255
349
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
350
|
+
# Get all the file values in the job store.
|
|
351
|
+
all_file_values = set()
|
|
352
|
+
for directory, _, files in os.walk(
|
|
353
|
+
job_store
|
|
354
|
+
): # can't switch to job_store.walk() until Python 3.12 is the minimum version
|
|
355
|
+
for filename in files:
|
|
356
|
+
with (Path(directory) / filename).open(
|
|
357
|
+
encoding="utf-8", errors="replace"
|
|
358
|
+
) as f:
|
|
359
|
+
all_file_values.add(f.read().rstrip())
|
|
360
|
+
|
|
361
|
+
# Make sure the files with the right contents are in the job store and
|
|
362
|
+
# the files with the wrong contents aren't anymore.
|
|
363
|
+
#
|
|
364
|
+
# This assumes no top-level cleanup in the main driver script.
|
|
365
|
+
|
|
366
|
+
# These are all created inside the workflow and not output
|
|
367
|
+
assert (
|
|
368
|
+
"This file is imported by local path in the workflow"
|
|
369
|
+
not in all_file_values
|
|
370
|
+
)
|
|
371
|
+
assert "This file is consumed by a task call" not in all_file_values
|
|
372
|
+
assert (
|
|
373
|
+
"This file is created in a task inputs section" not in all_file_values
|
|
374
|
+
)
|
|
375
|
+
assert "This file is created in a runtime section" not in all_file_values
|
|
376
|
+
assert "This task file is not used" not in all_file_values
|
|
377
|
+
assert "This file should be discarded" not in all_file_values
|
|
378
|
+
assert "This file is dropped by a subworkflow" not in all_file_values
|
|
379
|
+
assert "This file gets stored in a variable" not in all_file_values
|
|
380
|
+
assert "This file never gets stored in a variable" not in all_file_values
|
|
381
|
+
|
|
382
|
+
# These are created inside the workflow and output
|
|
383
|
+
assert "3" in all_file_values
|
|
384
|
+
assert "This file is collected as a task output twice" in all_file_values
|
|
385
|
+
assert "This file should be kept" in all_file_values
|
|
386
|
+
assert "This file is kept by a subworkflow" in all_file_values
|
|
387
|
+
|
|
388
|
+
# These are sent into the workflow from the enclosing environment and
|
|
389
|
+
# should not be deleted.
|
|
390
|
+
assert "This file is sent in as input" in all_file_values
|
|
391
|
+
|
|
392
|
+
# Make sure we didn't somehow delete the file sent as input
|
|
393
|
+
assert sent_in_file.exists()
|
|
259
394
|
|
|
260
395
|
@needs_singularity_or_docker
|
|
261
|
-
def test_all_call_outputs(self):
|
|
396
|
+
def test_all_call_outputs(self, tmp_path: Path) -> None:
|
|
262
397
|
"""
|
|
263
398
|
Test if Toil can collect all call outputs from a workflow that doesn't expose them.
|
|
264
399
|
"""
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
result = json.loads(result_json)
|
|
400
|
+
with get_data("test/wdl/testfiles/not_enough_outputs.wdl") as wdl:
|
|
401
|
+
# With no flag we don't include the call outputs
|
|
402
|
+
result_json = subprocess.check_output(
|
|
403
|
+
self.base_command
|
|
404
|
+
+ [str(wdl), "-o", str(tmp_path), "--logInfo", "--retryCount=0"]
|
|
405
|
+
)
|
|
406
|
+
result = json.loads(result_json)
|
|
273
407
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
408
|
+
assert "wf.only_result" in result
|
|
409
|
+
assert "wf.do_math.square" not in result
|
|
410
|
+
assert "wf.do_math.cube" not in result
|
|
411
|
+
assert "wf.should_never_output" not in result
|
|
278
412
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
413
|
+
# With flag off we don't include the call outputs
|
|
414
|
+
result_json = subprocess.check_output(
|
|
415
|
+
self.base_command
|
|
416
|
+
+ [
|
|
417
|
+
str(wdl),
|
|
418
|
+
"-o",
|
|
419
|
+
str(tmp_path),
|
|
420
|
+
"--logInfo",
|
|
421
|
+
"--retryCount=0",
|
|
422
|
+
"--allCallOutputs=false",
|
|
423
|
+
]
|
|
424
|
+
)
|
|
425
|
+
result = json.loads(result_json)
|
|
292
426
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
427
|
+
assert "wf.only_result" in result
|
|
428
|
+
assert "wf.do_math.square" not in result
|
|
429
|
+
assert "wf.do_math.cube" not in result
|
|
430
|
+
assert "wf.should_never_output" not in result
|
|
297
431
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
432
|
+
# With flag on we do include the call outputs
|
|
433
|
+
result_json = subprocess.check_output(
|
|
434
|
+
self.base_command
|
|
435
|
+
+ [
|
|
436
|
+
str(wdl),
|
|
437
|
+
"-o",
|
|
438
|
+
str(tmp_path),
|
|
439
|
+
"--logInfo",
|
|
440
|
+
"--retryCount=0",
|
|
441
|
+
"--allCallOutputs=on",
|
|
442
|
+
]
|
|
443
|
+
)
|
|
444
|
+
result = json.loads(result_json)
|
|
311
445
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
446
|
+
assert "wf.only_result" in result
|
|
447
|
+
assert "wf.do_math.square" in result
|
|
448
|
+
assert "wf.do_math.cube" in result
|
|
449
|
+
assert "wf.should_never_output" not in result
|
|
316
450
|
|
|
317
451
|
@needs_singularity_or_docker
|
|
318
|
-
def test_croo_detection(self):
|
|
452
|
+
def test_croo_detection(self, tmp_path: Path) -> None:
|
|
319
453
|
"""
|
|
320
454
|
Test if Toil can detect and do something sensible with Cromwell Output Organizer workflows.
|
|
321
455
|
"""
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
result = json.loads(result_json)
|
|
456
|
+
with get_data("test/wdl/testfiles/croo.wdl") as wdl:
|
|
457
|
+
# With no flag we should include all task outputs
|
|
458
|
+
result_json = subprocess.check_output(
|
|
459
|
+
self.base_command
|
|
460
|
+
+ [str(wdl), "-o", str(tmp_path), "--logInfo", "--retryCount=0"]
|
|
461
|
+
)
|
|
462
|
+
result = json.loads(result_json)
|
|
330
463
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
464
|
+
assert "wf.only_result" in result
|
|
465
|
+
assert "wf.do_math.square" in result
|
|
466
|
+
assert "wf.do_math.cube" in result
|
|
467
|
+
assert "wf.should_never_output" not in result
|
|
335
468
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
469
|
+
# With flag off we obey the WDL spec even if we're suspicious
|
|
470
|
+
result_json = subprocess.check_output(
|
|
471
|
+
self.base_command
|
|
472
|
+
+ [
|
|
473
|
+
str(wdl),
|
|
474
|
+
"-o",
|
|
475
|
+
str(tmp_path),
|
|
476
|
+
"--logInfo",
|
|
477
|
+
"--retryCount=0",
|
|
478
|
+
"--allCallOutputs=off",
|
|
479
|
+
]
|
|
480
|
+
)
|
|
481
|
+
result = json.loads(result_json)
|
|
349
482
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
483
|
+
assert "wf.only_result" in result
|
|
484
|
+
assert "wf.do_math.square" not in result
|
|
485
|
+
assert "wf.do_math.cube" not in result
|
|
486
|
+
assert "wf.should_never_output" not in result
|
|
354
487
|
|
|
355
488
|
@needs_singularity_or_docker
|
|
356
|
-
def test_caching(self):
|
|
489
|
+
def test_caching(self, tmp_path: Path) -> None:
|
|
357
490
|
"""
|
|
358
491
|
Test if Toil can cache task runs.
|
|
359
492
|
"""
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
result_json = subprocess.check_output(
|
|
368
|
-
self.base_command + [wdl, '-o', self.output_dir, '--logInfo', '--retryCount=0', '--inputs={"random.task_1_input": 1, "random.task_2_input": 1}'],
|
|
369
|
-
env=caching_env)
|
|
370
|
-
result_initial = json.loads(result_json)
|
|
371
|
-
|
|
372
|
-
assert 'random.value_seen' in result_initial
|
|
373
|
-
assert 'random.value_written' in result_initial
|
|
374
|
-
|
|
375
|
-
result_json = subprocess.check_output(
|
|
376
|
-
self.base_command + [wdl, '-o', self.output_dir, '--logInfo', '--retryCount=0', '--inputs={"random.task_1_input": 1, "random.task_2_input": 1}'],
|
|
377
|
-
env=caching_env)
|
|
378
|
-
result_cached = json.loads(result_json)
|
|
493
|
+
with get_data("test/wdl/testfiles/random.wdl") as wdl:
|
|
494
|
+
cachedir = tmp_path / "cache"
|
|
495
|
+
cachedir.mkdir()
|
|
496
|
+
caching_env = dict(os.environ)
|
|
497
|
+
caching_env["MINIWDL__CALL_CACHE__GET"] = "true"
|
|
498
|
+
caching_env["MINIWDL__CALL_CACHE__PUT"] = "true"
|
|
499
|
+
caching_env["MINIWDL__CALL_CACHE__DIR"] = str(cachedir)
|
|
379
500
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
assert result_not_cached['random.value_seen'] != result_initial['random.value_seen']
|
|
395
|
-
assert result_not_cached['random.value_written'] != result_initial['random.value_written']
|
|
501
|
+
result_json = subprocess.check_output(
|
|
502
|
+
self.base_command
|
|
503
|
+
+ [
|
|
504
|
+
str(wdl),
|
|
505
|
+
"-o",
|
|
506
|
+
str(tmp_path / "out1"),
|
|
507
|
+
"--logInfo",
|
|
508
|
+
"--retryCount=0",
|
|
509
|
+
'--inputs={"random.task_1_input": 1, "random.task_2_input": 1}',
|
|
510
|
+
],
|
|
511
|
+
env=caching_env,
|
|
512
|
+
)
|
|
513
|
+
result_initial = json.loads(result_json)
|
|
396
514
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
env=caching_env)
|
|
400
|
-
result_part_cached = json.loads(result_json)
|
|
515
|
+
assert "random.value_seen" in result_initial
|
|
516
|
+
assert "random.value_written" in result_initial
|
|
401
517
|
|
|
402
|
-
|
|
403
|
-
|
|
518
|
+
result_json = subprocess.check_output(
|
|
519
|
+
self.base_command
|
|
520
|
+
+ [
|
|
521
|
+
str(wdl),
|
|
522
|
+
"-o",
|
|
523
|
+
str(tmp_path / "out2"),
|
|
524
|
+
"--logInfo",
|
|
525
|
+
"--retryCount=0",
|
|
526
|
+
'--inputs={"random.task_1_input": 1, "random.task_2_input": 1}',
|
|
527
|
+
],
|
|
528
|
+
env=caching_env,
|
|
529
|
+
)
|
|
530
|
+
result_cached = json.loads(result_json)
|
|
404
531
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
assert result_part_cached['random.value_written'] != result_not_cached['random.value_written']
|
|
532
|
+
assert "random.value_seen" in result_cached
|
|
533
|
+
assert "random.value_written" in result_cached
|
|
408
534
|
|
|
535
|
+
assert (
|
|
536
|
+
result_cached["random.value_seen"]
|
|
537
|
+
== result_initial["random.value_seen"]
|
|
538
|
+
)
|
|
539
|
+
assert (
|
|
540
|
+
result_cached["random.value_written"]
|
|
541
|
+
== result_initial["random.value_written"]
|
|
542
|
+
)
|
|
409
543
|
|
|
544
|
+
result_json = subprocess.check_output(
|
|
545
|
+
self.base_command
|
|
546
|
+
+ [
|
|
547
|
+
str(wdl),
|
|
548
|
+
"-o",
|
|
549
|
+
str(tmp_path / "out3"),
|
|
550
|
+
"--logInfo",
|
|
551
|
+
"--retryCount=0",
|
|
552
|
+
'--inputs={"random.task_1_input": 2, "random.task_2_input": 1}',
|
|
553
|
+
],
|
|
554
|
+
env=caching_env,
|
|
555
|
+
)
|
|
556
|
+
result_not_cached = json.loads(result_json)
|
|
410
557
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
Test if missing and error-producing URLs are handled correctly for optional File? values.
|
|
414
|
-
"""
|
|
415
|
-
wdl = os.path.abspath("src/toil/test/wdl/testfiles/url_to_optional_file.wdl")
|
|
558
|
+
assert "random.value_seen" in result_not_cached
|
|
559
|
+
assert "random.value_written" in result_not_cached
|
|
416
560
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
561
|
+
assert (
|
|
562
|
+
result_not_cached["random.value_seen"]
|
|
563
|
+
!= result_initial["random.value_seen"]
|
|
564
|
+
)
|
|
565
|
+
assert (
|
|
566
|
+
result_not_cached["random.value_written"]
|
|
567
|
+
!= result_initial["random.value_written"]
|
|
568
|
+
)
|
|
420
569
|
|
|
421
|
-
Return the parsed output.
|
|
422
|
-
"""
|
|
423
|
-
logger.info("Test optional file with HTTP code %s", code)
|
|
424
|
-
json_value = '{"url_to_optional_file.http_code": %d}' % code
|
|
425
570
|
result_json = subprocess.check_output(
|
|
426
571
|
self.base_command
|
|
427
572
|
+ [
|
|
428
|
-
wdl,
|
|
429
|
-
json_value,
|
|
573
|
+
str(wdl),
|
|
430
574
|
"-o",
|
|
431
|
-
|
|
575
|
+
str(tmp_path / "out4"),
|
|
432
576
|
"--logInfo",
|
|
433
577
|
"--retryCount=0",
|
|
434
|
-
|
|
578
|
+
'--inputs={"random.task_1_input": 1, "random.task_2_input": 2}',
|
|
579
|
+
],
|
|
580
|
+
env=caching_env,
|
|
435
581
|
)
|
|
436
|
-
|
|
437
|
-
return result
|
|
582
|
+
result_part_cached = json.loads(result_json)
|
|
438
583
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
assert "url_to_optional_file.out_file" in result
|
|
442
|
-
self.assertNotEqual(result["url_to_optional_file.out_file"], None)
|
|
584
|
+
assert "random.value_seen" in result_part_cached
|
|
585
|
+
assert "random.value_written" in result_part_cached
|
|
443
586
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
587
|
+
assert (
|
|
588
|
+
result_part_cached["random.value_seen"]
|
|
589
|
+
== result_initial["random.value_seen"]
|
|
590
|
+
)
|
|
591
|
+
assert (
|
|
592
|
+
result_part_cached["random.value_written"]
|
|
593
|
+
!= result_initial["random.value_written"]
|
|
594
|
+
)
|
|
595
|
+
assert (
|
|
596
|
+
result_part_cached["random.value_written"]
|
|
597
|
+
!= result_not_cached["random.value_written"]
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
def test_url_to_optional_file(self, tmp_path: Path) -> None:
|
|
601
|
+
"""
|
|
602
|
+
Test if missing and error-producing URLs are handled correctly for optional File? values.
|
|
603
|
+
"""
|
|
604
|
+
with get_data("test/wdl/testfiles/url_to_optional_file.wdl") as wdl:
|
|
605
|
+
|
|
606
|
+
def run_for_code(code: int) -> dict[str, Any]:
|
|
607
|
+
"""
|
|
608
|
+
Run a workflow coercing URL to File? where the URL returns the given status code.
|
|
609
|
+
|
|
610
|
+
Return the parsed output.
|
|
611
|
+
"""
|
|
612
|
+
logger.info("Test optional file with HTTP code %s", code)
|
|
613
|
+
json_value = '{"url_to_optional_file.http_code": %d}' % code
|
|
614
|
+
result_json = subprocess.check_output(
|
|
615
|
+
self.base_command
|
|
616
|
+
+ [
|
|
617
|
+
str(wdl),
|
|
618
|
+
json_value,
|
|
619
|
+
"-o",
|
|
620
|
+
str(tmp_path),
|
|
621
|
+
"--logInfo",
|
|
622
|
+
"--retryCount=0",
|
|
623
|
+
]
|
|
624
|
+
)
|
|
625
|
+
result = json.loads(result_json)
|
|
626
|
+
return cast(dict[str, Any], json.loads(result_json))
|
|
627
|
+
|
|
628
|
+
# Check files that exist
|
|
629
|
+
result = run_for_code(200)
|
|
447
630
|
assert "url_to_optional_file.out_file" in result
|
|
448
|
-
|
|
631
|
+
assert result["url_to_optional_file.out_file"] is not None
|
|
632
|
+
|
|
633
|
+
for code in (404, 410):
|
|
634
|
+
# Check files that definitely don't
|
|
635
|
+
result = run_for_code(code)
|
|
636
|
+
assert "url_to_optional_file.out_file" in result
|
|
637
|
+
assert result["url_to_optional_file.out_file"] is None
|
|
449
638
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
639
|
+
for code in (402, 418, 500, 502):
|
|
640
|
+
# Check that cases where the server refuses to say if the file
|
|
641
|
+
# exists stop the workflow.
|
|
642
|
+
with pytest.raises(subprocess.CalledProcessError):
|
|
643
|
+
run_for_code(code)
|
|
455
644
|
|
|
456
|
-
def test_missing_output_directory(self):
|
|
645
|
+
def test_missing_output_directory(self, tmp_path: Path) -> None:
|
|
457
646
|
"""
|
|
458
647
|
Test if Toil can run a WDL workflow into a new directory.
|
|
459
648
|
"""
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
649
|
+
with get_data("test/wdl/md5sum/md5sum.1.0.wdl") as wdl:
|
|
650
|
+
with get_data("test/wdl/md5sum/md5sum.json") as json_file:
|
|
651
|
+
subprocess.check_call(
|
|
652
|
+
self.base_command
|
|
653
|
+
+ [
|
|
654
|
+
str(wdl),
|
|
655
|
+
str(json_file),
|
|
656
|
+
"-o",
|
|
657
|
+
str(tmp_path / "does" / "not" / "exist"),
|
|
658
|
+
"--logDebug",
|
|
659
|
+
"--retryCount=0",
|
|
660
|
+
]
|
|
661
|
+
)
|
|
473
662
|
|
|
474
663
|
@needs_singularity_or_docker
|
|
475
|
-
def test_miniwdl_self_test(
|
|
664
|
+
def test_miniwdl_self_test(
|
|
665
|
+
self, tmp_path: Path, extra_args: Optional[list[str]] = None
|
|
666
|
+
) -> None:
|
|
476
667
|
"""Test if the MiniWDL self test runs and produces the expected output."""
|
|
477
|
-
|
|
478
|
-
|
|
668
|
+
with get_data("test/wdl/miniwdl_self_test/self_test.wdl") as wdl_file:
|
|
669
|
+
with get_data("test/wdl/miniwdl_self_test/inputs.json") as json_file:
|
|
670
|
+
|
|
671
|
+
result_json = subprocess.check_output(
|
|
672
|
+
self.base_command
|
|
673
|
+
+ [
|
|
674
|
+
str(wdl_file),
|
|
675
|
+
str(json_file),
|
|
676
|
+
"--logDebug",
|
|
677
|
+
"-o",
|
|
678
|
+
str(tmp_path),
|
|
679
|
+
"--outputDialect",
|
|
680
|
+
"miniwdl",
|
|
681
|
+
]
|
|
682
|
+
+ (extra_args or [])
|
|
683
|
+
)
|
|
684
|
+
result = json.loads(result_json)
|
|
685
|
+
|
|
686
|
+
# Expect MiniWDL-style output with a designated "dir"
|
|
687
|
+
|
|
688
|
+
assert "dir" in result
|
|
689
|
+
assert isinstance(result["dir"], str)
|
|
690
|
+
out_dir = result["dir"]
|
|
691
|
+
|
|
692
|
+
assert "outputs" in result
|
|
693
|
+
assert isinstance(result["outputs"], dict)
|
|
694
|
+
outputs = result["outputs"]
|
|
695
|
+
|
|
696
|
+
assert "hello_caller.message_files" in outputs
|
|
697
|
+
assert isinstance(outputs["hello_caller.message_files"], list)
|
|
698
|
+
assert len(outputs["hello_caller.message_files"]) == 2
|
|
699
|
+
for item in outputs["hello_caller.message_files"]:
|
|
700
|
+
# All the files should be strings in the "out" directory
|
|
701
|
+
assert isinstance(item, str), "File output must be a string"
|
|
702
|
+
assert item.startswith(
|
|
703
|
+
out_dir
|
|
704
|
+
), "File output must be in the output directory"
|
|
705
|
+
|
|
706
|
+
# Look at the filename within that directory
|
|
707
|
+
name_in_out_dir = item[len(out_dir) :]
|
|
708
|
+
|
|
709
|
+
# Ity should contain the job name of "hello", so they are human-readable.
|
|
710
|
+
assert (
|
|
711
|
+
"hello" in name_in_out_dir
|
|
712
|
+
), f"File output {name_in_out_dir} should have the originating task name in it"
|
|
713
|
+
|
|
714
|
+
# And it should not contain non-human-readable content.
|
|
715
|
+
#
|
|
716
|
+
# We use a threshold number of digits as a proxy for this, but
|
|
717
|
+
# don't try and get around this by just rolling other random
|
|
718
|
+
# strings; we want these outputs to be human-readable!!!
|
|
719
|
+
digit_count = len(
|
|
720
|
+
[c for c in name_in_out_dir if c in string.digits]
|
|
721
|
+
)
|
|
722
|
+
assert (
|
|
723
|
+
digit_count < 3
|
|
724
|
+
), f"File output {name_in_out_dir} has {digit_count} digits, which is too many to be plausibly human-readable"
|
|
725
|
+
|
|
726
|
+
assert "hello_caller.messages" in outputs
|
|
727
|
+
assert outputs["hello_caller.messages"] == [
|
|
728
|
+
"Hello, Alyssa P. Hacker!",
|
|
729
|
+
"Hello, Ben Bitdiddle!",
|
|
730
|
+
]
|
|
731
|
+
|
|
732
|
+
@needs_singularity_or_docker
|
|
733
|
+
def test_miniwdl_self_test_by_reference(self, tmp_path: Path) -> None:
|
|
734
|
+
"""
|
|
735
|
+
Test if the MiniWDL self test works when passing input files by URL reference.
|
|
736
|
+
"""
|
|
737
|
+
self.test_miniwdl_self_test(
|
|
738
|
+
tmp_path=tmp_path, extra_args=["--referenceInputs=True"]
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
@pytest.mark.integrative
|
|
742
|
+
@needs_singularity_or_docker
|
|
743
|
+
def test_dockstore_trs(
|
|
744
|
+
self, tmp_path: Path, extra_args: Optional[list[str]] = None
|
|
745
|
+
) -> None:
|
|
746
|
+
wdl_file = "#workflow/github.com/dockstore/bcc2020-training/HelloWorld:master"
|
|
747
|
+
# Needs an input but doesn't provide a good one.
|
|
748
|
+
json_input = json.dumps(
|
|
749
|
+
{
|
|
750
|
+
"hello_world.hello.myName": "https://raw.githubusercontent.com/dockstore/bcc2020-training/refs/heads/master/wdl-training/exercise1/name.txt"
|
|
751
|
+
}
|
|
752
|
+
)
|
|
479
753
|
|
|
480
754
|
result_json = subprocess.check_output(
|
|
481
755
|
self.base_command
|
|
482
756
|
+ [
|
|
483
757
|
wdl_file,
|
|
484
|
-
|
|
758
|
+
json_input,
|
|
485
759
|
"--logDebug",
|
|
486
760
|
"-o",
|
|
487
|
-
|
|
761
|
+
str(tmp_path),
|
|
488
762
|
"--outputDialect",
|
|
489
763
|
"miniwdl",
|
|
490
764
|
]
|
|
@@ -492,89 +766,67 @@ class WDLTests(BaseWDLTest):
|
|
|
492
766
|
)
|
|
493
767
|
result = json.loads(result_json)
|
|
494
768
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
assert "dir" in result
|
|
498
|
-
assert isinstance(result["dir"], str)
|
|
499
|
-
out_dir = result["dir"]
|
|
500
|
-
|
|
501
|
-
assert "outputs" in result
|
|
502
|
-
assert isinstance(result["outputs"], dict)
|
|
503
|
-
outputs = result["outputs"]
|
|
504
|
-
|
|
505
|
-
assert "hello_caller.message_files" in outputs
|
|
506
|
-
assert isinstance(outputs["hello_caller.message_files"], list)
|
|
507
|
-
assert len(outputs["hello_caller.message_files"]) == 2
|
|
508
|
-
for item in outputs["hello_caller.message_files"]:
|
|
509
|
-
# All the files should be strings in the "out" directory
|
|
510
|
-
assert isinstance(item, str), "File output must be a string"
|
|
511
|
-
assert item.startswith(
|
|
512
|
-
out_dir
|
|
513
|
-
), "File output must be in the output directory"
|
|
514
|
-
|
|
515
|
-
# Look at the filename within that directory
|
|
516
|
-
name_in_out_dir = item[len(out_dir) :]
|
|
517
|
-
|
|
518
|
-
# Ity should contain the job name of "hello", so they are human-readable.
|
|
519
|
-
assert (
|
|
520
|
-
"hello" in name_in_out_dir
|
|
521
|
-
), f"File output {name_in_out_dir} should have the originating task name in it"
|
|
522
|
-
|
|
523
|
-
# And it should not contain non-human-readable content.
|
|
524
|
-
#
|
|
525
|
-
# We use a threshold number of digits as a proxy for this, but
|
|
526
|
-
# don't try and get around this by just rolling other random
|
|
527
|
-
# strings; we want these outputs to be human-readable!!!
|
|
528
|
-
digit_count = len([c for c in name_in_out_dir if c in string.digits])
|
|
529
|
-
assert (
|
|
530
|
-
digit_count < 3
|
|
531
|
-
), f"File output {name_in_out_dir} has {digit_count} digits, which is too many to be plausibly human-readable"
|
|
769
|
+
with open(result.get("outputs", {}).get("hello_world.helloFile")) as f:
|
|
770
|
+
result_text = f.read().strip()
|
|
532
771
|
|
|
533
|
-
assert "
|
|
534
|
-
assert outputs["hello_caller.messages"] == [
|
|
535
|
-
"Hello, Alyssa P. Hacker!",
|
|
536
|
-
"Hello, Ben Bitdiddle!",
|
|
537
|
-
]
|
|
538
|
-
|
|
539
|
-
@needs_singularity_or_docker
|
|
540
|
-
def test_miniwdl_self_test_by_reference(self) -> None:
|
|
541
|
-
"""
|
|
542
|
-
Test if the MiniWDL self test works when passing input files by URL reference.
|
|
543
|
-
"""
|
|
544
|
-
self.test_miniwdl_self_test(extra_args=["--referenceInputs=True"])
|
|
772
|
+
assert result_text == "Hello World!\nMy name is potato."
|
|
545
773
|
|
|
774
|
+
# TODO: Should this move to the TRS/Dockstore tests file?
|
|
546
775
|
@pytest.mark.integrative
|
|
547
776
|
@needs_singularity_or_docker
|
|
548
|
-
def
|
|
777
|
+
def test_dockstore_metrics_publication(
|
|
778
|
+
self, tmp_path: Path, extra_args: Optional[list[str]] = None
|
|
779
|
+
) -> None:
|
|
549
780
|
wdl_file = "#workflow/github.com/dockstore/bcc2020-training/HelloWorld:master"
|
|
550
781
|
# Needs an input but doesn't provide a good one.
|
|
551
|
-
json_input = json.dumps(
|
|
782
|
+
json_input = json.dumps(
|
|
783
|
+
{
|
|
784
|
+
"hello_world.hello.myName": "https://raw.githubusercontent.com/dockstore/bcc2020-training/refs/heads/master/wdl-training/exercise1/name.txt"
|
|
785
|
+
}
|
|
786
|
+
)
|
|
552
787
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
788
|
+
env = dict(os.environ)
|
|
789
|
+
# Set credentials we got permission to publish from the Dockstore team,
|
|
790
|
+
# and work on the staging Dockstore.
|
|
791
|
+
env["TOIL_TRS_ROOT"] = "https://staging.dockstore.org"
|
|
792
|
+
env["TOIL_DOCKSTORE_TOKEN"] = "99cf5578ebe94b194d7864630a86258fa3d6cedcc17d757b5dd49e64ee3b68c3"
|
|
793
|
+
# Enable history for when <https://github.com/DataBiosphere/toil/pull/5258> merges
|
|
794
|
+
env["TOIL_HISTORY"] = "True"
|
|
557
795
|
|
|
558
|
-
|
|
559
|
-
|
|
796
|
+
output_log = subprocess.check_output(
|
|
797
|
+
self.base_command
|
|
798
|
+
+ [
|
|
799
|
+
wdl_file,
|
|
800
|
+
json_input,
|
|
801
|
+
"--logDebug",
|
|
802
|
+
"-o",
|
|
803
|
+
str(tmp_path),
|
|
804
|
+
"--outputDialect",
|
|
805
|
+
"miniwdl",
|
|
806
|
+
"--publishWorkflowMetrics=current",
|
|
807
|
+
]
|
|
808
|
+
+ (extra_args or []),
|
|
809
|
+
stderr=subprocess.STDOUT,
|
|
810
|
+
env=env,
|
|
811
|
+
)
|
|
560
812
|
|
|
561
|
-
|
|
813
|
+
assert b'Workflow metrics were accepted by Dockstore.' in output_log
|
|
562
814
|
|
|
563
815
|
@slow
|
|
564
816
|
@needs_docker_cuda
|
|
565
|
-
def test_giraffe_deepvariant(self):
|
|
817
|
+
def test_giraffe_deepvariant(self, tmp_path: Path) -> None:
|
|
566
818
|
"""Test if Giraffe and GPU DeepVariant run. This could take 25 minutes."""
|
|
567
819
|
# TODO: enable test if nvidia-container-runtime and Singularity are installed but Docker isn't.
|
|
568
820
|
|
|
569
|
-
json_dir =
|
|
821
|
+
json_dir = tmp_path / "json"
|
|
822
|
+
json_dir.mkdir()
|
|
570
823
|
base_uri = "https://raw.githubusercontent.com/vgteam/vg_wdl/65dd739aae765f5c4dedd14f2e42d5a263f9267a"
|
|
571
824
|
|
|
572
825
|
wdl_file = f"{base_uri}/workflows/giraffe_and_deepvariant.wdl"
|
|
573
|
-
json_file =
|
|
574
|
-
with open(
|
|
826
|
+
json_file = json_dir / "inputs.json"
|
|
827
|
+
with json_file.open("w") as fp:
|
|
575
828
|
# Write some inputs. We need to override the example inputs to use a GPU container, but that means we need absolute input URLs.
|
|
576
829
|
json.dump(
|
|
577
|
-
fp,
|
|
578
830
|
{
|
|
579
831
|
"GiraffeDeepVariant.INPUT_READ_FILE_1": f"{base_uri}/tests/small_sim_graph/reads_1.fastq.gz",
|
|
580
832
|
"GiraffeDeepVariant.INPUT_READ_FILE_2": f"{base_uri}/tests/small_sim_graph/reads_2.fastq.gz",
|
|
@@ -587,11 +839,19 @@ class WDLTests(BaseWDLTest):
|
|
|
587
839
|
"GiraffeDeepVariant.OUTPUT_GAF": True,
|
|
588
840
|
"GiraffeDeepVariant.runDeepVariantCallVariants.in_dv_gpu_container": "google/deepvariant:1.3.0-gpu",
|
|
589
841
|
},
|
|
842
|
+
fp,
|
|
590
843
|
)
|
|
591
844
|
|
|
592
845
|
result_json = subprocess.check_output(
|
|
593
846
|
self.base_command
|
|
594
|
-
+ [
|
|
847
|
+
+ [
|
|
848
|
+
wdl_file,
|
|
849
|
+
json_file,
|
|
850
|
+
"-o",
|
|
851
|
+
str(tmp_path / "out"),
|
|
852
|
+
"--outputDialect",
|
|
853
|
+
"miniwdl",
|
|
854
|
+
]
|
|
595
855
|
)
|
|
596
856
|
result = json.loads(result_json)
|
|
597
857
|
|
|
@@ -611,13 +871,12 @@ class WDLTests(BaseWDLTest):
|
|
|
611
871
|
|
|
612
872
|
@slow
|
|
613
873
|
@needs_singularity_or_docker
|
|
614
|
-
def test_giraffe(self):
|
|
874
|
+
def test_giraffe(self, tmp_path: Path) -> None:
|
|
615
875
|
"""Test if Giraffe runs. This could take 12 minutes. Also we scale it down but it still demands lots of memory."""
|
|
616
876
|
# TODO: enable test if nvidia-container-runtime and Singularity are installed but Docker isn't.
|
|
617
877
|
# TODO: Reduce memory requests with custom/smaller inputs.
|
|
618
878
|
# TODO: Skip if node lacks enough memory.
|
|
619
879
|
|
|
620
|
-
json_dir = self._createTempDir()
|
|
621
880
|
base_uri = "https://raw.githubusercontent.com/vgteam/vg_wdl/65dd739aae765f5c4dedd14f2e42d5a263f9267a"
|
|
622
881
|
wdl_file = f"{base_uri}/workflows/giraffe.wdl"
|
|
623
882
|
json_file = f"{base_uri}/params/giraffe.json"
|
|
@@ -628,7 +887,7 @@ class WDLTests(BaseWDLTest):
|
|
|
628
887
|
wdl_file,
|
|
629
888
|
json_file,
|
|
630
889
|
"-o",
|
|
631
|
-
|
|
890
|
+
str(tmp_path),
|
|
632
891
|
"--outputDialect",
|
|
633
892
|
"miniwdl",
|
|
634
893
|
"--scale",
|
|
@@ -654,26 +913,43 @@ class WDLTests(BaseWDLTest):
|
|
|
654
913
|
|
|
655
914
|
@needs_singularity_or_docker
|
|
656
915
|
@needs_google_storage
|
|
657
|
-
def test_gs_uri(self):
|
|
916
|
+
def test_gs_uri(self, tmp_path: Path) -> None:
|
|
658
917
|
"""Test if Toil can access Google Storage URIs."""
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
918
|
+
with get_data("test/wdl/md5sum/md5sum.1.0.wdl") as wdl:
|
|
919
|
+
with get_data("test/wdl/md5sum/md5sum-gs.json") as json_file:
|
|
920
|
+
result_json = subprocess.check_output(
|
|
921
|
+
self.base_command
|
|
922
|
+
+ [str(wdl), str(json_file), "-o", str(tmp_path), "--logDebug"]
|
|
923
|
+
)
|
|
924
|
+
result = json.loads(result_json)
|
|
925
|
+
|
|
926
|
+
assert "ga4ghMd5.value" in result
|
|
927
|
+
assert isinstance(result["ga4ghMd5.value"], str)
|
|
928
|
+
assert os.path.exists(result["ga4ghMd5.value"])
|
|
929
|
+
assert os.path.basename(result["ga4ghMd5.value"]) == "md5sum.txt"
|
|
930
|
+
|
|
931
|
+
def test_check(self, tmp_path: Path) -> None:
|
|
932
|
+
"""Test that Toil's lint check works"""
|
|
933
|
+
with get_data("test/wdl/lint_error.wdl") as wdl:
|
|
934
|
+
out = subprocess.check_output(
|
|
935
|
+
self.base_command + [str(wdl), "-o", str(tmp_path), "--logInfo"], stderr=subprocess.STDOUT)
|
|
936
|
+
|
|
937
|
+
assert b'UnnecessaryQuantifier' in out
|
|
938
|
+
|
|
939
|
+
p = subprocess.Popen(
|
|
940
|
+
self.base_command + [wdl, "--strict=True", "--logCritical"], stderr=subprocess.PIPE)
|
|
941
|
+
# Not actually a test assert; we need this to teach MyPy that we
|
|
942
|
+
# get an stderr when we pass stderr=subprocess.PIPE.
|
|
943
|
+
assert p.stderr is not None
|
|
944
|
+
stderr = p.stderr.read()
|
|
945
|
+
p.wait()
|
|
946
|
+
assert p.returncode == 2
|
|
947
|
+
assert b'Workflow did not pass linting in strict mode' in stderr
|
|
948
|
+
|
|
949
|
+
class TestWDLToilBench(unittest.TestCase):
|
|
674
950
|
"""Tests for Toil's MiniWDL-based implementation that don't run workflows."""
|
|
675
951
|
|
|
676
|
-
def test_coalesce(self):
|
|
952
|
+
def test_coalesce(self) -> None:
|
|
677
953
|
"""
|
|
678
954
|
Test if WDLSectionJob can coalesce WDL decls.
|
|
679
955
|
|
|
@@ -792,7 +1068,7 @@ class WDLToilBenchTests(ToilTest):
|
|
|
792
1068
|
|
|
793
1069
|
return WDL.Expr.String(pos, parts)
|
|
794
1070
|
|
|
795
|
-
def test_remove_common_leading_whitespace(self):
|
|
1071
|
+
def test_remove_common_leading_whitespace(self) -> None:
|
|
796
1072
|
"""
|
|
797
1073
|
Make sure leading whitespace removal works properly.
|
|
798
1074
|
"""
|
|
@@ -907,7 +1183,7 @@ class WDLToilBenchTests(ToilTest):
|
|
|
907
1183
|
trimmed = remove_common_leading_whitespace(expr)
|
|
908
1184
|
assert trimmed.command == True
|
|
909
1185
|
|
|
910
|
-
def test_choose_human_readable_directory(self):
|
|
1186
|
+
def test_choose_human_readable_directory(self) -> None:
|
|
911
1187
|
"""
|
|
912
1188
|
Test to make sure that we pick sensible but non-colliding directories to put files in.
|
|
913
1189
|
"""
|
|
@@ -931,18 +1207,18 @@ class WDLToilBenchTests(ToilTest):
|
|
|
931
1207
|
same_id = choose_human_readable_directory(
|
|
932
1208
|
"root", "taskname", "111-222-333", state
|
|
933
1209
|
)
|
|
934
|
-
|
|
1210
|
+
assert same_id == first_chosen
|
|
935
1211
|
|
|
936
1212
|
# If we use a different ID we should get a different result still obeying the constraints
|
|
937
1213
|
diff_id = choose_human_readable_directory(
|
|
938
1214
|
"root", "taskname", "222-333-444", state
|
|
939
1215
|
)
|
|
940
|
-
|
|
1216
|
+
assert diff_id != first_chosen
|
|
941
1217
|
assert diff_id.startswith("root")
|
|
942
1218
|
assert "taskname" in diff_id
|
|
943
1219
|
assert "222-333-444" not in diff_id
|
|
944
1220
|
|
|
945
|
-
def test_uri_packing(self):
|
|
1221
|
+
def test_uri_packing(self) -> None:
|
|
946
1222
|
"""
|
|
947
1223
|
Test to make sure Toil URI packing brings through the required information.
|
|
948
1224
|
"""
|
|
@@ -960,53 +1236,50 @@ class WDLToilBenchTests(ToilTest):
|
|
|
960
1236
|
unpacked = unpack_toil_uri(uri)
|
|
961
1237
|
|
|
962
1238
|
# Make sure we got what we put in
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1239
|
+
assert unpacked[0] == file_id
|
|
1240
|
+
assert unpacked[0].size == file_id.size
|
|
1241
|
+
assert unpacked[0].executable == file_id.executable
|
|
966
1242
|
|
|
967
|
-
|
|
1243
|
+
assert unpacked[1] == task_path
|
|
968
1244
|
|
|
969
1245
|
# TODO: We don't make the UUIDs back into UUID objects
|
|
970
|
-
|
|
1246
|
+
assert unpacked[2] == str(dir_id)
|
|
971
1247
|
|
|
972
|
-
|
|
1248
|
+
assert unpacked[3] == file_basename
|
|
973
1249
|
|
|
974
|
-
def test_disk_parse(self):
|
|
1250
|
+
def test_disk_parse(self) -> None:
|
|
975
1251
|
"""
|
|
976
1252
|
Test to make sure the disk parsing is correct
|
|
977
1253
|
"""
|
|
978
1254
|
# Test cromwell compatibility
|
|
979
1255
|
spec = "local-disk 5 SSD"
|
|
980
1256
|
specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1257
|
+
assert specified_mount_point is None
|
|
1258
|
+
assert part_size == 5
|
|
1259
|
+
assert part_suffix == "GB"
|
|
984
1260
|
|
|
985
1261
|
# Test spec conformance
|
|
986
1262
|
# https://github.com/openwdl/wdl/blob/e43e042104b728df1f1ad6e6145945d2b32331a6/SPEC.md?plain=1#L5072-L5082
|
|
987
1263
|
spec = "10"
|
|
988
1264
|
specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1265
|
+
assert specified_mount_point is None
|
|
1266
|
+
assert part_size == 10
|
|
1267
|
+
assert part_suffix == "GiB" # WDL spec default
|
|
992
1268
|
|
|
993
1269
|
spec = "1 MB"
|
|
994
1270
|
specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1271
|
+
assert specified_mount_point is None
|
|
1272
|
+
assert part_size == 1
|
|
1273
|
+
assert part_suffix == "MB"
|
|
998
1274
|
|
|
999
1275
|
spec = "MOUNT_POINT 3"
|
|
1000
1276
|
specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1277
|
+
assert specified_mount_point == "MOUNT_POINT"
|
|
1278
|
+
assert part_size == 3
|
|
1279
|
+
assert part_suffix == "GiB"
|
|
1004
1280
|
|
|
1005
1281
|
spec = "MOUNT_POINT 2 MB"
|
|
1006
1282
|
specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
if __name__ == "__main__":
|
|
1012
|
-
unittest.main() # run all tests
|
|
1283
|
+
assert specified_mount_point == "MOUNT_POINT"
|
|
1284
|
+
assert part_size == 2
|
|
1285
|
+
assert part_suffix == "MB"
|