toil 8.1.0b1__py3-none-any.whl → 9.0.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/registry.py +15 -118
- toil/batchSystems/singleMachine.py +1 -1
- toil/batchSystems/slurm.py +27 -26
- toil/bus.py +5 -3
- toil/common.py +59 -12
- toil/cwl/cwltoil.py +81 -38
- toil/cwl/utils.py +103 -3
- toil/job.py +64 -49
- toil/jobStores/abstractJobStore.py +35 -239
- toil/jobStores/aws/jobStore.py +2 -1
- toil/jobStores/fileJobStore.py +27 -2
- toil/jobStores/googleJobStore.py +110 -33
- toil/leader.py +9 -0
- toil/lib/accelerators.py +4 -2
- 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/generatedEC2Lists.py +81 -19
- toil/lib/history.py +87 -13
- toil/lib/history_submission.py +23 -9
- toil/lib/io.py +34 -22
- toil/lib/misc.py +8 -2
- toil/lib/plugins.py +106 -0
- toil/lib/resources.py +2 -1
- toil/lib/threading.py +11 -10
- toil/lib/url.py +320 -0
- toil/options/common.py +8 -0
- toil/options/cwl.py +13 -1
- toil/options/runner.py +17 -10
- toil/options/wdl.py +22 -0
- toil/provisioners/aws/awsProvisioner.py +25 -2
- toil/server/api_spec/LICENSE +201 -0
- toil/server/api_spec/README.rst +5 -0
- toil/server/app.py +12 -6
- toil/server/cli/wes_cwl_runner.py +3 -2
- toil/server/wes/abstract_backend.py +21 -43
- toil/server/wes/toil_backend.py +2 -2
- toil/test/__init__.py +275 -115
- toil/test/batchSystems/batchSystemTest.py +228 -213
- toil/test/batchSystems/batch_system_plugin_test.py +7 -0
- 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 +1175 -870
- 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/docs/scriptsTest.py +2 -1
- 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/lib/test_url.py +69 -0
- toil/test/lib/url_plugin_test.py +105 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +13 -10
- toil/test/provisioners/clusterTest.py +17 -4
- toil/test/provisioners/gceProvisionerTest.py +17 -15
- toil/test/server/serverTest.py +78 -36
- 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/read_file.wdl +18 -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 +14 -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 +751 -529
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/utils/toilSshCluster.py +23 -0
- toil/utils/toilUpdateEC2Instances.py +1 -0
- toil/version.py +5 -5
- toil/wdl/wdltoil.py +518 -437
- toil/worker.py +11 -6
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/METADATA +25 -24
- toil-9.0.0.dist-info/RECORD +444 -0
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/WHEEL +1 -1
- toil-8.1.0b1.dist-info/RECORD +0 -259
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/entry_points.txt +0 -0
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info/licenses}/LICENSE +0 -0
- {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/top_level.txt +0 -0
toil/test/cwl/cwlTest.py
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
|
+
from collections.abc import Generator
|
|
15
16
|
import json
|
|
16
17
|
import logging
|
|
17
18
|
import os
|
|
@@ -20,7 +21,6 @@ import shutil
|
|
|
20
21
|
import stat
|
|
21
22
|
import subprocess
|
|
22
23
|
import sys
|
|
23
|
-
import unittest
|
|
24
24
|
import uuid
|
|
25
25
|
import zipfile
|
|
26
26
|
from functools import partial
|
|
@@ -45,27 +45,30 @@ from toil.cwl.utils import (
|
|
|
45
45
|
download_structure,
|
|
46
46
|
visit_cwl_class_and_reduce,
|
|
47
47
|
visit_top_cwl_class,
|
|
48
|
+
remove_redundant_mounts
|
|
48
49
|
)
|
|
49
50
|
from toil.fileStores import FileID
|
|
50
51
|
from toil.fileStores.abstractFileStore import AbstractFileStore
|
|
52
|
+
from toil.job import WorkerImportJob
|
|
51
53
|
from toil.lib.threading import cpu_count
|
|
52
54
|
from toil.test import (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
get_data,
|
|
56
|
+
)
|
|
57
|
+
from toil.test import (
|
|
58
|
+
pslow as slow,
|
|
59
|
+
pneeds_docker as needs_docker,
|
|
60
|
+
pneeds_cwl as needs_cwl,
|
|
61
|
+
pneeds_aws_s3 as needs_aws_s3,
|
|
62
|
+
pneeds_docker_cuda as needs_docker_cuda,
|
|
63
|
+
pneeds_gridengine as needs_gridengine,
|
|
64
|
+
pneeds_kubernetes as needs_kubernetes,
|
|
65
|
+
pneeds_local_cuda as needs_local_cuda,
|
|
66
|
+
pneeds_lsf as needs_lsf,
|
|
67
|
+
pneeds_mesos as needs_mesos,
|
|
68
|
+
pneeds_online as needs_online,
|
|
69
|
+
pneeds_slurm as needs_slurm,
|
|
70
|
+
pneeds_torque as needs_torque,
|
|
71
|
+
pneeds_wes_server as needs_wes_server,
|
|
69
72
|
)
|
|
70
73
|
|
|
71
74
|
log = logging.getLogger(__name__)
|
|
@@ -222,72 +225,58 @@ def run_conformance_tests(
|
|
|
222
225
|
log.info("Unsuccessful return code is OK")
|
|
223
226
|
|
|
224
227
|
|
|
225
|
-
TesterFuncType = Callable[[
|
|
228
|
+
TesterFuncType = Callable[[Path, Path, "CWLObjectType", Path], None]
|
|
226
229
|
|
|
227
230
|
|
|
228
231
|
@needs_cwl
|
|
229
|
-
|
|
232
|
+
@pytest.mark.cwl
|
|
233
|
+
class TestCWLWorkflow:
|
|
230
234
|
"""
|
|
231
235
|
CWL tests included in Toil that don't involve the whole CWL conformance
|
|
232
236
|
test suite. Tests Toil-specific functions like URL types supported for
|
|
233
237
|
inputs.
|
|
234
238
|
"""
|
|
235
239
|
|
|
236
|
-
def setUp(self) -> None:
|
|
237
|
-
"""Runs anew before each test to create farm fresh temp dirs."""
|
|
238
|
-
self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
|
|
239
|
-
os.makedirs(self.outDir)
|
|
240
|
-
self.rootDir = self._projectRootPath()
|
|
241
|
-
self.jobStoreDir = f"./jobstore-{str(uuid.uuid4())}"
|
|
242
|
-
|
|
243
|
-
def tearDown(self) -> None:
|
|
244
|
-
"""Clean up outputs."""
|
|
245
|
-
if os.path.exists(self.outDir):
|
|
246
|
-
shutil.rmtree(self.outDir)
|
|
247
|
-
if os.path.exists(self.jobStoreDir):
|
|
248
|
-
shutil.rmtree(self.jobStoreDir)
|
|
249
|
-
unittest.TestCase.tearDown(self)
|
|
250
|
-
|
|
251
240
|
def test_cwl_cmdline_input(self) -> None:
|
|
252
241
|
"""
|
|
253
242
|
Test that running a CWL workflow with inputs specified on the command line passes.
|
|
254
243
|
"""
|
|
255
244
|
from toil.cwl import cwltoil
|
|
256
245
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
246
|
+
with get_data("test/cwl/conditional_wf.cwl") as cwlfile:
|
|
247
|
+
args = [str(cwlfile), "--message", "str", "--sleep", "2"]
|
|
248
|
+
st = StringIO()
|
|
249
|
+
# If the workflow runs, it must have had options
|
|
250
|
+
cwltoil.main(args, stdout=st)
|
|
262
251
|
|
|
263
252
|
def _tester(
|
|
264
253
|
self,
|
|
265
|
-
cwlfile:
|
|
266
|
-
jobfile:
|
|
254
|
+
cwlfile: Path,
|
|
255
|
+
jobfile: Path,
|
|
267
256
|
expect: "CWLObjectType",
|
|
268
|
-
|
|
257
|
+
outdir: Path,
|
|
269
258
|
out_name: str = "output",
|
|
270
|
-
|
|
259
|
+
main_args: Optional[list[str]] = None,
|
|
271
260
|
) -> None:
|
|
272
261
|
from toil.cwl import cwltoil
|
|
273
262
|
|
|
274
263
|
st = StringIO()
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
# Don't just dump output in the working directory.
|
|
278
|
-
main_args.extend(["--logDebug", "--outdir", self.outDir])
|
|
279
|
-
main_args.extend(
|
|
264
|
+
real_main_args = main_args or []
|
|
265
|
+
real_main_args.extend(
|
|
280
266
|
[
|
|
281
|
-
|
|
282
|
-
|
|
267
|
+
"--logDebug",
|
|
268
|
+
"--outdir",
|
|
269
|
+
str(outdir),
|
|
270
|
+
str(cwlfile),
|
|
271
|
+
str(jobfile),
|
|
283
272
|
]
|
|
284
273
|
)
|
|
285
|
-
cwltoil.main(
|
|
274
|
+
cwltoil.main(real_main_args, stdout=st)
|
|
286
275
|
out = json.loads(st.getvalue())
|
|
287
276
|
out.get(out_name, {}).pop("http://commonwl.org/cwltool#generation", None)
|
|
288
277
|
out.get(out_name, {}).pop("nameext", None)
|
|
289
278
|
out.get(out_name, {}).pop("nameroot", None)
|
|
290
|
-
|
|
279
|
+
assert out == expect
|
|
291
280
|
|
|
292
281
|
for k, v in expect.items():
|
|
293
282
|
if (
|
|
@@ -298,11 +287,11 @@ class CWLWorkflowTest(ToilTest):
|
|
|
298
287
|
):
|
|
299
288
|
# This is a top-level output file.
|
|
300
289
|
# None of our output files should be executable.
|
|
301
|
-
|
|
302
|
-
|
|
290
|
+
assert os.path.exists(v["path"]) is True
|
|
291
|
+
assert (os.stat(v["path"]).st_mode & stat.S_IXUSR) == 0
|
|
303
292
|
|
|
304
293
|
def _debug_worker_tester(
|
|
305
|
-
self, cwlfile:
|
|
294
|
+
self, cwlfile: Path, jobfile: Path, expect: "CWLObjectType", outdir: Path
|
|
306
295
|
) -> None:
|
|
307
296
|
from toil.cwl import cwltoil
|
|
308
297
|
|
|
@@ -311,9 +300,9 @@ class CWLWorkflowTest(ToilTest):
|
|
|
311
300
|
[
|
|
312
301
|
"--debugWorker",
|
|
313
302
|
"--outdir",
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
303
|
+
str(outdir),
|
|
304
|
+
str(cwlfile),
|
|
305
|
+
str(jobfile),
|
|
317
306
|
],
|
|
318
307
|
stdout=st,
|
|
319
308
|
)
|
|
@@ -321,172 +310,216 @@ class CWLWorkflowTest(ToilTest):
|
|
|
321
310
|
out["output"].pop("http://commonwl.org/cwltool#generation", None)
|
|
322
311
|
out["output"].pop("nameext", None)
|
|
323
312
|
out["output"].pop("nameroot", None)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
def revsort(self, cwl_filename: str, tester_fn: TesterFuncType) -> None:
|
|
327
|
-
tester_fn(
|
|
328
|
-
"src/toil/test/cwl/" + cwl_filename,
|
|
329
|
-
"src/toil/test/cwl/revsort-job.json",
|
|
330
|
-
self._expected_revsort_output(self.outDir),
|
|
331
|
-
)
|
|
313
|
+
assert out == expect
|
|
332
314
|
|
|
333
|
-
def
|
|
334
|
-
tester_fn
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
315
|
+
def revsort(
|
|
316
|
+
self, cwl_filename: str, tester_fn: TesterFuncType, out_dir: Path
|
|
317
|
+
) -> None:
|
|
318
|
+
with get_data(f"test/cwl/{cwl_filename}") as cwl_file:
|
|
319
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
320
|
+
tester_fn(
|
|
321
|
+
cwl_file, job_file, self._expected_revsort_output(out_dir), out_dir
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def revsort_no_checksum(
|
|
325
|
+
self, cwl_filename: str, tester_fn: TesterFuncType, out_dir: Path
|
|
326
|
+
) -> None:
|
|
327
|
+
with get_data(f"test/cwl/{cwl_filename}") as cwl_file:
|
|
328
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
329
|
+
tester_fn(
|
|
330
|
+
cwl_file,
|
|
331
|
+
job_file,
|
|
332
|
+
self._expected_revsort_nochecksum_output(out_dir),
|
|
333
|
+
out_dir,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def download(self, inputs: str, tester_fn: TesterFuncType, out_dir: Path) -> None:
|
|
337
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
338
|
+
with get_data("test/cwl/download.cwl") as cwl_file:
|
|
339
|
+
tester_fn(
|
|
340
|
+
cwl_file,
|
|
341
|
+
input_location,
|
|
342
|
+
self._expected_download_output(out_dir),
|
|
343
|
+
out_dir,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def load_contents(
|
|
347
|
+
self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
|
|
348
|
+
) -> None:
|
|
349
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
350
|
+
with get_data("test/cwl/load_contents.cwl") as cwl_file:
|
|
351
|
+
tester_fn(
|
|
352
|
+
cwl_file,
|
|
353
|
+
input_location,
|
|
354
|
+
self._expected_load_contents_output(out_dir),
|
|
355
|
+
out_dir,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
def download_directory(
|
|
359
|
+
self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
|
|
360
|
+
) -> None:
|
|
361
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
362
|
+
with get_data("test/cwl/download_directory.cwl") as cwl_file:
|
|
363
|
+
tester_fn(
|
|
364
|
+
cwl_file,
|
|
365
|
+
input_location,
|
|
366
|
+
self._expected_download_output(out_dir),
|
|
367
|
+
out_dir,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def download_subdirectory(
|
|
371
|
+
self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
|
|
372
|
+
) -> None:
|
|
373
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
374
|
+
with get_data("test/cwl/download_subdirectory.cwl") as cwl_file:
|
|
375
|
+
tester_fn(
|
|
376
|
+
cwl_file,
|
|
377
|
+
input_location,
|
|
378
|
+
self._expected_download_output(out_dir),
|
|
379
|
+
out_dir,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
def test_mpi(self, tmp_path: Path) -> None:
|
|
373
383
|
from toil.cwl import cwltoil
|
|
374
384
|
|
|
375
385
|
stdout = StringIO()
|
|
376
|
-
|
|
377
|
-
"
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
386
|
+
with get_data("test/cwl/mock_mpi/fake_mpi.yml") as mpi_config_file:
|
|
387
|
+
with get_data("test/cwl/mpi_simple.cwl") as cwl_file:
|
|
388
|
+
with get_data("test/cwl/mock_mpi/fake_mpi_run.py") as fake_mpi_run:
|
|
389
|
+
main_args = [
|
|
390
|
+
"--logDebug",
|
|
391
|
+
"--outdir",
|
|
392
|
+
str(tmp_path),
|
|
393
|
+
"--enable-dev",
|
|
394
|
+
"--enable-ext",
|
|
395
|
+
"--mpi-config-file",
|
|
396
|
+
str(mpi_config_file),
|
|
397
|
+
str(cwl_file),
|
|
398
|
+
]
|
|
399
|
+
path = os.environ["PATH"]
|
|
400
|
+
os.environ["PATH"] = f"{path}:{fake_mpi_run.parent}"
|
|
401
|
+
cwltoil.main(main_args, stdout=stdout)
|
|
402
|
+
os.environ["PATH"] = path
|
|
403
|
+
stdout_text = stdout.getvalue()
|
|
404
|
+
assert "pids" in stdout_text
|
|
405
|
+
out = json.loads(stdout_text)
|
|
406
|
+
with open(
|
|
407
|
+
out.get("pids", {}).get("location")[len("file://") :]
|
|
408
|
+
) as f:
|
|
409
|
+
two_pids = [int(i) for i in f.read().split()]
|
|
410
|
+
assert len(two_pids) == 2
|
|
411
|
+
assert isinstance(two_pids[0], int)
|
|
412
|
+
assert isinstance(two_pids[1], int)
|
|
395
413
|
|
|
396
414
|
@needs_aws_s3
|
|
397
|
-
|
|
415
|
+
@pytest.mark.aws_s3
|
|
416
|
+
@pytest.mark.online
|
|
417
|
+
def test_s3_as_secondary_file(self, tmp_path: Path) -> None:
|
|
398
418
|
from toil.cwl import cwltoil
|
|
399
419
|
|
|
400
420
|
stdout = StringIO()
|
|
401
|
-
|
|
402
|
-
"
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
self.assertEqual(f.read().strip(), "When is s4 coming out?")
|
|
421
|
+
with get_data("test/cwl/s3_secondary_file.cwl") as cwl_file:
|
|
422
|
+
with get_data("test/cwl/s3_secondary_file.json") as inputs_file:
|
|
423
|
+
main_args = ["--outdir", str(tmp_path), str(cwl_file), str(inputs_file)]
|
|
424
|
+
cwltoil.main(main_args, stdout=stdout)
|
|
425
|
+
out = json.loads(stdout.getvalue())
|
|
426
|
+
assert (
|
|
427
|
+
out["output"]["checksum"]
|
|
428
|
+
== "sha1$d14dd02e354918b4776b941d154c18ebc15b9b38"
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
assert out["output"]["size"] == 24
|
|
432
|
+
with open(out["output"]["location"][len("file://") :]) as f:
|
|
433
|
+
assert f.read().strip() == "When is s4 coming out?"
|
|
415
434
|
|
|
416
|
-
|
|
417
|
-
|
|
435
|
+
@needs_docker
|
|
436
|
+
@pytest.mark.docker
|
|
437
|
+
@pytest.mark.online
|
|
438
|
+
def test_run_revsort(self, tmp_path: Path) -> None:
|
|
439
|
+
self.revsort("revsort.cwl", self._tester, tmp_path)
|
|
418
440
|
|
|
419
|
-
|
|
441
|
+
@needs_docker
|
|
442
|
+
@pytest.mark.docker
|
|
443
|
+
@pytest.mark.online
|
|
444
|
+
def test_run_revsort_nochecksum(self, tmp_path: Path) -> None:
|
|
420
445
|
self.revsort_no_checksum(
|
|
421
|
-
"revsort.cwl",
|
|
446
|
+
"revsort.cwl",
|
|
447
|
+
partial(self._tester, main_args=["--no-compute-checksum"]),
|
|
448
|
+
tmp_path,
|
|
422
449
|
)
|
|
423
450
|
|
|
424
|
-
def test_run_revsort_no_container(self) -> None:
|
|
451
|
+
def test_run_revsort_no_container(self, tmp_path: Path) -> None:
|
|
425
452
|
self.revsort(
|
|
426
|
-
"revsort.cwl", partial(self._tester, main_args=["--no-container"])
|
|
453
|
+
"revsort.cwl", partial(self._tester, main_args=["--no-container"]), tmp_path
|
|
427
454
|
)
|
|
428
455
|
|
|
429
|
-
|
|
430
|
-
|
|
456
|
+
@needs_docker
|
|
457
|
+
@pytest.mark.docker
|
|
458
|
+
@pytest.mark.online
|
|
459
|
+
def test_run_revsort2(self, tmp_path: Path) -> None:
|
|
460
|
+
self.revsort("revsort2.cwl", self._tester, tmp_path)
|
|
431
461
|
|
|
432
|
-
|
|
433
|
-
|
|
462
|
+
@needs_docker
|
|
463
|
+
@pytest.mark.docker
|
|
464
|
+
@pytest.mark.online
|
|
465
|
+
def test_run_revsort_debug_worker(self, tmp_path: Path) -> None:
|
|
466
|
+
self.revsort("revsort.cwl", self._debug_worker_tester, tmp_path)
|
|
467
|
+
|
|
468
|
+
@needs_docker
|
|
469
|
+
@pytest.mark.docker
|
|
470
|
+
@pytest.mark.online
|
|
471
|
+
def test_run_colon_output(self, tmp_path: Path) -> None:
|
|
472
|
+
with get_data("test/cwl/colon_test_output.cwl") as cwl_file:
|
|
473
|
+
with get_data("test/cwl/colon_test_output_job.yaml") as inputs_file:
|
|
474
|
+
self._tester(
|
|
475
|
+
cwl_file,
|
|
476
|
+
inputs_file,
|
|
477
|
+
self._expected_colon_output(tmp_path),
|
|
478
|
+
tmp_path,
|
|
479
|
+
out_name="result",
|
|
480
|
+
)
|
|
434
481
|
|
|
435
|
-
def test_run_colon_output(self) -> None:
|
|
436
|
-
self._tester(
|
|
437
|
-
"src/toil/test/cwl/colon_test_output.cwl",
|
|
438
|
-
"src/toil/test/cwl/colon_test_output_job.yaml",
|
|
439
|
-
self._expected_colon_output(self.outDir),
|
|
440
|
-
out_name="result",
|
|
441
|
-
)
|
|
442
|
-
|
|
443
482
|
@pytest.mark.integrative
|
|
444
|
-
@
|
|
445
|
-
|
|
483
|
+
@needs_docker
|
|
484
|
+
@pytest.mark.docker
|
|
485
|
+
@pytest.mark.online
|
|
486
|
+
def test_run_dockstore_trs(self, tmp_path: Path) -> None:
|
|
446
487
|
from toil.cwl import cwltoil
|
|
447
488
|
|
|
448
489
|
stdout = StringIO()
|
|
449
490
|
main_args = [
|
|
450
491
|
"--outdir",
|
|
451
|
-
|
|
492
|
+
str(tmp_path),
|
|
452
493
|
"#workflow/github.com/dockstore-testing/md5sum-checker:master",
|
|
453
|
-
"https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/refs/heads/master/md5sum/md5sum-input-cwl.json"
|
|
494
|
+
"https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/refs/heads/master/md5sum/md5sum-input-cwl.json",
|
|
454
495
|
]
|
|
455
496
|
cwltoil.main(main_args, stdout=stdout)
|
|
456
497
|
out = json.loads(stdout.getvalue())
|
|
457
498
|
with open(out.get("output_file", {}).get("location")[len("file://") :]) as f:
|
|
458
499
|
computed_hash = f.read().strip()
|
|
459
|
-
|
|
500
|
+
assert computed_hash == "00579a00e3e7fa0674428ac7049423e2"
|
|
460
501
|
|
|
461
|
-
def test_glob_dir_bypass_file_store(self) -> None:
|
|
502
|
+
def test_glob_dir_bypass_file_store(self, tmp_path: Path) -> None:
|
|
462
503
|
self.maxDiff = 1000
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# Clean up anything we made in the current directory.
|
|
475
|
-
try:
|
|
476
|
-
shutil.rmtree(os.path.join(os.getcwd(), "shouldmake"))
|
|
477
|
-
except FileNotFoundError:
|
|
478
|
-
pass
|
|
479
|
-
|
|
480
|
-
def test_required_input_condition_protection(self) -> None:
|
|
504
|
+
with get_data("test/cwl/glob_dir.cwl") as cwl_file:
|
|
505
|
+
with get_data("test/cwl/empty.json") as inputs_file:
|
|
506
|
+
self._tester(
|
|
507
|
+
cwl_file,
|
|
508
|
+
inputs_file,
|
|
509
|
+
self._expected_glob_dir_output(tmp_path),
|
|
510
|
+
tmp_path,
|
|
511
|
+
main_args=["--bypass-file-store"],
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
def test_required_input_condition_protection(self, tmp_path: Path) -> None:
|
|
481
515
|
# This doesn't run containerized
|
|
482
|
-
|
|
483
|
-
"
|
|
484
|
-
|
|
485
|
-
{},
|
|
486
|
-
)
|
|
516
|
+
with get_data("test/cwl/not_run_required_input.cwl") as cwl_file:
|
|
517
|
+
with get_data("test/cwl/empty.json") as inputs_file:
|
|
518
|
+
self._tester(cwl_file, inputs_file, {}, tmp_path)
|
|
487
519
|
|
|
488
520
|
@needs_slurm
|
|
489
|
-
|
|
521
|
+
@pytest.mark.slurm
|
|
522
|
+
def test_slurm_node_memory(self, tmp_path: Path) -> None:
|
|
490
523
|
pass
|
|
491
524
|
|
|
492
525
|
# Run the workflow. This will either finish quickly and tell us the
|
|
@@ -497,28 +530,29 @@ class CWLWorkflowTest(ToilTest):
|
|
|
497
530
|
# And if we run out of time we need to stop the workflow gracefully and
|
|
498
531
|
# cancel the Slurm jobs.
|
|
499
532
|
|
|
500
|
-
main_args = [
|
|
501
|
-
f"--jobStore={self.jobStoreDir}",
|
|
502
|
-
# Avoid racing to toil kill before the jobstore is removed
|
|
503
|
-
"--clean=never",
|
|
504
|
-
"--batchSystem=slurm",
|
|
505
|
-
"--no-cwl-default-ram",
|
|
506
|
-
"--slurmDefaultAllMem=True",
|
|
507
|
-
"--outdir",
|
|
508
|
-
self.outDir,
|
|
509
|
-
os.path.join(self.rootDir, "src/toil/test/cwl/measure_default_memory.cwl"),
|
|
510
|
-
]
|
|
511
533
|
try:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
534
|
+
with get_data("test/cwl/measure_default_memory.cwl") as cwl_file:
|
|
535
|
+
main_args = [
|
|
536
|
+
f"--jobStore={str(tmp_path / 'jobStoreDir')}",
|
|
537
|
+
# Avoid racing to toil kill before the jobstore is removed
|
|
538
|
+
"--clean=never",
|
|
539
|
+
"--batchSystem=slurm",
|
|
540
|
+
"--no-cwl-default-ram",
|
|
541
|
+
"--slurmDefaultAllMem=True",
|
|
542
|
+
"--outdir",
|
|
543
|
+
str(tmp_path / "outdir"),
|
|
544
|
+
str(cwl_file),
|
|
545
|
+
]
|
|
546
|
+
log.debug("Start test workflow")
|
|
547
|
+
child = subprocess.Popen(
|
|
548
|
+
["toil-cwl-runner"] + main_args, stdout=subprocess.PIPE
|
|
549
|
+
)
|
|
550
|
+
output, _ = child.communicate(timeout=60)
|
|
517
551
|
except subprocess.TimeoutExpired:
|
|
518
552
|
# The job didn't finish quickly; presumably waiting for a full node.
|
|
519
553
|
# Stop the workflow
|
|
520
554
|
log.debug("Workflow might be waiting for a full node. Stop it.")
|
|
521
|
-
subprocess.check_call(["toil", "kill",
|
|
555
|
+
subprocess.check_call(["toil", "kill", str(tmp_path / "jobStoreDir")])
|
|
522
556
|
# Wait another little bit for it to clean up, making sure to collect output in case it is blocked on writing
|
|
523
557
|
child.communicate(timeout=20)
|
|
524
558
|
# Kill it off in case it is still running
|
|
@@ -538,113 +572,151 @@ class CWLWorkflowTest(ToilTest):
|
|
|
538
572
|
else:
|
|
539
573
|
result = int(memory_string)
|
|
540
574
|
# We should see more than the CWL default or the Toil default, assuming Slurm nodes of reasonable size (3 GiB).
|
|
541
|
-
|
|
575
|
+
assert result > (3 * 1024 * 1024)
|
|
542
576
|
|
|
543
577
|
@needs_aws_s3
|
|
544
|
-
|
|
545
|
-
|
|
578
|
+
@pytest.mark.aws_s3
|
|
579
|
+
@pytest.mark.online
|
|
580
|
+
def test_download_s3(self, tmp_path: Path) -> None:
|
|
581
|
+
self.download("download_s3.json", self._tester, tmp_path)
|
|
546
582
|
|
|
547
|
-
def test_download_http(self) -> None:
|
|
548
|
-
self.download("download_http.json", self._tester)
|
|
583
|
+
def test_download_http(self, tmp_path: Path) -> None:
|
|
584
|
+
self.download("download_http.json", self._tester, tmp_path)
|
|
549
585
|
|
|
550
|
-
def test_download_https(self) -> None:
|
|
551
|
-
self.download("download_https.json", self._tester)
|
|
586
|
+
def test_download_https(self, tmp_path: Path) -> None:
|
|
587
|
+
self.download("download_https.json", self._tester, tmp_path)
|
|
552
588
|
|
|
553
|
-
def test_download_https_reference(self) -> None:
|
|
589
|
+
def test_download_https_reference(self, tmp_path: Path) -> None:
|
|
554
590
|
self.download(
|
|
555
591
|
"download_https.json",
|
|
556
592
|
partial(self._tester, main_args=["--reference-inputs"]),
|
|
593
|
+
tmp_path,
|
|
557
594
|
)
|
|
558
595
|
|
|
559
|
-
def test_download_file(self) -> None:
|
|
560
|
-
self.download("download_file.json", self._tester)
|
|
596
|
+
def test_download_file(self, tmp_path: Path) -> None:
|
|
597
|
+
self.download("download_file.json", self._tester, tmp_path)
|
|
561
598
|
|
|
562
599
|
@needs_aws_s3
|
|
563
|
-
|
|
564
|
-
|
|
600
|
+
@pytest.mark.aws_s3
|
|
601
|
+
@pytest.mark.online
|
|
602
|
+
def test_download_directory_s3(self, tmp_path: Path) -> None:
|
|
603
|
+
self.download_directory("download_directory_s3.json", self._tester, tmp_path)
|
|
565
604
|
|
|
566
605
|
@needs_aws_s3
|
|
567
|
-
|
|
606
|
+
@pytest.mark.aws_s3
|
|
607
|
+
@pytest.mark.online
|
|
608
|
+
def test_download_directory_s3_reference(self, tmp_path: Path) -> None:
|
|
568
609
|
self.download_directory(
|
|
569
610
|
"download_directory_s3.json",
|
|
570
611
|
partial(self._tester, main_args=["--reference-inputs"]),
|
|
612
|
+
tmp_path,
|
|
571
613
|
)
|
|
572
614
|
|
|
573
|
-
def test_download_directory_file(self) -> None:
|
|
574
|
-
self.download_directory("download_directory_file.json", self._tester)
|
|
615
|
+
def test_download_directory_file(self, tmp_path: Path) -> None:
|
|
616
|
+
self.download_directory("download_directory_file.json", self._tester, tmp_path)
|
|
575
617
|
|
|
576
618
|
@needs_aws_s3
|
|
577
|
-
|
|
578
|
-
|
|
619
|
+
@pytest.mark.aws_s3
|
|
620
|
+
@pytest.mark.online
|
|
621
|
+
def test_download_subdirectory_s3(self, tmp_path: Path) -> None:
|
|
622
|
+
self.download_subdirectory(
|
|
623
|
+
"download_subdirectory_s3.json", self._tester, tmp_path
|
|
624
|
+
)
|
|
579
625
|
|
|
580
|
-
def test_download_subdirectory_file(self) -> None:
|
|
581
|
-
self.download_subdirectory(
|
|
626
|
+
def test_download_subdirectory_file(self, tmp_path: Path) -> None:
|
|
627
|
+
self.download_subdirectory(
|
|
628
|
+
"download_subdirectory_file.json", self._tester, tmp_path
|
|
629
|
+
)
|
|
582
630
|
|
|
583
631
|
# We also want to make sure we can run a bare tool with loadContents on the inputs, which requires accessing the input data early in the leader.
|
|
584
632
|
|
|
585
633
|
@needs_aws_s3
|
|
586
|
-
|
|
587
|
-
|
|
634
|
+
@pytest.mark.aws_s3
|
|
635
|
+
@pytest.mark.online
|
|
636
|
+
def test_load_contents_s3(self, tmp_path: Path) -> None:
|
|
637
|
+
self.load_contents("download_s3.json", self._tester, tmp_path)
|
|
588
638
|
|
|
589
|
-
def test_load_contents_http(self) -> None:
|
|
590
|
-
self.load_contents("download_http.json", self._tester)
|
|
639
|
+
def test_load_contents_http(self, tmp_path: Path) -> None:
|
|
640
|
+
self.load_contents("download_http.json", self._tester, tmp_path)
|
|
591
641
|
|
|
592
|
-
def test_load_contents_https(self) -> None:
|
|
593
|
-
self.load_contents("download_https.json", self._tester)
|
|
642
|
+
def test_load_contents_https(self, tmp_path: Path) -> None:
|
|
643
|
+
self.load_contents("download_https.json", self._tester, tmp_path)
|
|
594
644
|
|
|
595
|
-
def test_load_contents_file(self) -> None:
|
|
596
|
-
self.load_contents("download_file.json", self._tester)
|
|
645
|
+
def test_load_contents_file(self, tmp_path: Path) -> None:
|
|
646
|
+
self.load_contents("download_file.json", self._tester, tmp_path)
|
|
597
647
|
|
|
598
648
|
@slow
|
|
599
649
|
@pytest.mark.integrative
|
|
600
|
-
@
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
"
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
650
|
+
@pytest.mark.slow
|
|
651
|
+
@pytest.mark.skip("Fails too often due to remote service")
|
|
652
|
+
def test_bioconda(self, tmp_path: Path) -> None:
|
|
653
|
+
with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
|
|
654
|
+
with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
|
|
655
|
+
self._tester(
|
|
656
|
+
cwl_file,
|
|
657
|
+
inputs_file,
|
|
658
|
+
self._expected_seqtk_output(tmp_path),
|
|
659
|
+
tmp_path,
|
|
660
|
+
main_args=["--beta-conda-dependencies"],
|
|
661
|
+
out_name="output1",
|
|
662
|
+
)
|
|
609
663
|
|
|
610
664
|
@needs_docker
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
665
|
+
@pytest.mark.docker
|
|
666
|
+
@pytest.mark.online
|
|
667
|
+
def test_default_args(self, tmp_path: Path) -> None:
|
|
668
|
+
with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
|
|
669
|
+
with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
|
|
670
|
+
self._tester(
|
|
671
|
+
cwl_file,
|
|
672
|
+
inputs_file,
|
|
673
|
+
self._expected_seqtk_output(tmp_path),
|
|
674
|
+
tmp_path,
|
|
675
|
+
main_args=[
|
|
676
|
+
"--default-container",
|
|
677
|
+
"quay.io/biocontainers/seqtk:1.4--he4a0461_1",
|
|
678
|
+
],
|
|
679
|
+
out_name="output1",
|
|
680
|
+
)
|
|
622
681
|
|
|
623
682
|
@needs_docker
|
|
683
|
+
@pytest.mark.docker
|
|
624
684
|
@pytest.mark.integrative
|
|
625
|
-
@
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
"
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
685
|
+
@pytest.mark.online
|
|
686
|
+
@pytest.mark.skip(reason="Fails too often due to remote service")
|
|
687
|
+
def test_biocontainers(self, tmp_path: Path) -> None:
|
|
688
|
+
with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
|
|
689
|
+
with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
|
|
690
|
+
self._tester(
|
|
691
|
+
cwl_file,
|
|
692
|
+
inputs_file,
|
|
693
|
+
self._expected_seqtk_output(tmp_path),
|
|
694
|
+
tmp_path,
|
|
695
|
+
main_args=["--beta-use-biocontainers"],
|
|
696
|
+
out_name="output1",
|
|
697
|
+
)
|
|
634
698
|
|
|
635
699
|
@needs_docker
|
|
636
700
|
@needs_docker_cuda
|
|
637
701
|
@needs_local_cuda
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
702
|
+
@pytest.mark.docker
|
|
703
|
+
@pytest.mark.online
|
|
704
|
+
@pytest.mark.docker_cuda
|
|
705
|
+
@pytest.mark.local_cuda
|
|
706
|
+
def test_cuda(self, tmp_path: Path) -> None:
|
|
707
|
+
with get_data("test/cwl/nvidia_smi.cwl") as cwl_file:
|
|
708
|
+
with get_data("test/cwl/empty.json") as inputs_file:
|
|
709
|
+
self._tester(
|
|
710
|
+
cwl_file,
|
|
711
|
+
inputs_file,
|
|
712
|
+
{},
|
|
713
|
+
tmp_path,
|
|
714
|
+
out_name="result",
|
|
715
|
+
)
|
|
645
716
|
|
|
646
717
|
@slow
|
|
647
|
-
|
|
718
|
+
@pytest.mark.slow
|
|
719
|
+
def test_restart(self, tmp_path: Path) -> None:
|
|
648
720
|
"""
|
|
649
721
|
Enable restarts with toil-cwl-runner -- run failing test, re-run correct test.
|
|
650
722
|
Only implemented for single machine.
|
|
@@ -652,195 +724,203 @@ class CWLWorkflowTest(ToilTest):
|
|
|
652
724
|
log.info("Running CWL Test Restart. Expecting failure, then success.")
|
|
653
725
|
from toil.cwl import cwltoil
|
|
654
726
|
|
|
655
|
-
outDir =
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
["
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
727
|
+
outDir = tmp_path / "outDir"
|
|
728
|
+
outDir.mkdir()
|
|
729
|
+
jobStore = tmp_path / "jobStore"
|
|
730
|
+
with get_data("test/cwl/revsort.cwl") as cwl_file:
|
|
731
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
732
|
+
cmd = [
|
|
733
|
+
"--outdir",
|
|
734
|
+
str(outDir),
|
|
735
|
+
"--jobStore",
|
|
736
|
+
str(jobStore),
|
|
737
|
+
"--no-container",
|
|
738
|
+
str(cwl_file),
|
|
739
|
+
str(job_file),
|
|
740
|
+
]
|
|
741
|
+
|
|
742
|
+
# create a fake rev bin that actually points to the "date" binary
|
|
743
|
+
cal_path = [
|
|
744
|
+
d
|
|
745
|
+
for d in os.environ["PATH"].split(":")
|
|
746
|
+
if os.path.exists(os.path.join(d, "date"))
|
|
747
|
+
][-1]
|
|
748
|
+
os.symlink(
|
|
749
|
+
os.path.realpath(os.path.join(cal_path, "date")), outDir / "rev"
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
def path_with_bogus_rev() -> str:
|
|
753
|
+
# append to the front of the PATH so that we check there first
|
|
754
|
+
return f"{str(outDir)}:" + os.environ["PATH"]
|
|
755
|
+
|
|
756
|
+
orig_path = os.environ["PATH"]
|
|
757
|
+
# Force a failure by trying to use an incorrect version of `rev` from the PATH
|
|
758
|
+
os.environ["PATH"] = path_with_bogus_rev()
|
|
759
|
+
try:
|
|
760
|
+
subprocess.check_output(
|
|
761
|
+
["toil-cwl-runner"] + cmd,
|
|
762
|
+
env=os.environ.copy(),
|
|
763
|
+
stderr=subprocess.STDOUT,
|
|
764
|
+
)
|
|
765
|
+
pytest.fail("Expected problem job with incorrect PATH did not fail")
|
|
766
|
+
except subprocess.CalledProcessError:
|
|
767
|
+
pass
|
|
768
|
+
# Finish the job with a correct PATH
|
|
769
|
+
os.environ["PATH"] = orig_path
|
|
770
|
+
cmd.insert(0, "--restart")
|
|
771
|
+
cwltoil.main(cmd)
|
|
772
|
+
# Should fail because previous job completed successfully
|
|
773
|
+
try:
|
|
774
|
+
subprocess.check_output(
|
|
775
|
+
["toil-cwl-runner"] + cmd,
|
|
776
|
+
env=os.environ.copy(),
|
|
777
|
+
stderr=subprocess.STDOUT,
|
|
778
|
+
)
|
|
779
|
+
pytest.fail("Restart with missing directory did not fail")
|
|
780
|
+
except subprocess.CalledProcessError:
|
|
781
|
+
pass
|
|
782
|
+
|
|
783
|
+
def test_caching(self, tmp_path: Path) -> None:
|
|
707
784
|
log.info("Running CWL caching test.")
|
|
708
785
|
from toil.cwl import cwltoil
|
|
709
786
|
|
|
710
|
-
outDir =
|
|
711
|
-
cacheDir =
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
787
|
+
outDir = tmp_path / "outDir"
|
|
788
|
+
cacheDir = tmp_path / "cacheDir"
|
|
789
|
+
log_path = outDir / "log"
|
|
790
|
+
|
|
791
|
+
with get_data("test/cwl/revsort.cwl") as cwl_file:
|
|
792
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
793
|
+
cmd = [
|
|
794
|
+
"--outdir",
|
|
795
|
+
str(outDir),
|
|
796
|
+
"--jobStore",
|
|
797
|
+
str(tmp_path / "jobStore"),
|
|
798
|
+
"--clean=always",
|
|
799
|
+
"--no-container",
|
|
800
|
+
"--cachedir",
|
|
801
|
+
str(cacheDir),
|
|
802
|
+
str(cwl_file),
|
|
803
|
+
str(job_file),
|
|
804
|
+
]
|
|
805
|
+
st = StringIO()
|
|
806
|
+
ret = cwltoil.main(cmd, stdout=st)
|
|
807
|
+
assert ret == 0
|
|
808
|
+
# cwltool hashes certain steps into directories, ensure it exists
|
|
809
|
+
# since cwltool caches per task and revsort has 2 cwl tasks, there should be 2 directories and 2 status files
|
|
810
|
+
assert sum(1 for _ in cacheDir.iterdir()) == 4
|
|
811
|
+
|
|
812
|
+
# Rerun the workflow to ensure there is a cache hit and that we don't rerun the tools
|
|
813
|
+
st = StringIO()
|
|
814
|
+
cmd = [
|
|
815
|
+
"--writeLogsFromAllJobs=True",
|
|
816
|
+
"--writeLogs",
|
|
817
|
+
str(log_path),
|
|
818
|
+
] + cmd
|
|
819
|
+
ret = cwltoil.main(cmd, stdout=st)
|
|
820
|
+
assert ret == 0
|
|
821
|
+
|
|
822
|
+
# Ensure all of the worker logs are using their cached outputs
|
|
823
|
+
for file in log_path.iterdir():
|
|
824
|
+
assert "Using cached output" in file.read_text(encoding="utf-8")
|
|
749
825
|
|
|
750
826
|
@needs_aws_s3
|
|
751
|
-
|
|
827
|
+
@pytest.mark.aws_s3
|
|
828
|
+
@pytest.mark.online
|
|
829
|
+
def test_streamable(
|
|
830
|
+
self, tmp_path: Path, extra_args: Optional[list[str]] = None
|
|
831
|
+
) -> None:
|
|
752
832
|
"""
|
|
753
833
|
Test that a file with 'streamable'=True is a named pipe.
|
|
754
834
|
This is a CWL1.2 feature.
|
|
755
835
|
"""
|
|
756
|
-
cwlfile = "src/toil/test/cwl/stream.cwl"
|
|
757
|
-
jobfile = "src/toil/test/cwl/stream.json"
|
|
758
|
-
out_name = "output"
|
|
759
|
-
jobstore = f"--jobStore=aws:us-west-1:toil-stream-{uuid.uuid4()}"
|
|
760
|
-
from toil.cwl import cwltoil
|
|
761
|
-
|
|
762
836
|
st = StringIO()
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
"
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
837
|
+
outDir = tmp_path / "outDir"
|
|
838
|
+
with get_data("test/cwl/stream.cwl") as cwlfile:
|
|
839
|
+
with get_data("test/cwl/stream.json") as jobfile:
|
|
840
|
+
out_name = "output"
|
|
841
|
+
jobstore = f"--jobStore=aws:us-west-1:toil-stream-{uuid.uuid4()}"
|
|
842
|
+
from toil.cwl import cwltoil
|
|
843
|
+
|
|
844
|
+
args = [
|
|
845
|
+
"--logDebug",
|
|
846
|
+
"--outdir",
|
|
847
|
+
str(outDir),
|
|
848
|
+
jobstore,
|
|
849
|
+
str(cwlfile),
|
|
850
|
+
str(jobfile),
|
|
851
|
+
]
|
|
852
|
+
if extra_args:
|
|
853
|
+
args = extra_args + args
|
|
854
|
+
log.info("Run CWL run: %s", " ".join(args))
|
|
855
|
+
cwltoil.main(args, stdout=st)
|
|
775
856
|
out = json.loads(st.getvalue())
|
|
776
857
|
out[out_name].pop("http://commonwl.org/cwltool#generation", None)
|
|
777
858
|
out[out_name].pop("nameext", None)
|
|
778
859
|
out[out_name].pop("nameroot", None)
|
|
779
|
-
|
|
860
|
+
assert out == self._expected_streaming_output(outDir)
|
|
780
861
|
with open(out[out_name]["location"][len("file://") :]) as f:
|
|
781
|
-
|
|
862
|
+
assert f.read().strip() == "When is s4 coming out?"
|
|
782
863
|
|
|
783
864
|
@needs_aws_s3
|
|
784
|
-
|
|
865
|
+
@pytest.mark.aws_s3
|
|
866
|
+
@pytest.mark.online
|
|
867
|
+
def test_streamable_reference(self, tmp_path: Path) -> None:
|
|
785
868
|
"""
|
|
786
869
|
Test that a streamable file is a stream even when passed around by URI.
|
|
787
870
|
"""
|
|
788
|
-
self.test_streamable(extra_args=["--reference-inputs"])
|
|
871
|
+
self.test_streamable(tmp_path=tmp_path, extra_args=["--reference-inputs"])
|
|
789
872
|
|
|
790
|
-
def test_preemptible(self) -> None:
|
|
873
|
+
def test_preemptible(self, tmp_path: Path) -> None:
|
|
791
874
|
"""
|
|
792
875
|
Tests that the http://arvados.org/cwl#UsePreemptible extension is supported.
|
|
793
876
|
"""
|
|
794
|
-
cwlfile = "src/toil/test/cwl/preemptible.cwl"
|
|
795
|
-
jobfile = "src/toil/test/cwl/empty.json"
|
|
796
|
-
out_name = "output"
|
|
797
877
|
from toil.cwl import cwltoil
|
|
798
878
|
|
|
799
879
|
st = StringIO()
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
880
|
+
out_name = "output"
|
|
881
|
+
with get_data("test/cwl/preemptible.cwl") as cwlfile:
|
|
882
|
+
with get_data("test/cwl/empty.json") as jobfile:
|
|
883
|
+
args = [
|
|
884
|
+
"--outdir",
|
|
885
|
+
str(tmp_path / "outDir"),
|
|
886
|
+
str(cwlfile),
|
|
887
|
+
str(jobfile),
|
|
888
|
+
]
|
|
889
|
+
cwltoil.main(args, stdout=st)
|
|
807
890
|
out = json.loads(st.getvalue())
|
|
808
891
|
out[out_name].pop("http://commonwl.org/cwltool#generation", None)
|
|
809
892
|
out[out_name].pop("nameext", None)
|
|
810
893
|
out[out_name].pop("nameroot", None)
|
|
811
894
|
with open(out[out_name]["location"][len("file://") :]) as f:
|
|
812
|
-
|
|
895
|
+
assert f.read().strip() == "hello"
|
|
813
896
|
|
|
814
|
-
def test_preemptible_expression(self) -> None:
|
|
897
|
+
def test_preemptible_expression(self, tmp_path: Path) -> None:
|
|
815
898
|
"""
|
|
816
899
|
Tests that the http://arvados.org/cwl#UsePreemptible extension is validated.
|
|
817
900
|
"""
|
|
818
|
-
cwlfile = "src/toil/test/cwl/preemptible_expression.cwl"
|
|
819
|
-
jobfile = "src/toil/test/cwl/preemptible_expression.json"
|
|
820
901
|
from toil.cwl import cwltoil
|
|
821
902
|
|
|
822
903
|
st = StringIO()
|
|
823
|
-
|
|
824
|
-
"
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
904
|
+
with get_data("test/cwl/preemptible_expression.cwl") as cwlfile:
|
|
905
|
+
with get_data("test/cwl/preemptible_expression.json") as jobfile:
|
|
906
|
+
args = [
|
|
907
|
+
"--outdir",
|
|
908
|
+
str(tmp_path),
|
|
909
|
+
str(cwlfile),
|
|
910
|
+
str(jobfile),
|
|
911
|
+
]
|
|
912
|
+
with pytest.raises(
|
|
913
|
+
ValidationException, match=re.escape("expressions are not allowed")
|
|
914
|
+
):
|
|
915
|
+
cwltoil.main(args, stdout=st)
|
|
835
916
|
|
|
836
917
|
@staticmethod
|
|
837
|
-
def _expected_seqtk_output(outDir:
|
|
838
|
-
path =
|
|
839
|
-
loc = "file://" + path
|
|
918
|
+
def _expected_seqtk_output(outDir: Path) -> "CWLObjectType":
|
|
919
|
+
path = outDir / "out"
|
|
840
920
|
return {
|
|
841
921
|
"output1": {
|
|
842
|
-
"location":
|
|
843
|
-
"path": path,
|
|
922
|
+
"location": path.as_uri(),
|
|
923
|
+
"path": str(path),
|
|
844
924
|
"checksum": "sha1$322e001e5a99f19abdce9f02ad0f02a17b5066c2",
|
|
845
925
|
"basename": "out",
|
|
846
926
|
"class": "File",
|
|
@@ -849,13 +929,12 @@ class CWLWorkflowTest(ToilTest):
|
|
|
849
929
|
}
|
|
850
930
|
|
|
851
931
|
@staticmethod
|
|
852
|
-
def _expected_revsort_output(outDir:
|
|
853
|
-
path =
|
|
854
|
-
loc = "file://" + path
|
|
932
|
+
def _expected_revsort_output(outDir: Path) -> "CWLObjectType":
|
|
933
|
+
path = outDir / "output.txt"
|
|
855
934
|
return {
|
|
856
935
|
"output": {
|
|
857
|
-
"location":
|
|
858
|
-
"path": path,
|
|
936
|
+
"location": path.as_uri(),
|
|
937
|
+
"path": str(path),
|
|
859
938
|
"basename": "output.txt",
|
|
860
939
|
"size": 1111,
|
|
861
940
|
"class": "File",
|
|
@@ -864,13 +943,12 @@ class CWLWorkflowTest(ToilTest):
|
|
|
864
943
|
}
|
|
865
944
|
|
|
866
945
|
@staticmethod
|
|
867
|
-
def _expected_revsort_nochecksum_output(outDir:
|
|
868
|
-
path =
|
|
869
|
-
loc = "file://" + path
|
|
946
|
+
def _expected_revsort_nochecksum_output(outDir: Path) -> "CWLObjectType":
|
|
947
|
+
path = outDir / "output.txt"
|
|
870
948
|
return {
|
|
871
949
|
"output": {
|
|
872
|
-
"location":
|
|
873
|
-
"path": path,
|
|
950
|
+
"location": path.as_uri(),
|
|
951
|
+
"path": str(path),
|
|
874
952
|
"basename": "output.txt",
|
|
875
953
|
"size": 1111,
|
|
876
954
|
"class": "File",
|
|
@@ -878,30 +956,29 @@ class CWLWorkflowTest(ToilTest):
|
|
|
878
956
|
}
|
|
879
957
|
|
|
880
958
|
@staticmethod
|
|
881
|
-
def _expected_download_output(outDir:
|
|
882
|
-
path =
|
|
883
|
-
loc = "file://" + path
|
|
959
|
+
def _expected_download_output(outDir: Path) -> "CWLObjectType":
|
|
960
|
+
path = outDir / "output.txt"
|
|
884
961
|
return {
|
|
885
962
|
"output": {
|
|
886
|
-
"location":
|
|
963
|
+
"location": path.as_uri(),
|
|
887
964
|
"basename": "output.txt",
|
|
888
965
|
"size": 0,
|
|
889
966
|
"class": "File",
|
|
890
967
|
"checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
891
|
-
"path": path,
|
|
968
|
+
"path": str(path),
|
|
892
969
|
}
|
|
893
970
|
}
|
|
894
971
|
|
|
895
972
|
@staticmethod
|
|
896
|
-
def _expected_glob_dir_output(out_dir:
|
|
897
|
-
dir_path =
|
|
898
|
-
dir_loc =
|
|
899
|
-
file_path =
|
|
900
|
-
file_loc =
|
|
973
|
+
def _expected_glob_dir_output(out_dir: Path) -> "CWLObjectType":
|
|
974
|
+
dir_path = out_dir / "shouldmake"
|
|
975
|
+
dir_loc = dir_path.as_uri()
|
|
976
|
+
file_path = dir_path / "test.txt"
|
|
977
|
+
file_loc = file_path.as_uri()
|
|
901
978
|
return {
|
|
902
979
|
"shouldmake": {
|
|
903
980
|
"location": dir_loc,
|
|
904
|
-
"path": dir_path,
|
|
981
|
+
"path": str(dir_path),
|
|
905
982
|
"basename": "shouldmake",
|
|
906
983
|
"nameroot": "shouldmake",
|
|
907
984
|
"nameext": "",
|
|
@@ -910,7 +987,7 @@ class CWLWorkflowTest(ToilTest):
|
|
|
910
987
|
{
|
|
911
988
|
"class": "File",
|
|
912
989
|
"location": file_loc,
|
|
913
|
-
"path": file_path,
|
|
990
|
+
"path": str(file_path),
|
|
914
991
|
"basename": "test.txt",
|
|
915
992
|
"checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
916
993
|
"size": 0,
|
|
@@ -922,7 +999,7 @@ class CWLWorkflowTest(ToilTest):
|
|
|
922
999
|
}
|
|
923
1000
|
|
|
924
1001
|
@classmethod
|
|
925
|
-
def _expected_load_contents_output(cls, out_dir:
|
|
1002
|
+
def _expected_load_contents_output(cls, out_dir: Path) -> "CWLObjectType":
|
|
926
1003
|
"""
|
|
927
1004
|
Generate the putput we expect from load_contents.cwl, when sending
|
|
928
1005
|
output files to the given directory.
|
|
@@ -932,13 +1009,15 @@ class CWLWorkflowTest(ToilTest):
|
|
|
932
1009
|
return expected
|
|
933
1010
|
|
|
934
1011
|
@staticmethod
|
|
935
|
-
def _expected_colon_output(outDir:
|
|
936
|
-
path =
|
|
937
|
-
loc = "file://" + os.path.join(
|
|
1012
|
+
def _expected_colon_output(outDir: Path) -> "CWLObjectType":
|
|
1013
|
+
path = outDir / "A:Gln2Cys_result"
|
|
1014
|
+
loc = "file://" + os.path.join(
|
|
1015
|
+
outDir, "A%3AGln2Cys_result"
|
|
1016
|
+
) # not using .as_uri to ensure the expected escaping
|
|
938
1017
|
return {
|
|
939
1018
|
"result": {
|
|
940
1019
|
"location": loc,
|
|
941
|
-
"path": path,
|
|
1020
|
+
"path": str(path),
|
|
942
1021
|
"basename": "A:Gln2Cys_result",
|
|
943
1022
|
"class": "Directory",
|
|
944
1023
|
"listing": [
|
|
@@ -956,13 +1035,12 @@ class CWLWorkflowTest(ToilTest):
|
|
|
956
1035
|
}
|
|
957
1036
|
}
|
|
958
1037
|
|
|
959
|
-
def _expected_streaming_output(self, outDir:
|
|
960
|
-
path =
|
|
961
|
-
loc = "file://" + path
|
|
1038
|
+
def _expected_streaming_output(self, outDir: Path) -> "CWLObjectType":
|
|
1039
|
+
path = outDir / "output.txt"
|
|
962
1040
|
return {
|
|
963
1041
|
"output": {
|
|
964
|
-
"location":
|
|
965
|
-
"path": path,
|
|
1042
|
+
"location": path.as_uri(),
|
|
1043
|
+
"path": str(path),
|
|
966
1044
|
"basename": "output.txt",
|
|
967
1045
|
"size": 24,
|
|
968
1046
|
"class": "File",
|
|
@@ -970,102 +1048,101 @@ class CWLWorkflowTest(ToilTest):
|
|
|
970
1048
|
}
|
|
971
1049
|
}
|
|
972
1050
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1051
|
+
@needs_docker
|
|
1052
|
+
@pytest.mark.docker
|
|
1053
|
+
@pytest.mark.online
|
|
1054
|
+
def test_missing_import(self, tmp_path: Path) -> None:
|
|
1055
|
+
with get_data("test/cwl/revsort.cwl") as cwl_file:
|
|
1056
|
+
with get_data("test/cwl/revsort-job-missing.json") as inputs_file:
|
|
1057
|
+
cmd = [
|
|
1058
|
+
"toil-cwl-runner",
|
|
1059
|
+
f"--outdir={str(tmp_path)}",
|
|
1060
|
+
"--clean=always",
|
|
1061
|
+
str(cwl_file),
|
|
1062
|
+
str(inputs_file),
|
|
1063
|
+
]
|
|
1064
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1065
|
+
# Make sure that the missing file is mentioned in the log so the user knows
|
|
1066
|
+
assert p.returncode == 1, p.stderr
|
|
1067
|
+
assert "missing.txt" in p.stderr
|
|
987
1068
|
|
|
988
1069
|
@needs_aws_s3
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
cwlfile = "src/toil/test/cwl/optional-file.cwl"
|
|
994
|
-
jobfile = "src/toil/test/cwl/optional-file-exists.json"
|
|
995
|
-
|
|
996
|
-
args = [
|
|
997
|
-
os.path.join(self.rootDir, cwlfile),
|
|
998
|
-
os.path.join(self.rootDir, jobfile),
|
|
999
|
-
f"--outdir={out_dir}"
|
|
1000
|
-
]
|
|
1070
|
+
@pytest.mark.aws_s3
|
|
1071
|
+
@pytest.mark.online
|
|
1072
|
+
def test_optional_secondary_files_exists(self, tmp_path: Path) -> None:
|
|
1001
1073
|
from toil.cwl import cwltoil
|
|
1002
1074
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1075
|
+
with get_data("test/cwl/optional-file.cwl") as cwlfile:
|
|
1076
|
+
with get_data("test/cwl/optional-file-exists.json") as jobfile:
|
|
1077
|
+
args = [str(cwlfile), str(jobfile), f"--outdir={str(tmp_path)}"]
|
|
1078
|
+
ret = cwltoil.main(args)
|
|
1079
|
+
assert ret == 0
|
|
1080
|
+
assert (tmp_path / "wdl_templates_old.zip").exists()
|
|
1006
1081
|
|
|
1007
1082
|
@needs_aws_s3
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1083
|
+
@pytest.mark.aws_s3
|
|
1084
|
+
@pytest.mark.online
|
|
1085
|
+
def test_optional_secondary_files_missing(self, tmp_path: Path) -> None:
|
|
1086
|
+
from toil.cwl import cwltoil
|
|
1011
1087
|
|
|
1012
|
-
|
|
1013
|
-
|
|
1088
|
+
with get_data("test/cwl/optional-file.cwl") as cwlfile:
|
|
1089
|
+
with get_data("test/cwl/optional-file-missing.json") as jobfile:
|
|
1090
|
+
args = [str(cwlfile), str(jobfile), f"--outdir={str(tmp_path)}"]
|
|
1091
|
+
ret = cwltoil.main(args)
|
|
1092
|
+
assert ret == 0
|
|
1093
|
+
assert not (tmp_path / "hello_old.zip").exists()
|
|
1014
1094
|
|
|
1015
|
-
args = [
|
|
1016
|
-
os.path.join(self.rootDir, cwlfile),
|
|
1017
|
-
os.path.join(self.rootDir, jobfile),
|
|
1018
|
-
f"--outdir={out_dir}"
|
|
1019
|
-
]
|
|
1020
|
-
from toil.cwl import cwltoil
|
|
1021
1095
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1096
|
+
@pytest.fixture(scope="function")
|
|
1097
|
+
def cwl_v1_0_spec(tmp_path: Path) -> Generator[Path]:
|
|
1098
|
+
# The latest cwl git commit hash from https://github.com/common-workflow-language/common-workflow-language.
|
|
1099
|
+
# Update it to get the latest tests.
|
|
1100
|
+
testhash = (
|
|
1101
|
+
"6a955874ade22080b8ef962b4e0d6e408112c1ef" # Date: Tue Dec 16 2020 8:43pm PST
|
|
1102
|
+
)
|
|
1103
|
+
url = (
|
|
1104
|
+
"https://github.com/common-workflow-language/common-workflow-language/archive/%s.zip"
|
|
1105
|
+
% testhash
|
|
1106
|
+
)
|
|
1107
|
+
urlretrieve(url, "spec.zip")
|
|
1108
|
+
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1109
|
+
z.extractall()
|
|
1110
|
+
shutil.move("common-workflow-language-%s" % testhash, str(tmp_path))
|
|
1111
|
+
os.remove("spec.zip")
|
|
1112
|
+
try:
|
|
1113
|
+
yield tmp_path / ("common-workflow-language-%s" % testhash)
|
|
1114
|
+
finally:
|
|
1115
|
+
pass # no cleanup
|
|
1025
1116
|
|
|
1117
|
+
@pytest.mark.integrative
|
|
1118
|
+
@pytest.mark.conformance
|
|
1026
1119
|
@needs_cwl
|
|
1027
1120
|
@needs_online
|
|
1028
|
-
|
|
1121
|
+
@pytest.mark.cwl
|
|
1122
|
+
@pytest.mark.online
|
|
1123
|
+
class TestCWLv10Conformance:
|
|
1029
1124
|
"""
|
|
1030
1125
|
Run the CWL 1.0 conformance tests in various environments.
|
|
1031
1126
|
"""
|
|
1032
1127
|
|
|
1033
|
-
def setUp(self) -> None:
|
|
1034
|
-
"""Runs anew before each test to create farm fresh temp dirs."""
|
|
1035
|
-
self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
|
|
1036
|
-
os.makedirs(self.outDir)
|
|
1037
|
-
self.rootDir = self._projectRootPath()
|
|
1038
|
-
self.cwlSpec = os.path.join(self.rootDir, "src/toil/test/cwl/spec")
|
|
1039
|
-
self.workDir = os.path.join(self.cwlSpec, "v1.0")
|
|
1040
|
-
# The latest cwl git commit hash from https://github.com/common-workflow-language/common-workflow-language.
|
|
1041
|
-
# Update it to get the latest tests.
|
|
1042
|
-
testhash = "6a955874ade22080b8ef962b4e0d6e408112c1ef" # Date: Tue Dec 16 2020 8:43pm PST
|
|
1043
|
-
url = (
|
|
1044
|
-
"https://github.com/common-workflow-language/common-workflow-language/archive/%s.zip"
|
|
1045
|
-
% testhash
|
|
1046
|
-
)
|
|
1047
|
-
if not os.path.exists(self.cwlSpec):
|
|
1048
|
-
urlretrieve(url, "spec.zip")
|
|
1049
|
-
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1050
|
-
z.extractall()
|
|
1051
|
-
shutil.move("common-workflow-language-%s" % testhash, self.cwlSpec)
|
|
1052
|
-
os.remove("spec.zip")
|
|
1053
|
-
|
|
1054
|
-
def tearDown(self) -> None:
|
|
1055
|
-
"""Clean up outputs."""
|
|
1056
|
-
if os.path.exists(self.outDir):
|
|
1057
|
-
shutil.rmtree(self.outDir)
|
|
1058
|
-
unittest.TestCase.tearDown(self)
|
|
1059
|
-
|
|
1060
1128
|
@slow
|
|
1129
|
+
@needs_docker
|
|
1130
|
+
@pytest.mark.slow
|
|
1131
|
+
@pytest.mark.docker
|
|
1132
|
+
@pytest.mark.online
|
|
1061
1133
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1062
|
-
def test_run_conformance_with_caching(self) -> None:
|
|
1063
|
-
self.test_run_conformance(caching=True)
|
|
1134
|
+
def test_run_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1135
|
+
self.test_run_conformance(cwl_v1_0_spec, caching=True)
|
|
1064
1136
|
|
|
1065
1137
|
@slow
|
|
1138
|
+
@needs_docker
|
|
1139
|
+
@pytest.mark.slow
|
|
1140
|
+
@pytest.mark.docker
|
|
1141
|
+
@pytest.mark.online
|
|
1066
1142
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1067
1143
|
def test_run_conformance(
|
|
1068
1144
|
self,
|
|
1145
|
+
cwl_v1_0_spec: Path,
|
|
1069
1146
|
batchSystem: Optional[str] = None,
|
|
1070
1147
|
caching: bool = False,
|
|
1071
1148
|
selected_tests: Optional[str] = None,
|
|
@@ -1073,8 +1150,8 @@ class CWLv10Test(ToilTest):
|
|
|
1073
1150
|
extra_args: Optional[list[str]] = None,
|
|
1074
1151
|
) -> None:
|
|
1075
1152
|
run_conformance_tests(
|
|
1076
|
-
workDir=
|
|
1077
|
-
yml="conformance_test_v1.0.yaml",
|
|
1153
|
+
workDir=str(cwl_v1_0_spec / "v1.0"),
|
|
1154
|
+
yml=str(cwl_v1_0_spec / "v1.0" / "conformance_test_v1.0.yaml"),
|
|
1078
1155
|
caching=caching,
|
|
1079
1156
|
batchSystem=batchSystem,
|
|
1080
1157
|
selected_tests=selected_tests,
|
|
@@ -1084,38 +1161,66 @@ class CWLv10Test(ToilTest):
|
|
|
1084
1161
|
|
|
1085
1162
|
@slow
|
|
1086
1163
|
@needs_lsf
|
|
1087
|
-
@
|
|
1088
|
-
|
|
1089
|
-
|
|
1164
|
+
@pytest.mark.slow
|
|
1165
|
+
@pytest.mark.lsf
|
|
1166
|
+
@pytest.mark.skip("Not run")
|
|
1167
|
+
def test_lsf_cwl_conformance(
|
|
1168
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1169
|
+
) -> None:
|
|
1170
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="lsf", caching=caching)
|
|
1090
1171
|
|
|
1091
1172
|
@slow
|
|
1092
1173
|
@needs_slurm
|
|
1093
|
-
@
|
|
1094
|
-
|
|
1095
|
-
|
|
1174
|
+
@pytest.mark.slow
|
|
1175
|
+
@pytest.mark.slurm
|
|
1176
|
+
@pytest.mark.skip("Not run")
|
|
1177
|
+
def test_slurm_cwl_conformance(
|
|
1178
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1179
|
+
) -> None:
|
|
1180
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="slurm", caching=caching)
|
|
1096
1181
|
|
|
1097
1182
|
@slow
|
|
1098
1183
|
@needs_torque
|
|
1099
|
-
@
|
|
1100
|
-
|
|
1101
|
-
|
|
1184
|
+
@pytest.mark.slow
|
|
1185
|
+
@pytest.mark.torque
|
|
1186
|
+
@pytest.mark.skip("Not run")
|
|
1187
|
+
def test_torque_cwl_conformance(
|
|
1188
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1189
|
+
) -> None:
|
|
1190
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="torque", caching=caching)
|
|
1102
1191
|
|
|
1103
1192
|
@slow
|
|
1104
1193
|
@needs_gridengine
|
|
1105
|
-
@
|
|
1106
|
-
|
|
1107
|
-
|
|
1194
|
+
@pytest.mark.slow
|
|
1195
|
+
@pytest.mark.gridengine
|
|
1196
|
+
@pytest.mark.skip("Not run")
|
|
1197
|
+
def test_gridengine_cwl_conformance(
|
|
1198
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1199
|
+
) -> None:
|
|
1200
|
+
self.test_run_conformance(
|
|
1201
|
+
cwl_v1_0_spec, batchSystem="grid_engine", caching=caching
|
|
1202
|
+
)
|
|
1108
1203
|
|
|
1109
1204
|
@slow
|
|
1110
1205
|
@needs_mesos
|
|
1111
|
-
@
|
|
1112
|
-
|
|
1113
|
-
|
|
1206
|
+
@pytest.mark.slow
|
|
1207
|
+
@pytest.mark.mesos
|
|
1208
|
+
@pytest.mark.skip("Not run")
|
|
1209
|
+
def test_mesos_cwl_conformance(
|
|
1210
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1211
|
+
) -> None:
|
|
1212
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="mesos", caching=caching)
|
|
1114
1213
|
|
|
1115
1214
|
@slow
|
|
1116
1215
|
@needs_kubernetes
|
|
1117
|
-
|
|
1216
|
+
@pytest.mark.slow
|
|
1217
|
+
@pytest.mark.kubernetes
|
|
1218
|
+
@pytest.mark.online
|
|
1219
|
+
def test_kubernetes_cwl_conformance(
|
|
1220
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1221
|
+
) -> None:
|
|
1118
1222
|
self.test_run_conformance(
|
|
1223
|
+
cwl_v1_0_spec,
|
|
1119
1224
|
caching=caching,
|
|
1120
1225
|
batchSystem="kubernetes",
|
|
1121
1226
|
extra_args=["--retryCount=3"],
|
|
@@ -1127,82 +1232,100 @@ class CWLv10Test(ToilTest):
|
|
|
1127
1232
|
|
|
1128
1233
|
@slow
|
|
1129
1234
|
@needs_lsf
|
|
1130
|
-
@
|
|
1131
|
-
|
|
1132
|
-
|
|
1235
|
+
@pytest.mark.slow
|
|
1236
|
+
@pytest.mark.lsf
|
|
1237
|
+
@pytest.mark.skip(reason="Not run")
|
|
1238
|
+
def test_lsf_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1239
|
+
self.test_lsf_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1133
1240
|
|
|
1134
1241
|
@slow
|
|
1135
1242
|
@needs_slurm
|
|
1136
|
-
@
|
|
1137
|
-
|
|
1138
|
-
|
|
1243
|
+
@pytest.mark.slow
|
|
1244
|
+
@pytest.mark.slurm
|
|
1245
|
+
@pytest.mark.skip(reason="Not run")
|
|
1246
|
+
def test_slurm_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1247
|
+
self.test_slurm_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1139
1248
|
|
|
1140
1249
|
@slow
|
|
1141
1250
|
@needs_torque
|
|
1142
|
-
@
|
|
1143
|
-
|
|
1144
|
-
|
|
1251
|
+
@pytest.mark.slow
|
|
1252
|
+
@pytest.mark.torque
|
|
1253
|
+
@pytest.mark.skip(reason="Not run")
|
|
1254
|
+
def test_torque_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1255
|
+
self.test_torque_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1145
1256
|
|
|
1146
1257
|
@slow
|
|
1147
1258
|
@needs_gridengine
|
|
1148
|
-
@
|
|
1149
|
-
|
|
1150
|
-
|
|
1259
|
+
@pytest.mark.slow
|
|
1260
|
+
@pytest.mark.gridengine
|
|
1261
|
+
@pytest.mark.skip(reason="Not run")
|
|
1262
|
+
def test_gridengine_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1263
|
+
self.test_gridengine_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1151
1264
|
|
|
1152
1265
|
@slow
|
|
1153
1266
|
@needs_mesos
|
|
1154
|
-
@
|
|
1155
|
-
|
|
1156
|
-
|
|
1267
|
+
@pytest.mark.slow
|
|
1268
|
+
@pytest.mark.mesos
|
|
1269
|
+
@pytest.mark.skip(reason="Not run")
|
|
1270
|
+
def test_mesos_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1271
|
+
self.test_mesos_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1157
1272
|
|
|
1158
1273
|
@slow
|
|
1159
1274
|
@needs_kubernetes
|
|
1160
|
-
|
|
1161
|
-
|
|
1275
|
+
@pytest.mark.slow
|
|
1276
|
+
@pytest.mark.kubernetes
|
|
1277
|
+
@pytest.mark.online
|
|
1278
|
+
def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1279
|
+
self.test_kubernetes_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
@pytest.fixture(scope="function")
|
|
1283
|
+
def cwl_v1_1_spec(tmp_path: Path) -> Generator[Path]:
|
|
1284
|
+
# The latest cwl git commit hash from https://github.com/common-workflow-language/cwl-v1.1
|
|
1285
|
+
# Update it to get the latest tests.
|
|
1286
|
+
testhash = "664835e83eb5e57eee18a04ce7b05fb9d70d77b7"
|
|
1287
|
+
url = (
|
|
1288
|
+
"https://github.com/common-workflow-language/cwl-v1.1/archive/%s.zip" % testhash
|
|
1289
|
+
)
|
|
1290
|
+
urlretrieve(url, "spec.zip")
|
|
1291
|
+
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1292
|
+
z.extractall()
|
|
1293
|
+
shutil.move("cwl-v1.1-%s" % testhash, str(tmp_path))
|
|
1294
|
+
os.remove("spec.zip")
|
|
1295
|
+
try:
|
|
1296
|
+
yield tmp_path / ("cwl-v1.1-%s" % testhash)
|
|
1297
|
+
finally:
|
|
1298
|
+
pass # no cleanup
|
|
1162
1299
|
|
|
1163
1300
|
|
|
1301
|
+
@pytest.mark.integrative
|
|
1302
|
+
@pytest.mark.conformance
|
|
1164
1303
|
@needs_cwl
|
|
1165
1304
|
@needs_online
|
|
1166
|
-
|
|
1305
|
+
@pytest.mark.cwl
|
|
1306
|
+
@pytest.mark.online
|
|
1307
|
+
class TestCWLv11Conformance:
|
|
1167
1308
|
"""
|
|
1168
1309
|
Run the CWL 1.1 conformance tests in various environments.
|
|
1169
1310
|
"""
|
|
1170
1311
|
|
|
1171
|
-
rootDir: str
|
|
1172
|
-
cwlSpec: str
|
|
1173
|
-
test_yaml: str
|
|
1174
|
-
|
|
1175
|
-
@classmethod
|
|
1176
|
-
def setUpClass(cls) -> None:
|
|
1177
|
-
"""Runs anew before each test."""
|
|
1178
|
-
cls.rootDir = cls._projectRootPath()
|
|
1179
|
-
cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v11")
|
|
1180
|
-
cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
|
|
1181
|
-
# TODO: Use a commit zip in case someone decides to rewrite master's history?
|
|
1182
|
-
url = "https://github.com/common-workflow-language/cwl-v1.1.git"
|
|
1183
|
-
commit = "664835e83eb5e57eee18a04ce7b05fb9d70d77b7"
|
|
1184
|
-
p = subprocess.Popen(
|
|
1185
|
-
f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
|
|
1186
|
-
shell=True,
|
|
1187
|
-
)
|
|
1188
|
-
p.communicate()
|
|
1189
|
-
|
|
1190
|
-
def tearDown(self) -> None:
|
|
1191
|
-
"""Clean up outputs."""
|
|
1192
|
-
unittest.TestCase.tearDown(self)
|
|
1193
|
-
|
|
1194
1312
|
@slow
|
|
1313
|
+
@needs_docker
|
|
1314
|
+
@pytest.mark.slow
|
|
1315
|
+
@pytest.mark.docker
|
|
1316
|
+
@pytest.mark.online
|
|
1195
1317
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1196
1318
|
def test_run_conformance(
|
|
1197
1319
|
self,
|
|
1320
|
+
cwl_v1_1_spec: Path,
|
|
1198
1321
|
caching: bool = False,
|
|
1199
1322
|
batchSystem: Optional[str] = None,
|
|
1200
1323
|
skipped_tests: Optional[str] = None,
|
|
1201
1324
|
extra_args: Optional[list[str]] = None,
|
|
1202
1325
|
) -> None:
|
|
1203
1326
|
run_conformance_tests(
|
|
1204
|
-
workDir=
|
|
1205
|
-
yml=
|
|
1327
|
+
workDir=str(cwl_v1_1_spec),
|
|
1328
|
+
yml=str(cwl_v1_1_spec / "conformance_tests.yaml"),
|
|
1206
1329
|
caching=caching,
|
|
1207
1330
|
batchSystem=batchSystem,
|
|
1208
1331
|
skipped_tests=skipped_tests,
|
|
@@ -1210,14 +1333,24 @@ class CWLv11Test(ToilTest):
|
|
|
1210
1333
|
)
|
|
1211
1334
|
|
|
1212
1335
|
@slow
|
|
1336
|
+
@needs_docker
|
|
1337
|
+
@pytest.mark.slow
|
|
1338
|
+
@pytest.mark.docker
|
|
1339
|
+
@pytest.mark.online
|
|
1213
1340
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1214
|
-
def test_run_conformance_with_caching(self) -> None:
|
|
1215
|
-
self.test_run_conformance(caching=True)
|
|
1341
|
+
def test_run_conformance_with_caching(self, cwl_v1_1_spec: Path) -> None:
|
|
1342
|
+
self.test_run_conformance(cwl_v1_1_spec, caching=True)
|
|
1216
1343
|
|
|
1217
1344
|
@slow
|
|
1218
1345
|
@needs_kubernetes
|
|
1219
|
-
|
|
1346
|
+
@pytest.mark.slow
|
|
1347
|
+
@pytest.mark.kubernetes
|
|
1348
|
+
@pytest.mark.online
|
|
1349
|
+
def test_kubernetes_cwl_conformance(
|
|
1350
|
+
self, cwl_v1_1_spec: Path, caching: bool = False
|
|
1351
|
+
) -> None:
|
|
1220
1352
|
self.test_run_conformance(
|
|
1353
|
+
cwl_v1_1_spec,
|
|
1221
1354
|
batchSystem="kubernetes",
|
|
1222
1355
|
extra_args=["--retryCount=3"],
|
|
1223
1356
|
# These tests don't work with
|
|
@@ -1229,44 +1362,52 @@ class CWLv11Test(ToilTest):
|
|
|
1229
1362
|
|
|
1230
1363
|
@slow
|
|
1231
1364
|
@needs_kubernetes
|
|
1232
|
-
|
|
1233
|
-
|
|
1365
|
+
@pytest.mark.slow
|
|
1366
|
+
@pytest.mark.kubernetes
|
|
1367
|
+
@pytest.mark.online
|
|
1368
|
+
def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_1_spec: Path) -> None:
|
|
1369
|
+
self.test_kubernetes_cwl_conformance(cwl_v1_1_spec, caching=True)
|
|
1370
|
+
|
|
1371
|
+
|
|
1372
|
+
@pytest.fixture(scope="function")
|
|
1373
|
+
def cwl_v1_2_spec(tmp_path: Path) -> Generator[Path]:
|
|
1374
|
+
# The latest cwl git commit hash from https://github.com/common-workflow-language/cwl-v1.2
|
|
1375
|
+
# Update it to get the latest tests.
|
|
1376
|
+
testhash = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
|
|
1377
|
+
url = (
|
|
1378
|
+
"https://github.com/common-workflow-language/cwl-v1.2/archive/%s.zip" % testhash
|
|
1379
|
+
)
|
|
1380
|
+
urlretrieve(url, "spec.zip")
|
|
1381
|
+
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1382
|
+
z.extractall()
|
|
1383
|
+
shutil.move("cwl-v1.2-%s" % testhash, str(tmp_path))
|
|
1384
|
+
os.remove("spec.zip")
|
|
1385
|
+
try:
|
|
1386
|
+
yield tmp_path / ("cwl-v1.2-%s" % testhash)
|
|
1387
|
+
finally:
|
|
1388
|
+
pass # no cleanup
|
|
1234
1389
|
|
|
1235
1390
|
|
|
1391
|
+
@pytest.mark.integrative
|
|
1392
|
+
@pytest.mark.conformance
|
|
1236
1393
|
@needs_cwl
|
|
1237
1394
|
@needs_online
|
|
1238
|
-
|
|
1395
|
+
@pytest.mark.cwl
|
|
1396
|
+
@pytest.mark.online
|
|
1397
|
+
class TestCWLv12Conformance:
|
|
1239
1398
|
"""
|
|
1240
1399
|
Run the CWL 1.2 conformance tests in various environments.
|
|
1241
1400
|
"""
|
|
1242
1401
|
|
|
1243
|
-
rootDir: str
|
|
1244
|
-
cwlSpec: str
|
|
1245
|
-
test_yaml: str
|
|
1246
|
-
|
|
1247
|
-
@classmethod
|
|
1248
|
-
def setUpClass(cls) -> None:
|
|
1249
|
-
"""Runs anew before each test."""
|
|
1250
|
-
cls.rootDir = cls._projectRootPath()
|
|
1251
|
-
cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v12")
|
|
1252
|
-
cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
|
|
1253
|
-
# TODO: Use a commit zip in case someone decides to rewrite master's history?
|
|
1254
|
-
url = "https://github.com/common-workflow-language/cwl-v1.2.git"
|
|
1255
|
-
commit = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
|
|
1256
|
-
p = subprocess.Popen(
|
|
1257
|
-
f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
|
|
1258
|
-
shell=True,
|
|
1259
|
-
)
|
|
1260
|
-
p.communicate()
|
|
1261
|
-
|
|
1262
|
-
def tearDown(self) -> None:
|
|
1263
|
-
"""Clean up outputs."""
|
|
1264
|
-
unittest.TestCase.tearDown(self)
|
|
1265
|
-
|
|
1266
1402
|
@slow
|
|
1403
|
+
@needs_docker
|
|
1404
|
+
@pytest.mark.slow
|
|
1405
|
+
@pytest.mark.docker
|
|
1406
|
+
@pytest.mark.online
|
|
1267
1407
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1268
1408
|
def test_run_conformance(
|
|
1269
1409
|
self,
|
|
1410
|
+
cwl_v1_2_spec: Path,
|
|
1270
1411
|
runner: Optional[str] = None,
|
|
1271
1412
|
caching: bool = False,
|
|
1272
1413
|
batchSystem: Optional[str] = None,
|
|
@@ -1277,10 +1418,10 @@ class CWLv12Test(ToilTest):
|
|
|
1277
1418
|
junit_file: Optional[str] = None,
|
|
1278
1419
|
) -> None:
|
|
1279
1420
|
if junit_file is None:
|
|
1280
|
-
junit_file = os.path.
|
|
1421
|
+
junit_file = os.path.abspath("conformance-1.2.junit.xml")
|
|
1281
1422
|
run_conformance_tests(
|
|
1282
|
-
workDir=
|
|
1283
|
-
yml=
|
|
1423
|
+
workDir=str(cwl_v1_2_spec),
|
|
1424
|
+
yml=str(cwl_v1_2_spec / "conformance_tests.yaml"),
|
|
1284
1425
|
runner=runner,
|
|
1285
1426
|
caching=caching,
|
|
1286
1427
|
batchSystem=batchSystem,
|
|
@@ -1290,48 +1431,67 @@ class CWLv12Test(ToilTest):
|
|
|
1290
1431
|
must_support_all_features=must_support_all_features,
|
|
1291
1432
|
junit_file=junit_file,
|
|
1292
1433
|
)
|
|
1434
|
+
|
|
1293
1435
|
@slow
|
|
1436
|
+
@needs_docker
|
|
1437
|
+
@pytest.mark.slow
|
|
1438
|
+
@pytest.mark.docker
|
|
1439
|
+
@pytest.mark.online
|
|
1294
1440
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1295
|
-
def test_run_conformance_with_caching(self) -> None:
|
|
1441
|
+
def test_run_conformance_with_caching(self, cwl_v1_2_spec: Path) -> None:
|
|
1296
1442
|
self.test_run_conformance(
|
|
1443
|
+
cwl_v1_2_spec,
|
|
1297
1444
|
caching=True,
|
|
1298
|
-
junit_file=os.path.
|
|
1445
|
+
junit_file=os.path.abspath("caching-conformance-1.2.junit.xml"),
|
|
1299
1446
|
)
|
|
1300
1447
|
|
|
1301
1448
|
@slow
|
|
1449
|
+
@needs_docker
|
|
1450
|
+
@pytest.mark.slow
|
|
1451
|
+
@pytest.mark.docker
|
|
1302
1452
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1303
|
-
def test_run_conformance_with_task_caching(
|
|
1453
|
+
def test_run_conformance_with_task_caching(
|
|
1454
|
+
self, cwl_v1_2_spec: Path, tmp_path: Path
|
|
1455
|
+
) -> None:
|
|
1304
1456
|
self.test_run_conformance(
|
|
1305
|
-
|
|
1306
|
-
|
|
1457
|
+
cwl_v1_2_spec,
|
|
1458
|
+
junit_file=os.path.abspath("task-caching-conformance-1.2.junit.xml"),
|
|
1459
|
+
extra_args=["--cachedir", str(tmp_path / "task_cache")],
|
|
1307
1460
|
)
|
|
1308
1461
|
|
|
1309
1462
|
@slow
|
|
1463
|
+
@needs_docker
|
|
1464
|
+
@pytest.mark.slow
|
|
1465
|
+
@pytest.mark.docker
|
|
1310
1466
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1311
|
-
def test_run_conformance_with_in_place_update(self) -> None:
|
|
1467
|
+
def test_run_conformance_with_in_place_update(self, cwl_v1_2_spec: Path) -> None:
|
|
1312
1468
|
"""
|
|
1313
1469
|
Make sure that with --bypass-file-store we properly support in place
|
|
1314
1470
|
update on a single node, and that this doesn't break any other
|
|
1315
1471
|
features.
|
|
1316
1472
|
"""
|
|
1317
1473
|
self.test_run_conformance(
|
|
1474
|
+
cwl_v1_2_spec,
|
|
1318
1475
|
extra_args=["--bypass-file-store"],
|
|
1319
1476
|
must_support_all_features=True,
|
|
1320
|
-
junit_file=os.path.
|
|
1321
|
-
self.rootDir, "in-place-update-conformance-1.2.junit.xml"
|
|
1322
|
-
),
|
|
1477
|
+
junit_file=os.path.abspath("in-place-update-conformance-1.2.junit.xml"),
|
|
1323
1478
|
)
|
|
1324
1479
|
|
|
1325
1480
|
@slow
|
|
1326
1481
|
@needs_kubernetes
|
|
1482
|
+
@pytest.mark.slow
|
|
1483
|
+
@pytest.mark.kubernetes
|
|
1484
|
+
@pytest.mark.online
|
|
1327
1485
|
def test_kubernetes_cwl_conformance(
|
|
1328
|
-
self,
|
|
1486
|
+
self,
|
|
1487
|
+
cwl_v1_2_spec: Path,
|
|
1488
|
+
caching: bool = False,
|
|
1489
|
+
junit_file: Optional[str] = None,
|
|
1329
1490
|
) -> None:
|
|
1330
1491
|
if junit_file is None:
|
|
1331
|
-
junit_file = os.path.
|
|
1332
|
-
self.rootDir, "kubernetes-conformance-1.2.junit.xml"
|
|
1333
|
-
)
|
|
1492
|
+
junit_file = os.path.abspath("kubernetes-conformance-1.2.junit.xml")
|
|
1334
1493
|
self.test_run_conformance(
|
|
1494
|
+
cwl_v1_2_spec,
|
|
1335
1495
|
caching=caching,
|
|
1336
1496
|
batchSystem="kubernetes",
|
|
1337
1497
|
extra_args=["--retryCount=3"],
|
|
@@ -1346,17 +1506,22 @@ class CWLv12Test(ToilTest):
|
|
|
1346
1506
|
|
|
1347
1507
|
@slow
|
|
1348
1508
|
@needs_kubernetes
|
|
1349
|
-
|
|
1509
|
+
@pytest.mark.slow
|
|
1510
|
+
@pytest.mark.kubernetes
|
|
1511
|
+
@pytest.mark.online
|
|
1512
|
+
def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_2_spec: Path) -> None:
|
|
1350
1513
|
self.test_kubernetes_cwl_conformance(
|
|
1514
|
+
cwl_v1_2_spec,
|
|
1351
1515
|
caching=True,
|
|
1352
|
-
junit_file=os.path.
|
|
1353
|
-
self.rootDir, "kubernetes-caching-conformance-1.2.junit.xml"
|
|
1354
|
-
),
|
|
1516
|
+
junit_file=os.path.abspath("kubernetes-caching-conformance-1.2.junit.xml"),
|
|
1355
1517
|
)
|
|
1356
1518
|
|
|
1357
1519
|
@slow
|
|
1358
1520
|
@needs_wes_server
|
|
1359
|
-
|
|
1521
|
+
@pytest.mark.slow
|
|
1522
|
+
@pytest.mark.wes_server
|
|
1523
|
+
@pytest.mark.online
|
|
1524
|
+
def test_wes_server_cwl_conformance(self, cwl_v1_2_spec: Path) -> None:
|
|
1360
1525
|
"""
|
|
1361
1526
|
Run the CWL conformance tests via WES. TOIL_WES_ENDPOINT must be
|
|
1362
1527
|
specified. If the WES server requires authentication, set TOIL_WES_USER
|
|
@@ -1367,7 +1532,7 @@ class CWLv12Test(ToilTest):
|
|
|
1367
1532
|
TOIL_WES_ENDPOINT=http://localhost:8080 \
|
|
1368
1533
|
TOIL_WES_USER=test \
|
|
1369
1534
|
TOIL_WES_PASSWORD=password \
|
|
1370
|
-
python -m pytest src/toil/test/cwl/cwlTest.py::
|
|
1535
|
+
python -m pytest src/toil/test/cwl/cwlTest.py::TestCWLv12Conformance::test_wes_server_cwl_conformance -vv --log-level INFO --log-cli-level INFO
|
|
1371
1536
|
"""
|
|
1372
1537
|
endpoint = os.environ.get("TOIL_WES_ENDPOINT")
|
|
1373
1538
|
extra_args = [f"--wes_endpoint={endpoint}"]
|
|
@@ -1382,6 +1547,7 @@ class CWLv12Test(ToilTest):
|
|
|
1382
1547
|
# e.g.: https://github.com/common-workflow-language/cwl-v1.2/blob/1.2.1_proposed/tests/mixed-versions/wf-v10.cwl#L4-L10
|
|
1383
1548
|
|
|
1384
1549
|
self.test_run_conformance(
|
|
1550
|
+
cwl_v1_2_spec,
|
|
1385
1551
|
runner="toil-wes-cwl-runner",
|
|
1386
1552
|
selected_tests="1-309,313-337",
|
|
1387
1553
|
extra_args=extra_args,
|
|
@@ -1389,183 +1555,162 @@ class CWLv12Test(ToilTest):
|
|
|
1389
1555
|
|
|
1390
1556
|
|
|
1391
1557
|
@needs_cwl
|
|
1558
|
+
@pytest.mark.cwl
|
|
1392
1559
|
@pytest.mark.cwl_small_log_dir
|
|
1393
1560
|
def test_workflow_echo_string_scatter_stderr_log_dir(tmp_path: Path) -> None:
|
|
1394
1561
|
log_dir = tmp_path / "cwl-logs"
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
new_file_loc = out_base["location"]
|
|
1419
|
-
assert (
|
|
1420
|
-
new_file_loc == file["location"]
|
|
1421
|
-
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1562
|
+
with get_data("test/cwl/echo_string_scatter_capture_stdout.cwl") as cwl_file:
|
|
1563
|
+
cmd = [
|
|
1564
|
+
"toil-cwl-runner",
|
|
1565
|
+
f"--jobStore={tmp_path / 'jobstore'}",
|
|
1566
|
+
"--strict-memory-limit",
|
|
1567
|
+
f"--log-dir={log_dir}",
|
|
1568
|
+
str(cwl_file),
|
|
1569
|
+
]
|
|
1570
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1571
|
+
outputs = json.loads(p.stdout)
|
|
1572
|
+
out_list = outputs["list_out"]
|
|
1573
|
+
assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
|
|
1574
|
+
out_base = outputs["list_out"][0]
|
|
1575
|
+
# This is a test on the scatter functionality and stdout.
|
|
1576
|
+
# Each value of scatter should generate a separate file in the output.
|
|
1577
|
+
for index, file in enumerate(out_list):
|
|
1578
|
+
if index > 0:
|
|
1579
|
+
new_file_loc = out_base["location"] + f"_{index + 1}"
|
|
1580
|
+
else:
|
|
1581
|
+
new_file_loc = out_base["location"]
|
|
1582
|
+
assert (
|
|
1583
|
+
new_file_loc == file["location"]
|
|
1584
|
+
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1422
1585
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1586
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1587
|
+
assert p.returncode == 0
|
|
1425
1588
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1589
|
+
assert log_dir.exists()
|
|
1590
|
+
scatter_0 = log_dir / "echo-test-scatter.0.scatter"
|
|
1591
|
+
scatter_1 = log_dir / "echo-test-scatter.1.scatter"
|
|
1592
|
+
list_0 = log_dir / "echo-test-scatter.0.list"
|
|
1593
|
+
list_1 = log_dir / "echo-test-scatter.1.list"
|
|
1594
|
+
assert scatter_0.exists()
|
|
1595
|
+
assert scatter_1.exists()
|
|
1596
|
+
assert list_0.exists()
|
|
1597
|
+
assert list_1.exists()
|
|
1435
1598
|
|
|
1436
1599
|
|
|
1437
1600
|
@needs_cwl
|
|
1601
|
+
@pytest.mark.cwl
|
|
1438
1602
|
@pytest.mark.cwl_small_log_dir
|
|
1439
1603
|
def test_log_dir_echo_no_output(tmp_path: Path) -> None:
|
|
1440
1604
|
log_dir = tmp_path / "cwl-logs"
|
|
1441
|
-
job_store = "test_log_dir_echo_no_output"
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
result = next(subdir.iterdir())
|
|
1463
|
-
assert result.name == "out.txt"
|
|
1464
|
-
output = open(result).read()
|
|
1465
|
-
assert "hello" in output
|
|
1605
|
+
job_store = tmp_path / "test_log_dir_echo_no_output"
|
|
1606
|
+
with get_data("test/cwl/echo-stdout-log-dir.cwl") as cwl_file:
|
|
1607
|
+
cmd = [
|
|
1608
|
+
"toil-cwl-runner",
|
|
1609
|
+
f"--jobStore={job_store}",
|
|
1610
|
+
"--strict-memory-limit",
|
|
1611
|
+
f"--log-dir={str(log_dir)}",
|
|
1612
|
+
str(cwl_file),
|
|
1613
|
+
]
|
|
1614
|
+
subprocess.run(cmd)
|
|
1615
|
+
|
|
1616
|
+
assert log_dir.exists()
|
|
1617
|
+
assert sum(1 for _ in log_dir.iterdir()) == 1
|
|
1618
|
+
|
|
1619
|
+
subdir = next(log_dir.iterdir())
|
|
1620
|
+
assert subdir.name == "echo"
|
|
1621
|
+
assert subdir.is_dir()
|
|
1622
|
+
assert sum(1 for _ in subdir.iterdir()) == 1
|
|
1623
|
+
result = next(subdir.iterdir())
|
|
1624
|
+
assert result.name == "out.txt"
|
|
1625
|
+
assert "hello" in result.read_text()
|
|
1466
1626
|
|
|
1467
1627
|
|
|
1468
1628
|
@needs_cwl
|
|
1629
|
+
@pytest.mark.cwl
|
|
1469
1630
|
@pytest.mark.cwl_small_log_dir
|
|
1470
1631
|
def test_log_dir_echo_stderr(tmp_path: Path) -> None:
|
|
1471
1632
|
log_dir = tmp_path / "cwl-logs"
|
|
1633
|
+
log_dir.mkdir()
|
|
1634
|
+
with get_data("test/cwl/echo-stderr.cwl") as cwl_file:
|
|
1635
|
+
cmd = [
|
|
1636
|
+
"toil-cwl-runner",
|
|
1637
|
+
f"--jobStore={str(tmp_path / 'test_log_dir_echo_stderr')}",
|
|
1638
|
+
"--strict-memory-limit",
|
|
1639
|
+
"--force-docker-pull",
|
|
1640
|
+
"--clean=always",
|
|
1641
|
+
f"--log-dir={str(log_dir)}",
|
|
1642
|
+
str(cwl_file),
|
|
1643
|
+
]
|
|
1644
|
+
subprocess.run(cmd)
|
|
1645
|
+
tmp_path = log_dir
|
|
1472
1646
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
stdout, stderr = p.communicate()
|
|
1484
|
-
tmp_path = log_dir
|
|
1485
|
-
|
|
1486
|
-
assert len(list(tmp_path.iterdir())) == 1
|
|
1487
|
-
|
|
1488
|
-
subdir = next(tmp_path.iterdir())
|
|
1489
|
-
assert subdir.name == "echo-stderr.cwl"
|
|
1490
|
-
assert subdir.is_dir()
|
|
1491
|
-
assert len(list(subdir.iterdir())) == 1
|
|
1492
|
-
result = next(subdir.iterdir())
|
|
1493
|
-
assert result.name == "out.txt"
|
|
1494
|
-
output = open(result).read()
|
|
1495
|
-
assert output == "hello\n"
|
|
1647
|
+
assert len(list(tmp_path.iterdir())) == 1
|
|
1648
|
+
|
|
1649
|
+
subdir = next(tmp_path.iterdir())
|
|
1650
|
+
assert subdir.name == "echo-stderr.cwl"
|
|
1651
|
+
assert subdir.is_dir()
|
|
1652
|
+
assert len(list(subdir.iterdir())) == 1
|
|
1653
|
+
result = next(subdir.iterdir())
|
|
1654
|
+
assert result.name == "out.txt"
|
|
1655
|
+
output = open(result).read()
|
|
1656
|
+
assert output == "hello\n"
|
|
1496
1657
|
|
|
1497
1658
|
|
|
1498
1659
|
# TODO: It's not clear how this test tests filename conflict resolution; it
|
|
1499
1660
|
# seems like it runs a python script to copy some files and makes sure the
|
|
1500
1661
|
# workflow doesn't fail.
|
|
1501
1662
|
@needs_cwl
|
|
1663
|
+
@pytest.mark.cwl
|
|
1502
1664
|
@pytest.mark.cwl_small_log_dir
|
|
1503
1665
|
def test_filename_conflict_resolution(tmp_path: Path) -> None:
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
cwl_inputs = ["--msin", input]
|
|
1517
|
-
cmd = [toil] + options + [cwl] + cwl_inputs
|
|
1518
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1519
|
-
stdout, stderr = p.communicate()
|
|
1520
|
-
assert b"Finished toil run successfully" in stderr
|
|
1521
|
-
assert p.returncode == 0
|
|
1666
|
+
with get_data("test/cwl/test_filename_conflict_resolution.cwl") as cwl_file:
|
|
1667
|
+
with get_data("test/cwl/test_filename_conflict_resolution.ms") as msin:
|
|
1668
|
+
cmd = [
|
|
1669
|
+
"toil-cwl-runner",
|
|
1670
|
+
f"--outdir={tmp_path}",
|
|
1671
|
+
str(cwl_file),
|
|
1672
|
+
"--msin",
|
|
1673
|
+
str(msin),
|
|
1674
|
+
]
|
|
1675
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1676
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1677
|
+
assert p.returncode == 0
|
|
1522
1678
|
|
|
1523
1679
|
|
|
1524
1680
|
@needs_cwl
|
|
1681
|
+
@pytest.mark.cwl
|
|
1525
1682
|
@pytest.mark.cwl_small_log_dir
|
|
1526
1683
|
def test_filename_conflict_resolution_3_or_more(tmp_path: Path) -> None:
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1536
|
-
stdout, stderr = p.communicate()
|
|
1537
|
-
assert b"Finished toil run successfully" in stderr
|
|
1538
|
-
assert p.returncode == 0
|
|
1539
|
-
assert (
|
|
1540
|
-
len(os.listdir(out_dir)) == 9
|
|
1541
|
-
), "All 9 files made by the scatter should be in the directory"
|
|
1684
|
+
with get_data("test/cwl/scatter_duplicate_outputs.cwl") as cwl_file:
|
|
1685
|
+
cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
|
|
1686
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1687
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1688
|
+
assert p.returncode == 0
|
|
1689
|
+
assert (
|
|
1690
|
+
sum(1 for _ in tmp_path.iterdir()) == 9
|
|
1691
|
+
), f"All 9 files made by the scatter should be in the directory: {tmp_path}"
|
|
1542
1692
|
|
|
1543
1693
|
|
|
1544
1694
|
@needs_cwl
|
|
1545
1695
|
@needs_docker
|
|
1696
|
+
@pytest.mark.cwl
|
|
1697
|
+
@pytest.mark.docker
|
|
1546
1698
|
@pytest.mark.cwl_small_log_dir
|
|
1547
1699
|
def test_filename_conflict_detection(tmp_path: Path) -> None:
|
|
1548
1700
|
"""
|
|
1549
1701
|
Make sure we don't just stage files over each other when using a container.
|
|
1550
1702
|
"""
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
]
|
|
1557
|
-
cwl = os.path.join(
|
|
1558
|
-
os.path.dirname(__file__), "test_filename_conflict_detection.cwl"
|
|
1559
|
-
)
|
|
1560
|
-
cmd = [toil] + options + [cwl]
|
|
1561
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1562
|
-
stdout, stderr = p.communicate()
|
|
1563
|
-
assert b"File staging conflict" in stderr
|
|
1564
|
-
assert p.returncode != 0
|
|
1703
|
+
with get_data("test/cwl/test_filename_conflict_detection.cwl") as cwl_file:
|
|
1704
|
+
cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
|
|
1705
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1706
|
+
assert "File staging conflict" in p.stderr
|
|
1707
|
+
assert p.returncode != 0
|
|
1565
1708
|
|
|
1566
1709
|
|
|
1567
1710
|
@needs_cwl
|
|
1568
1711
|
@needs_docker
|
|
1712
|
+
@pytest.mark.cwl
|
|
1713
|
+
@pytest.mark.docker
|
|
1569
1714
|
@pytest.mark.cwl_small_log_dir
|
|
1570
1715
|
def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
|
|
1571
1716
|
"""
|
|
@@ -1573,101 +1718,89 @@ def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
|
|
|
1573
1718
|
|
|
1574
1719
|
Specifically, when using a container and the files are at the root of the work dir.
|
|
1575
1720
|
"""
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
]
|
|
1582
|
-
cwl = os.path.join(
|
|
1583
|
-
os.path.dirname(__file__), "test_filename_conflict_detection_at_root.cwl"
|
|
1584
|
-
)
|
|
1585
|
-
cmd = [toil] + options + [cwl]
|
|
1586
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1587
|
-
stdout, stderr = p.communicate()
|
|
1588
|
-
assert b"File staging conflict" in stderr
|
|
1589
|
-
assert p.returncode != 0
|
|
1721
|
+
with get_data("test/cwl/test_filename_conflict_detection_at_root.cwl") as cwl_file:
|
|
1722
|
+
cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
|
|
1723
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1724
|
+
assert "File staging conflict" in p.stderr
|
|
1725
|
+
assert p.returncode != 0
|
|
1590
1726
|
|
|
1591
1727
|
|
|
1592
1728
|
@needs_cwl
|
|
1729
|
+
@pytest.mark.cwl
|
|
1593
1730
|
@pytest.mark.cwl_small
|
|
1594
|
-
def test_pick_value_with_one_null_value(
|
|
1731
|
+
def test_pick_value_with_one_null_value(
|
|
1732
|
+
caplog: pytest.LogCaptureFixture, tmp_path: Path
|
|
1733
|
+
) -> None:
|
|
1595
1734
|
"""
|
|
1596
1735
|
Make sure toil-cwl-runner does not false log a warning when pickValue is
|
|
1597
1736
|
used but outputSource only contains one null value. See: #3991.
|
|
1598
1737
|
"""
|
|
1599
1738
|
from toil.cwl import cwltoil
|
|
1600
1739
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
not in line
|
|
1611
|
-
)
|
|
1740
|
+
with get_data("test/cwl/conditional_wf.cwl") as cwl_file:
|
|
1741
|
+
with get_data("test/cwl/conditional_wf.yaml") as job_file:
|
|
1742
|
+
with caplog.at_level(logging.WARNING, logger="toil.cwl.cwltoil"):
|
|
1743
|
+
cwltoil.main([f"--outdir={tmp_path}", str(cwl_file), str(job_file)])
|
|
1744
|
+
for line in caplog.messages:
|
|
1745
|
+
assert (
|
|
1746
|
+
"You had a conditional step that did not run, but you did not use pickValue to handle the skipped input."
|
|
1747
|
+
not in line
|
|
1748
|
+
)
|
|
1612
1749
|
|
|
1613
1750
|
|
|
1614
1751
|
@needs_cwl
|
|
1752
|
+
@pytest.mark.cwl
|
|
1615
1753
|
@pytest.mark.cwl_small
|
|
1616
|
-
def test_workflow_echo_string() -> None:
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
), f"Got wrong output: {stdout2}\nWith error: {stderr2}"
|
|
1631
|
-
assert "Finished toil run successfully" in stderr2
|
|
1632
|
-
assert p.returncode == 0
|
|
1754
|
+
def test_workflow_echo_string(tmp_path: Path) -> None:
|
|
1755
|
+
with get_data("test/cwl/echo_string.cwl") as cwl_file:
|
|
1756
|
+
cmd = [
|
|
1757
|
+
"toil-cwl-runner",
|
|
1758
|
+
f"--jobStore=file:{tmp_path / 'jobstore'}",
|
|
1759
|
+
"--strict-memory-limit",
|
|
1760
|
+
str(cwl_file),
|
|
1761
|
+
]
|
|
1762
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1763
|
+
assert (
|
|
1764
|
+
p.stdout.strip() == "{}"
|
|
1765
|
+
), f"Got wrong output: {p.stdout}\nWith error: {p.stderr}"
|
|
1766
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1767
|
+
assert p.returncode == 0
|
|
1633
1768
|
|
|
1634
1769
|
|
|
1635
1770
|
@needs_cwl
|
|
1771
|
+
@pytest.mark.cwl
|
|
1636
1772
|
@pytest.mark.cwl_small
|
|
1637
|
-
def test_workflow_echo_string_scatter_capture_stdout() -> None:
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
new_file_loc = out_base["location"]
|
|
1662
|
-
assert (
|
|
1663
|
-
new_file_loc == file["location"]
|
|
1664
|
-
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1773
|
+
def test_workflow_echo_string_scatter_capture_stdout(tmp_path: Path) -> None:
|
|
1774
|
+
with get_data("test/cwl/echo_string_scatter_capture_stdout.cwl") as cwl_file:
|
|
1775
|
+
cmd = [
|
|
1776
|
+
"toil-cwl-runner",
|
|
1777
|
+
f"--jobStore=file:{tmp_path / 'jobStore'}",
|
|
1778
|
+
"--strict-memory-limit",
|
|
1779
|
+
str(cwl_file),
|
|
1780
|
+
]
|
|
1781
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1782
|
+
assert len(p.stdout) > 0
|
|
1783
|
+
outputs = json.loads(p.stdout)
|
|
1784
|
+
out_list = outputs["list_out"]
|
|
1785
|
+
assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
|
|
1786
|
+
out_base = outputs["list_out"][0]
|
|
1787
|
+
# This is a test on the scatter functionality and stdout.
|
|
1788
|
+
# Each value of scatter should generate a separate file in the output.
|
|
1789
|
+
for index, file in enumerate(out_list):
|
|
1790
|
+
if index > 0:
|
|
1791
|
+
new_file_loc = out_base["location"] + f"_{index + 1}"
|
|
1792
|
+
else:
|
|
1793
|
+
new_file_loc = out_base["location"]
|
|
1794
|
+
assert (
|
|
1795
|
+
new_file_loc == file["location"]
|
|
1796
|
+
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1665
1797
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1798
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1799
|
+
assert p.returncode == 0
|
|
1668
1800
|
|
|
1669
1801
|
|
|
1670
1802
|
@needs_cwl
|
|
1803
|
+
@pytest.mark.cwl
|
|
1671
1804
|
@pytest.mark.cwl_small
|
|
1672
1805
|
def test_visit_top_cwl_class() -> None:
|
|
1673
1806
|
structure = {
|
|
@@ -1719,6 +1852,7 @@ def test_visit_top_cwl_class() -> None:
|
|
|
1719
1852
|
|
|
1720
1853
|
|
|
1721
1854
|
@needs_cwl
|
|
1855
|
+
@pytest.mark.cwl
|
|
1722
1856
|
@pytest.mark.cwl_small
|
|
1723
1857
|
def test_visit_cwl_class_and_reduce() -> None:
|
|
1724
1858
|
structure = {
|
|
@@ -1780,6 +1914,135 @@ def test_visit_cwl_class_and_reduce() -> None:
|
|
|
1780
1914
|
|
|
1781
1915
|
|
|
1782
1916
|
@needs_cwl
|
|
1917
|
+
@pytest.mark.cwl
|
|
1918
|
+
@pytest.mark.cwl_small
|
|
1919
|
+
def test_trim_mounts_op_nonredundant() -> None:
|
|
1920
|
+
"""
|
|
1921
|
+
Make sure we don't remove all non-duplicate listings
|
|
1922
|
+
"""
|
|
1923
|
+
s: CWLObjectType = {"class": "Directory", "basename": "directory", "listing": [{"class": "File", "basename": "file", "contents": "hello world"}]}
|
|
1924
|
+
remove_redundant_mounts(s)
|
|
1925
|
+
|
|
1926
|
+
# nothing should have been removed
|
|
1927
|
+
assert isinstance(s['listing'], list)
|
|
1928
|
+
assert len(s['listing']) == 1
|
|
1929
|
+
|
|
1930
|
+
@needs_cwl
|
|
1931
|
+
@pytest.mark.cwl
|
|
1932
|
+
@pytest.mark.cwl_small
|
|
1933
|
+
def test_trim_mounts_op_redundant() -> None:
|
|
1934
|
+
"""
|
|
1935
|
+
Make sure we remove all duplicate listings
|
|
1936
|
+
"""
|
|
1937
|
+
s: CWLObjectType = {
|
|
1938
|
+
"class": "Directory",
|
|
1939
|
+
"location": "file:///home/heaucques/Documents/toil/test_dir",
|
|
1940
|
+
"basename": "test_dir",
|
|
1941
|
+
"listing": [
|
|
1942
|
+
{
|
|
1943
|
+
"class": "Directory",
|
|
1944
|
+
"location": "file:///home/heaucques/Documents/toil/test_dir/nested_dir",
|
|
1945
|
+
"basename": "nested_dir",
|
|
1946
|
+
"listing": [],
|
|
1947
|
+
"path": "/home/heaucques/Documents/toil/test_dir/nested_dir"
|
|
1948
|
+
},
|
|
1949
|
+
{
|
|
1950
|
+
"class": "File",
|
|
1951
|
+
"location": "file:///home/heaucques/Documents/toil/test_dir/test_file",
|
|
1952
|
+
"basename": "test_file",
|
|
1953
|
+
"size": 0,
|
|
1954
|
+
"nameroot": "test_file",
|
|
1955
|
+
"nameext": "",
|
|
1956
|
+
"path": "/home/heaucques/Documents/toil/test_dir/test_file",
|
|
1957
|
+
"checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
|
1958
|
+
}
|
|
1959
|
+
],
|
|
1960
|
+
"path": "/home/heaucques/Documents/toil/test_dir"
|
|
1961
|
+
}
|
|
1962
|
+
remove_redundant_mounts(s)
|
|
1963
|
+
|
|
1964
|
+
# everything should have been removed
|
|
1965
|
+
assert isinstance(s['listing'], list)
|
|
1966
|
+
assert len(s['listing']) == 0
|
|
1967
|
+
|
|
1968
|
+
@needs_cwl
|
|
1969
|
+
@pytest.mark.cwl
|
|
1970
|
+
@pytest.mark.cwl_small
|
|
1971
|
+
def test_trim_mounts_op_partially_redundant() -> None:
|
|
1972
|
+
"""
|
|
1973
|
+
Make sure we remove only the redundant listings in the CWL object and leave nonredundant listings intact
|
|
1974
|
+
"""
|
|
1975
|
+
s: CWLObjectType = {
|
|
1976
|
+
"class": "Directory",
|
|
1977
|
+
"location": "file:///home/heaucques/Documents/toil/test_dir",
|
|
1978
|
+
"basename": "test_dir",
|
|
1979
|
+
"listing": [
|
|
1980
|
+
{
|
|
1981
|
+
"class": "Directory",
|
|
1982
|
+
"location": "file:///home/heaucques/Documents/thing",
|
|
1983
|
+
"basename": "thing2",
|
|
1984
|
+
"listing": [],
|
|
1985
|
+
"path": "/home/heaucques/Documents/toil/thing2"
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
"class": "File",
|
|
1989
|
+
"location": "file:///home/heaucques/Documents/toil/test_dir/test_file",
|
|
1990
|
+
"basename": "test_file",
|
|
1991
|
+
"size": 0,
|
|
1992
|
+
"nameroot": "test_file",
|
|
1993
|
+
"nameext": "",
|
|
1994
|
+
"path": "/home/heaucques/Documents/toil/test_dir/test_file",
|
|
1995
|
+
"checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709"
|
|
1996
|
+
}
|
|
1997
|
+
],
|
|
1998
|
+
"path": "/home/heaucques/Documents/toil/test_dir"
|
|
1999
|
+
}
|
|
2000
|
+
remove_redundant_mounts(s)
|
|
2001
|
+
|
|
2002
|
+
# everything except the nested directory should be removed
|
|
2003
|
+
assert isinstance(s['listing'], list)
|
|
2004
|
+
assert len(s['listing']) == 1
|
|
2005
|
+
|
|
2006
|
+
@needs_cwl
|
|
2007
|
+
@pytest.mark.cwl
|
|
2008
|
+
@pytest.mark.cwl_small
|
|
2009
|
+
def test_trim_mounts_op_mixed_urls_and_paths() -> None:
|
|
2010
|
+
"""
|
|
2011
|
+
Ensure we remove redundant listings in certain edge cases
|
|
2012
|
+
"""
|
|
2013
|
+
# Edge cases around encoding:
|
|
2014
|
+
# Ensure URL decoded file URIs match the bare path equivalent. Both of these paths should have the same shared directory
|
|
2015
|
+
s: CWLObjectType = {"class": "Directory", "basename": "123", "location": "file:///tmp/%25/123", "listing": [{"class": "File", "path": "/tmp/%/123/456", "basename": "456"}]}
|
|
2016
|
+
remove_redundant_mounts(s)
|
|
2017
|
+
assert isinstance(s['listing'], list)
|
|
2018
|
+
assert len(s['listing']) == 0
|
|
2019
|
+
|
|
2020
|
+
@needs_cwl
|
|
2021
|
+
@pytest.mark.cwl
|
|
2022
|
+
@pytest.mark.cwl_small
|
|
2023
|
+
def test_trim_mounts_op_decodable_paths() -> None:
|
|
2024
|
+
""""""
|
|
2025
|
+
# Ensure path names don't get unnecessarily decoded
|
|
2026
|
+
s: CWLObjectType = {"class": "Directory", "basename": "dir", "path": "/tmp/cat%2Ftag/dir", "listing": [{"class": "File", "path": "/tmp/cat/tag/dir/file", "basename": "file"}]}
|
|
2027
|
+
remove_redundant_mounts(s)
|
|
2028
|
+
assert isinstance(s['listing'], list)
|
|
2029
|
+
assert len(s['listing']) == 1
|
|
2030
|
+
|
|
2031
|
+
@needs_cwl
|
|
2032
|
+
@pytest.mark.cwl
|
|
2033
|
+
@pytest.mark.cwl_small
|
|
2034
|
+
def test_trim_mounts_op_multiple_encodings() -> None:
|
|
2035
|
+
# Ensure differently encoded URLs are properly decoded
|
|
2036
|
+
s: CWLObjectType = {"class": "Directory", "basename": "dir", "location": "file:///tmp/cat%2Ftag/dir", "listing": [{"class": "File", "location": "file:///tmp/cat%2ftag/dir/file", "basename": "file"}]}
|
|
2037
|
+
remove_redundant_mounts(s)
|
|
2038
|
+
assert isinstance(s['listing'], list)
|
|
2039
|
+
assert len(s['listing']) == 0
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
|
|
2044
|
+
@needs_cwl
|
|
2045
|
+
@pytest.mark.cwl
|
|
1783
2046
|
@pytest.mark.cwl_small
|
|
1784
2047
|
def test_download_structure(tmp_path: Path) -> None:
|
|
1785
2048
|
"""
|
|
@@ -1804,7 +2067,7 @@ def test_download_structure(tmp_path: Path) -> None:
|
|
|
1804
2067
|
}
|
|
1805
2068
|
|
|
1806
2069
|
# Say where to put it on the filesystem
|
|
1807
|
-
to_dir =
|
|
2070
|
+
to_dir = tmp_path
|
|
1808
2071
|
|
|
1809
2072
|
# Make a fake file store
|
|
1810
2073
|
file_store = Mock(AbstractFileStore)
|
|
@@ -1817,7 +2080,7 @@ def test_download_structure(tmp_path: Path) -> None:
|
|
|
1817
2080
|
existing: dict[str, str] = {}
|
|
1818
2081
|
|
|
1819
2082
|
# Do the download
|
|
1820
|
-
download_structure(file_store, index, existing, structure, to_dir)
|
|
2083
|
+
download_structure(file_store, index, existing, structure, str(to_dir))
|
|
1821
2084
|
|
|
1822
2085
|
# Check the results
|
|
1823
2086
|
# 3 files should be made
|
|
@@ -1826,61 +2089,53 @@ def test_download_structure(tmp_path: Path) -> None:
|
|
|
1826
2089
|
assert len(existing) == 2
|
|
1827
2090
|
|
|
1828
2091
|
# Make sure that the index contents (path to URI) are correct
|
|
1829
|
-
assert
|
|
1830
|
-
assert
|
|
1831
|
-
assert
|
|
2092
|
+
assert str(to_dir / "dir1/dir2/f1") in index
|
|
2093
|
+
assert str(to_dir / "dir1/dir2/f1again") in index
|
|
2094
|
+
assert str(to_dir / "anotherfile") in index
|
|
1832
2095
|
assert (
|
|
1833
|
-
index[
|
|
2096
|
+
index[str(to_dir / "dir1/dir2/f1")]
|
|
1834
2097
|
== cast(
|
|
1835
2098
|
DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
|
|
1836
2099
|
)["f1"]
|
|
1837
2100
|
)
|
|
1838
2101
|
assert (
|
|
1839
|
-
index[
|
|
2102
|
+
index[str(to_dir / "dir1/dir2/f1again")]
|
|
1840
2103
|
== cast(
|
|
1841
2104
|
DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
|
|
1842
2105
|
)["f1again"]
|
|
1843
2106
|
)
|
|
1844
|
-
assert index[
|
|
2107
|
+
assert index[str(to_dir / "anotherfile")] == structure["anotherfile"]
|
|
1845
2108
|
|
|
1846
2109
|
# And the existing contents (URI to path)
|
|
1847
2110
|
assert "toilfile:" + fid1.pack() in existing
|
|
1848
2111
|
assert "toilfile:" + fid2.pack() in existing
|
|
1849
2112
|
assert existing["toilfile:" + fid1.pack()] in [
|
|
1850
|
-
|
|
1851
|
-
|
|
2113
|
+
str(to_dir / "dir1/dir2/f1"),
|
|
2114
|
+
str(to_dir / "dir1/dir2/f1again"),
|
|
1852
2115
|
]
|
|
1853
|
-
assert existing["toilfile:" + fid2.pack()] ==
|
|
2116
|
+
assert existing["toilfile:" + fid2.pack()] == str(to_dir / "anotherfile")
|
|
1854
2117
|
|
|
1855
2118
|
# The directory structure should be created for real
|
|
1856
|
-
assert
|
|
1857
|
-
assert
|
|
1858
|
-
assert
|
|
1859
|
-
assert
|
|
2119
|
+
assert (to_dir / "dir1").is_dir()
|
|
2120
|
+
assert (to_dir / "dir1/dir2").is_dir()
|
|
2121
|
+
assert (to_dir / "dir1/dir2/dir2sub").is_dir()
|
|
2122
|
+
assert (to_dir / "dir1/dir3").is_dir()
|
|
1860
2123
|
|
|
1861
2124
|
# The file store should have been asked to do the download
|
|
1862
2125
|
file_store.readGlobalFile.assert_has_calls(
|
|
1863
2126
|
[
|
|
1864
|
-
call(fid1,
|
|
1865
|
-
call(fid1,
|
|
1866
|
-
call(fid2,
|
|
2127
|
+
call(fid1, str(to_dir / "dir1/dir2/f1"), symlink=False),
|
|
2128
|
+
call(fid1, str(to_dir / "dir1/dir2/f1again"), symlink=False),
|
|
2129
|
+
call(fid2, str(to_dir / "anotherfile"), symlink=False),
|
|
1867
2130
|
],
|
|
1868
2131
|
any_order=True,
|
|
1869
2132
|
)
|
|
1870
2133
|
|
|
1871
2134
|
|
|
1872
2135
|
@needs_cwl
|
|
2136
|
+
@pytest.mark.cwl
|
|
1873
2137
|
@pytest.mark.timeout(300)
|
|
1874
2138
|
def test_import_on_workers() -> None:
|
|
1875
|
-
args = [
|
|
1876
|
-
"src/toil/test/cwl/download.cwl",
|
|
1877
|
-
"src/toil/test/cwl/download_file.json",
|
|
1878
|
-
"--runImportsOnWorkers",
|
|
1879
|
-
"--importWorkersDisk=10MiB",
|
|
1880
|
-
"--realTimeLogging=True",
|
|
1881
|
-
"--logLevel=INFO",
|
|
1882
|
-
"--logColors=False",
|
|
1883
|
-
]
|
|
1884
2139
|
from toil.cwl import cwltoil
|
|
1885
2140
|
|
|
1886
2141
|
detector = ImportWorkersMessageHandler()
|
|
@@ -1888,10 +2143,49 @@ def test_import_on_workers() -> None:
|
|
|
1888
2143
|
# Set up a log message detector to the root logger
|
|
1889
2144
|
logging.getLogger().addHandler(detector)
|
|
1890
2145
|
|
|
1891
|
-
|
|
2146
|
+
with get_data("test/cwl/download.cwl") as cwl_file:
|
|
2147
|
+
with get_data("test/cwl/directory/directory/file.txt") as file_path:
|
|
2148
|
+
# To make sure we see every job issued with a leader log message
|
|
2149
|
+
# that we can then detect for the test, we need to turn off
|
|
2150
|
+
# chaining.
|
|
2151
|
+
args = [
|
|
2152
|
+
"--runImportsOnWorkers",
|
|
2153
|
+
"--importWorkersDisk=10MiB",
|
|
2154
|
+
"--realTimeLogging=True",
|
|
2155
|
+
"--logLevel=INFO",
|
|
2156
|
+
"--logColors=False",
|
|
2157
|
+
"--disableChaining=True",
|
|
2158
|
+
str(cwl_file),
|
|
2159
|
+
"--input",
|
|
2160
|
+
str(file_path),
|
|
2161
|
+
]
|
|
2162
|
+
cwltoil.main(args)
|
|
2163
|
+
|
|
2164
|
+
assert detector.detected is True
|
|
1892
2165
|
|
|
1893
|
-
|
|
2166
|
+
@needs_cwl
|
|
2167
|
+
@pytest.mark.cwl
|
|
2168
|
+
@pytest.mark.cwl_small
|
|
2169
|
+
def test_missing_tmpdir_and_tmp_outdir(tmp_path: Path) -> None:
|
|
2170
|
+
"""
|
|
2171
|
+
tmpdir_prefix and tmp_outdir_prefix do not need to exist prior to running the workflow
|
|
2172
|
+
"""
|
|
2173
|
+
tmpdir_prefix = os.path.join(tmp_path, "tmpdir/blah")
|
|
2174
|
+
tmp_outdir_prefix = os.path.join(tmp_path, "tmp_outdir/blah")
|
|
1894
2175
|
|
|
2176
|
+
assert not os.path.exists(os.path.dirname(tmpdir_prefix))
|
|
2177
|
+
assert not os.path.exists(os.path.dirname(tmp_outdir_prefix))
|
|
2178
|
+
with get_data("test/cwl/echo_string.cwl") as cwl_file:
|
|
2179
|
+
cmd = [
|
|
2180
|
+
"toil-cwl-runner",
|
|
2181
|
+
f"--jobStore=file:{tmp_path / 'jobstore'}",
|
|
2182
|
+
"--strict-memory-limit",
|
|
2183
|
+
f'--tmpdir-prefix={tmpdir_prefix}',
|
|
2184
|
+
f'--tmp-outdir-prefix={tmp_outdir_prefix}',
|
|
2185
|
+
str(cwl_file),
|
|
2186
|
+
]
|
|
2187
|
+
p = subprocess.run(cmd)
|
|
2188
|
+
assert p.returncode == 0
|
|
1895
2189
|
|
|
1896
2190
|
# StreamHandler is generic, _typeshed doesn't exist at runtime, do a bit of typing trickery, see https://github.com/python/typeshed/issues/5680
|
|
1897
2191
|
if TYPE_CHECKING:
|
|
@@ -1904,7 +2198,7 @@ else:
|
|
|
1904
2198
|
|
|
1905
2199
|
class ImportWorkersMessageHandler(_stream_handler):
|
|
1906
2200
|
"""
|
|
1907
|
-
Detect
|
|
2201
|
+
Detect whether any WorkerImportJob jobs ran during a workflow.
|
|
1908
2202
|
"""
|
|
1909
2203
|
|
|
1910
2204
|
def __init__(self) -> None:
|
|
@@ -1913,7 +2207,18 @@ class ImportWorkersMessageHandler(_stream_handler):
|
|
|
1913
2207
|
super().__init__(sys.stderr)
|
|
1914
2208
|
|
|
1915
2209
|
def emit(self, record: logging.LogRecord) -> None:
|
|
1916
|
-
|
|
1917
|
-
|
|
2210
|
+
# We get the job name from the class since we already started failing
|
|
2211
|
+
# this test once due to it being renamed.
|
|
2212
|
+
try:
|
|
2213
|
+
formatted = record.getMessage()
|
|
2214
|
+
except TypeError as e:
|
|
2215
|
+
# The log message has the wrong number of items for its fields.
|
|
2216
|
+
# Complain in a way we could figure out.
|
|
2217
|
+
raise RuntimeError(
|
|
2218
|
+
f"Log message {record.msg} has wrong number of "
|
|
2219
|
+
f"fields in {record.args}"
|
|
2220
|
+
) from e
|
|
2221
|
+
if formatted.startswith(
|
|
2222
|
+
f"Issued job '{WorkerImportJob.__name__}'"
|
|
1918
2223
|
):
|
|
1919
2224
|
self.detected = True
|