toil 8.0.0__py3-none-any.whl → 8.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toil/__init__.py +4 -39
- 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/options.py +1 -0
- toil/batchSystems/singleMachine.py +1 -1
- toil/batchSystems/slurm.py +229 -84
- toil/bus.py +5 -3
- toil/common.py +198 -54
- toil/cwl/cwltoil.py +32 -11
- toil/job.py +110 -86
- toil/jobStores/abstractJobStore.py +24 -3
- toil/jobStores/aws/jobStore.py +46 -10
- toil/jobStores/fileJobStore.py +25 -1
- toil/jobStores/googleJobStore.py +104 -30
- toil/leader.py +9 -0
- toil/lib/accelerators.py +3 -1
- toil/lib/aws/session.py +14 -3
- toil/lib/aws/utils.py +92 -35
- toil/lib/aws/utils.py.orig +504 -0
- toil/lib/bioio.py +1 -1
- toil/lib/docker.py +252 -91
- toil/lib/dockstore.py +387 -0
- toil/lib/ec2nodes.py +3 -2
- toil/lib/exceptions.py +5 -3
- toil/lib/history.py +1345 -0
- toil/lib/history_submission.py +695 -0
- toil/lib/io.py +56 -23
- toil/lib/misc.py +25 -1
- toil/lib/resources.py +2 -1
- toil/lib/retry.py +10 -10
- toil/lib/threading.py +11 -10
- toil/lib/{integration.py → trs.py} +95 -46
- toil/lib/web.py +38 -0
- toil/options/common.py +25 -2
- toil/options/cwl.py +10 -0
- toil/options/wdl.py +11 -0
- toil/provisioners/gceProvisioner.py +4 -4
- toil/server/api_spec/LICENSE +201 -0
- toil/server/api_spec/README.rst +5 -0
- toil/server/cli/wes_cwl_runner.py +5 -4
- toil/server/utils.py +2 -3
- toil/statsAndLogging.py +35 -1
- toil/test/__init__.py +275 -115
- toil/test/batchSystems/batchSystemTest.py +227 -205
- toil/test/batchSystems/test_slurm.py +199 -2
- 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 +39 -0
- toil/test/cwl/cwlTest.py +1015 -780
- 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/optional-file.cwl +18 -0
- toil/test/cwl/preemptible_expression.json +1 -0
- toil/test/cwl/revsort-job-missing.json +6 -0
- toil/test/cwl/revsort-job.json +6 -0
- toil/test/cwl/s3_secondary_file.json +16 -0
- toil/test/cwl/seqtk_seq_job.json +6 -0
- toil/test/cwl/stream.json +6 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
- toil/test/cwl/whale.txt +16 -0
- toil/test/docs/scripts/example_alwaysfail.py +38 -0
- toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
- toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
- toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
- toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
- toil/test/docs/scripts/tutorial_arguments.py +23 -0
- toil/test/docs/scripts/tutorial_debugging.patch +12 -0
- toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
- toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
- toil/test/docs/scripts/tutorial_docker.py +20 -0
- toil/test/docs/scripts/tutorial_dynamic.py +24 -0
- toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
- toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
- toil/test/docs/scripts/tutorial_helloworld.py +15 -0
- toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
- toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
- toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
- toil/test/docs/scripts/tutorial_managing.py +29 -0
- toil/test/docs/scripts/tutorial_managing2.py +56 -0
- toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
- toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
- toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
- toil/test/docs/scripts/tutorial_promises.py +25 -0
- toil/test/docs/scripts/tutorial_promises2.py +30 -0
- toil/test/docs/scripts/tutorial_quickstart.py +22 -0
- toil/test/docs/scripts/tutorial_requirements.py +44 -0
- toil/test/docs/scripts/tutorial_services.py +45 -0
- toil/test/docs/scripts/tutorial_staging.py +45 -0
- toil/test/docs/scripts/tutorial_stats.py +64 -0
- toil/test/lib/aws/test_iam.py +3 -1
- toil/test/lib/dockerTest.py +205 -122
- toil/test/lib/test_history.py +236 -0
- toil/test/lib/test_trs.py +161 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
- toil/test/provisioners/clusterTest.py +4 -4
- toil/test/provisioners/gceProvisionerTest.py +16 -14
- toil/test/sort/sort.py +4 -1
- toil/test/src/busTest.py +17 -17
- toil/test/src/deferredFunctionTest.py +145 -132
- toil/test/src/importExportFileTest.py +71 -63
- toil/test/src/jobEncapsulationTest.py +27 -28
- toil/test/src/jobServiceTest.py +149 -133
- toil/test/src/jobTest.py +219 -211
- toil/test/src/miscTests.py +66 -60
- toil/test/src/promisedRequirementTest.py +163 -169
- toil/test/src/regularLogTest.py +24 -24
- toil/test/src/resourceTest.py +82 -76
- toil/test/src/restartDAGTest.py +51 -47
- toil/test/src/resumabilityTest.py +24 -19
- toil/test/src/retainTempDirTest.py +60 -57
- toil/test/src/systemTest.py +17 -13
- toil/test/src/threadingTest.py +29 -32
- toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
- toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
- toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
- toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
- toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
- toil/test/utils/toilDebugTest.py +117 -102
- toil/test/utils/toilKillTest.py +54 -53
- toil/test/utils/utilsTest.py +303 -229
- toil/test/wdl/lint_error.wdl +9 -0
- toil/test/wdl/md5sum/empty_file.json +1 -0
- toil/test/wdl/md5sum/md5sum-gs.json +1 -0
- toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
- toil/test/wdl/md5sum/md5sum.input +1 -0
- toil/test/wdl/md5sum/md5sum.json +1 -0
- toil/test/wdl/md5sum/md5sum.wdl +25 -0
- toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
- toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
- toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
- toil/test/wdl/standard_library/as_map.json +16 -0
- toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
- toil/test/wdl/standard_library/as_pairs.json +7 -0
- toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
- toil/test/wdl/standard_library/ceil.json +3 -0
- toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
- toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
- toil/test/wdl/standard_library/collect_by_key.json +1 -0
- toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
- toil/test/wdl/standard_library/cross.json +11 -0
- toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
- toil/test/wdl/standard_library/flatten.json +7 -0
- toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
- toil/test/wdl/standard_library/floor.json +3 -0
- toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
- toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
- toil/test/wdl/standard_library/keys.json +8 -0
- toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
- toil/test/wdl/standard_library/length.json +7 -0
- toil/test/wdl/standard_library/length_as_input.wdl +16 -0
- toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
- toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
- toil/test/wdl/standard_library/length_invalid.json +3 -0
- toil/test/wdl/standard_library/range.json +3 -0
- toil/test/wdl/standard_library/range_0.json +3 -0
- toil/test/wdl/standard_library/range_as_input.wdl +17 -0
- toil/test/wdl/standard_library/range_invalid.json +3 -0
- toil/test/wdl/standard_library/read_boolean.json +3 -0
- toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_float.json +3 -0
- toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_int.json +3 -0
- toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_json.json +3 -0
- toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_lines.json +3 -0
- toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_map.json +3 -0
- toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_string.json +3 -0
- toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_tsv.json +3 -0
- toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
- toil/test/wdl/standard_library/round.json +3 -0
- toil/test/wdl/standard_library/round_as_command.wdl +16 -0
- toil/test/wdl/standard_library/round_as_input.wdl +16 -0
- toil/test/wdl/standard_library/size.json +3 -0
- toil/test/wdl/standard_library/size_as_command.wdl +17 -0
- toil/test/wdl/standard_library/size_as_output.wdl +36 -0
- toil/test/wdl/standard_library/stderr.json +3 -0
- toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
- toil/test/wdl/standard_library/stdout.json +3 -0
- toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
- toil/test/wdl/standard_library/sub.json +3 -0
- toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
- toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
- toil/test/wdl/standard_library/transpose.json +6 -0
- toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
- toil/test/wdl/standard_library/write_json.json +6 -0
- toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_lines.json +7 -0
- toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_map.json +6 -0
- toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_tsv.json +6 -0
- toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
- toil/test/wdl/standard_library/zip.json +12 -0
- toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
- toil/test/wdl/test.csv +3 -0
- toil/test/wdl/test.tsv +3 -0
- toil/test/wdl/testfiles/croo.wdl +38 -0
- toil/test/wdl/testfiles/drop_files.wdl +62 -0
- toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
- toil/test/wdl/testfiles/empty.txt +0 -0
- toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
- toil/test/wdl/testfiles/random.wdl +66 -0
- toil/test/wdl/testfiles/string_file_coercion.json +1 -0
- toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
- toil/test/wdl/testfiles/test.json +4 -0
- toil/test/wdl/testfiles/test_boolean.txt +1 -0
- toil/test/wdl/testfiles/test_float.txt +1 -0
- toil/test/wdl/testfiles/test_int.txt +1 -0
- toil/test/wdl/testfiles/test_lines.txt +5 -0
- toil/test/wdl/testfiles/test_map.txt +2 -0
- toil/test/wdl/testfiles/test_string.txt +1 -0
- toil/test/wdl/testfiles/url_to_file.wdl +13 -0
- toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
- toil/test/wdl/testfiles/vocab.json +1 -0
- toil/test/wdl/testfiles/vocab.wdl +66 -0
- toil/test/wdl/testfiles/wait.wdl +34 -0
- toil/test/wdl/wdl_specification/type_pair.json +23 -0
- toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
- toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
- toil/test/wdl/wdl_specification/v1_spec.json +1 -0
- toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
- toil/test/wdl/wdltoil_test.py +681 -408
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/version.py +10 -10
- toil/wdl/wdltoil.py +350 -123
- toil/worker.py +113 -33
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/METADATA +13 -7
- toil-8.2.0.dist-info/RECORD +439 -0
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
- toil/test/lib/test_integration.py +0 -104
- toil-8.0.0.dist-info/RECORD +0 -253
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
- {toil-8.0.0.dist-info → toil-8.2.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
|
|
@@ -50,22 +50,23 @@ from toil.fileStores import FileID
|
|
|
50
50
|
from toil.fileStores.abstractFileStore import AbstractFileStore
|
|
51
51
|
from toil.lib.threading import cpu_count
|
|
52
52
|
from toil.test import (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
get_data,
|
|
54
|
+
)
|
|
55
|
+
from toil.test import (
|
|
56
|
+
pslow as slow,
|
|
57
|
+
pneeds_docker as needs_docker,
|
|
58
|
+
pneeds_cwl as needs_cwl,
|
|
59
|
+
pneeds_aws_s3 as needs_aws_s3,
|
|
60
|
+
pneeds_docker_cuda as needs_docker_cuda,
|
|
61
|
+
pneeds_gridengine as needs_gridengine,
|
|
62
|
+
pneeds_kubernetes as needs_kubernetes,
|
|
63
|
+
pneeds_local_cuda as needs_local_cuda,
|
|
64
|
+
pneeds_lsf as needs_lsf,
|
|
65
|
+
pneeds_mesos as needs_mesos,
|
|
66
|
+
pneeds_online as needs_online,
|
|
67
|
+
pneeds_slurm as needs_slurm,
|
|
68
|
+
pneeds_torque as needs_torque,
|
|
69
|
+
pneeds_wes_server as needs_wes_server,
|
|
69
70
|
)
|
|
70
71
|
|
|
71
72
|
log = logging.getLogger(__name__)
|
|
@@ -222,72 +223,58 @@ def run_conformance_tests(
|
|
|
222
223
|
log.info("Unsuccessful return code is OK")
|
|
223
224
|
|
|
224
225
|
|
|
225
|
-
TesterFuncType = Callable[[
|
|
226
|
+
TesterFuncType = Callable[[Path, Path, "CWLObjectType", Path], None]
|
|
226
227
|
|
|
227
228
|
|
|
228
229
|
@needs_cwl
|
|
229
|
-
|
|
230
|
+
@pytest.mark.cwl
|
|
231
|
+
class TestCWLWorkflow:
|
|
230
232
|
"""
|
|
231
233
|
CWL tests included in Toil that don't involve the whole CWL conformance
|
|
232
234
|
test suite. Tests Toil-specific functions like URL types supported for
|
|
233
235
|
inputs.
|
|
234
236
|
"""
|
|
235
237
|
|
|
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
238
|
def test_cwl_cmdline_input(self) -> None:
|
|
252
239
|
"""
|
|
253
240
|
Test that running a CWL workflow with inputs specified on the command line passes.
|
|
254
241
|
"""
|
|
255
242
|
from toil.cwl import cwltoil
|
|
256
243
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
244
|
+
with get_data("test/cwl/conditional_wf.cwl") as cwlfile:
|
|
245
|
+
args = [str(cwlfile), "--message", "str", "--sleep", "2"]
|
|
246
|
+
st = StringIO()
|
|
247
|
+
# If the workflow runs, it must have had options
|
|
248
|
+
cwltoil.main(args, stdout=st)
|
|
262
249
|
|
|
263
250
|
def _tester(
|
|
264
251
|
self,
|
|
265
|
-
cwlfile:
|
|
266
|
-
jobfile:
|
|
252
|
+
cwlfile: Path,
|
|
253
|
+
jobfile: Path,
|
|
267
254
|
expect: "CWLObjectType",
|
|
268
|
-
|
|
255
|
+
outdir: Path,
|
|
269
256
|
out_name: str = "output",
|
|
270
|
-
|
|
257
|
+
main_args: Optional[list[str]] = None,
|
|
271
258
|
) -> None:
|
|
272
259
|
from toil.cwl import cwltoil
|
|
273
260
|
|
|
274
261
|
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(
|
|
262
|
+
real_main_args = main_args or []
|
|
263
|
+
real_main_args.extend(
|
|
280
264
|
[
|
|
281
|
-
|
|
282
|
-
|
|
265
|
+
"--logDebug",
|
|
266
|
+
"--outdir",
|
|
267
|
+
str(outdir),
|
|
268
|
+
str(cwlfile),
|
|
269
|
+
str(jobfile),
|
|
283
270
|
]
|
|
284
271
|
)
|
|
285
|
-
cwltoil.main(
|
|
272
|
+
cwltoil.main(real_main_args, stdout=st)
|
|
286
273
|
out = json.loads(st.getvalue())
|
|
287
274
|
out.get(out_name, {}).pop("http://commonwl.org/cwltool#generation", None)
|
|
288
275
|
out.get(out_name, {}).pop("nameext", None)
|
|
289
276
|
out.get(out_name, {}).pop("nameroot", None)
|
|
290
|
-
|
|
277
|
+
assert out == expect
|
|
291
278
|
|
|
292
279
|
for k, v in expect.items():
|
|
293
280
|
if (
|
|
@@ -298,11 +285,11 @@ class CWLWorkflowTest(ToilTest):
|
|
|
298
285
|
):
|
|
299
286
|
# This is a top-level output file.
|
|
300
287
|
# None of our output files should be executable.
|
|
301
|
-
|
|
302
|
-
|
|
288
|
+
assert os.path.exists(v["path"]) is True
|
|
289
|
+
assert (os.stat(v["path"]).st_mode & stat.S_IXUSR) == 0
|
|
303
290
|
|
|
304
291
|
def _debug_worker_tester(
|
|
305
|
-
self, cwlfile:
|
|
292
|
+
self, cwlfile: Path, jobfile: Path, expect: "CWLObjectType", outdir: Path
|
|
306
293
|
) -> None:
|
|
307
294
|
from toil.cwl import cwltoil
|
|
308
295
|
|
|
@@ -311,9 +298,9 @@ class CWLWorkflowTest(ToilTest):
|
|
|
311
298
|
[
|
|
312
299
|
"--debugWorker",
|
|
313
300
|
"--outdir",
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
301
|
+
str(outdir),
|
|
302
|
+
str(cwlfile),
|
|
303
|
+
str(jobfile),
|
|
317
304
|
],
|
|
318
305
|
stdout=st,
|
|
319
306
|
)
|
|
@@ -321,172 +308,216 @@ class CWLWorkflowTest(ToilTest):
|
|
|
321
308
|
out["output"].pop("http://commonwl.org/cwltool#generation", None)
|
|
322
309
|
out["output"].pop("nameext", None)
|
|
323
310
|
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
|
-
)
|
|
332
|
-
|
|
333
|
-
def revsort_no_checksum(self, cwl_filename: str, tester_fn: TesterFuncType) -> None:
|
|
334
|
-
tester_fn(
|
|
335
|
-
"src/toil/test/cwl/" + cwl_filename,
|
|
336
|
-
"src/toil/test/cwl/revsort-job.json",
|
|
337
|
-
self._expected_revsort_nochecksum_output(self.outDir),
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
def download(self, inputs: str, tester_fn: TesterFuncType) -> None:
|
|
341
|
-
input_location = os.path.join("src/toil/test/cwl", inputs)
|
|
342
|
-
tester_fn(
|
|
343
|
-
"src/toil/test/cwl/download.cwl",
|
|
344
|
-
input_location,
|
|
345
|
-
self._expected_download_output(self.outDir),
|
|
346
|
-
)
|
|
311
|
+
assert out == expect
|
|
347
312
|
|
|
348
|
-
def
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
tester_fn
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
|
|
313
|
+
def revsort(
|
|
314
|
+
self, cwl_filename: str, tester_fn: TesterFuncType, out_dir: Path
|
|
315
|
+
) -> None:
|
|
316
|
+
with get_data(f"test/cwl/{cwl_filename}") as cwl_file:
|
|
317
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
318
|
+
tester_fn(
|
|
319
|
+
cwl_file, job_file, self._expected_revsort_output(out_dir), out_dir
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def revsort_no_checksum(
|
|
323
|
+
self, cwl_filename: str, tester_fn: TesterFuncType, out_dir: Path
|
|
324
|
+
) -> None:
|
|
325
|
+
with get_data(f"test/cwl/{cwl_filename}") as cwl_file:
|
|
326
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
327
|
+
tester_fn(
|
|
328
|
+
cwl_file,
|
|
329
|
+
job_file,
|
|
330
|
+
self._expected_revsort_nochecksum_output(out_dir),
|
|
331
|
+
out_dir,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def download(self, inputs: str, tester_fn: TesterFuncType, out_dir: Path) -> None:
|
|
335
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
336
|
+
with get_data("test/cwl/download.cwl") as cwl_file:
|
|
337
|
+
tester_fn(
|
|
338
|
+
cwl_file,
|
|
339
|
+
input_location,
|
|
340
|
+
self._expected_download_output(out_dir),
|
|
341
|
+
out_dir,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def load_contents(
|
|
345
|
+
self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
|
|
346
|
+
) -> None:
|
|
347
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
348
|
+
with get_data("test/cwl/load_contents.cwl") as cwl_file:
|
|
349
|
+
tester_fn(
|
|
350
|
+
cwl_file,
|
|
351
|
+
input_location,
|
|
352
|
+
self._expected_load_contents_output(out_dir),
|
|
353
|
+
out_dir,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def download_directory(
|
|
357
|
+
self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
|
|
358
|
+
) -> None:
|
|
359
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
360
|
+
with get_data("test/cwl/download_directory.cwl") as cwl_file:
|
|
361
|
+
tester_fn(
|
|
362
|
+
cwl_file,
|
|
363
|
+
input_location,
|
|
364
|
+
self._expected_download_output(out_dir),
|
|
365
|
+
out_dir,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def download_subdirectory(
|
|
369
|
+
self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
|
|
370
|
+
) -> None:
|
|
371
|
+
with get_data(f"test/cwl/{inputs}") as input_location:
|
|
372
|
+
with get_data("test/cwl/download_subdirectory.cwl") as cwl_file:
|
|
373
|
+
tester_fn(
|
|
374
|
+
cwl_file,
|
|
375
|
+
input_location,
|
|
376
|
+
self._expected_download_output(out_dir),
|
|
377
|
+
out_dir,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def test_mpi(self, tmp_path: Path) -> None:
|
|
373
381
|
from toil.cwl import cwltoil
|
|
374
382
|
|
|
375
383
|
stdout = StringIO()
|
|
376
|
-
|
|
377
|
-
"
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
384
|
+
with get_data("test/cwl/mock_mpi/fake_mpi.yml") as mpi_config_file:
|
|
385
|
+
with get_data("test/cwl/mpi_simple.cwl") as cwl_file:
|
|
386
|
+
with get_data("test/cwl/mock_mpi/fake_mpi_run.py") as fake_mpi_run:
|
|
387
|
+
main_args = [
|
|
388
|
+
"--logDebug",
|
|
389
|
+
"--outdir",
|
|
390
|
+
str(tmp_path),
|
|
391
|
+
"--enable-dev",
|
|
392
|
+
"--enable-ext",
|
|
393
|
+
"--mpi-config-file",
|
|
394
|
+
str(mpi_config_file),
|
|
395
|
+
str(cwl_file),
|
|
396
|
+
]
|
|
397
|
+
path = os.environ["PATH"]
|
|
398
|
+
os.environ["PATH"] = f"{path}:{fake_mpi_run.parent}"
|
|
399
|
+
cwltoil.main(main_args, stdout=stdout)
|
|
400
|
+
os.environ["PATH"] = path
|
|
401
|
+
stdout_text = stdout.getvalue()
|
|
402
|
+
assert "pids" in stdout_text
|
|
403
|
+
out = json.loads(stdout_text)
|
|
404
|
+
with open(
|
|
405
|
+
out.get("pids", {}).get("location")[len("file://") :]
|
|
406
|
+
) as f:
|
|
407
|
+
two_pids = [int(i) for i in f.read().split()]
|
|
408
|
+
assert len(two_pids) == 2
|
|
409
|
+
assert isinstance(two_pids[0], int)
|
|
410
|
+
assert isinstance(two_pids[1], int)
|
|
395
411
|
|
|
396
412
|
@needs_aws_s3
|
|
397
|
-
|
|
413
|
+
@pytest.mark.aws_s3
|
|
414
|
+
@pytest.mark.online
|
|
415
|
+
def test_s3_as_secondary_file(self, tmp_path: Path) -> None:
|
|
398
416
|
from toil.cwl import cwltoil
|
|
399
417
|
|
|
400
418
|
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?")
|
|
419
|
+
with get_data("test/cwl/s3_secondary_file.cwl") as cwl_file:
|
|
420
|
+
with get_data("test/cwl/s3_secondary_file.json") as inputs_file:
|
|
421
|
+
main_args = ["--outdir", str(tmp_path), str(cwl_file), str(inputs_file)]
|
|
422
|
+
cwltoil.main(main_args, stdout=stdout)
|
|
423
|
+
out = json.loads(stdout.getvalue())
|
|
424
|
+
assert (
|
|
425
|
+
out["output"]["checksum"]
|
|
426
|
+
== "sha1$d14dd02e354918b4776b941d154c18ebc15b9b38"
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
assert out["output"]["size"] == 24
|
|
430
|
+
with open(out["output"]["location"][len("file://") :]) as f:
|
|
431
|
+
assert f.read().strip() == "When is s4 coming out?"
|
|
415
432
|
|
|
416
|
-
|
|
417
|
-
|
|
433
|
+
@needs_docker
|
|
434
|
+
@pytest.mark.docker
|
|
435
|
+
@pytest.mark.online
|
|
436
|
+
def test_run_revsort(self, tmp_path: Path) -> None:
|
|
437
|
+
self.revsort("revsort.cwl", self._tester, tmp_path)
|
|
418
438
|
|
|
419
|
-
|
|
439
|
+
@needs_docker
|
|
440
|
+
@pytest.mark.docker
|
|
441
|
+
@pytest.mark.online
|
|
442
|
+
def test_run_revsort_nochecksum(self, tmp_path: Path) -> None:
|
|
420
443
|
self.revsort_no_checksum(
|
|
421
|
-
"revsort.cwl",
|
|
444
|
+
"revsort.cwl",
|
|
445
|
+
partial(self._tester, main_args=["--no-compute-checksum"]),
|
|
446
|
+
tmp_path,
|
|
422
447
|
)
|
|
423
448
|
|
|
424
|
-
def test_run_revsort_no_container(self) -> None:
|
|
449
|
+
def test_run_revsort_no_container(self, tmp_path: Path) -> None:
|
|
425
450
|
self.revsort(
|
|
426
|
-
"revsort.cwl", partial(self._tester, main_args=["--no-container"])
|
|
451
|
+
"revsort.cwl", partial(self._tester, main_args=["--no-container"]), tmp_path
|
|
427
452
|
)
|
|
428
453
|
|
|
429
|
-
|
|
430
|
-
|
|
454
|
+
@needs_docker
|
|
455
|
+
@pytest.mark.docker
|
|
456
|
+
@pytest.mark.online
|
|
457
|
+
def test_run_revsort2(self, tmp_path: Path) -> None:
|
|
458
|
+
self.revsort("revsort2.cwl", self._tester, tmp_path)
|
|
431
459
|
|
|
432
|
-
|
|
433
|
-
|
|
460
|
+
@needs_docker
|
|
461
|
+
@pytest.mark.docker
|
|
462
|
+
@pytest.mark.online
|
|
463
|
+
def test_run_revsort_debug_worker(self, tmp_path: Path) -> None:
|
|
464
|
+
self.revsort("revsort.cwl", self._debug_worker_tester, tmp_path)
|
|
465
|
+
|
|
466
|
+
@needs_docker
|
|
467
|
+
@pytest.mark.docker
|
|
468
|
+
@pytest.mark.online
|
|
469
|
+
def test_run_colon_output(self, tmp_path: Path) -> None:
|
|
470
|
+
with get_data("test/cwl/colon_test_output.cwl") as cwl_file:
|
|
471
|
+
with get_data("test/cwl/colon_test_output_job.yaml") as inputs_file:
|
|
472
|
+
self._tester(
|
|
473
|
+
cwl_file,
|
|
474
|
+
inputs_file,
|
|
475
|
+
self._expected_colon_output(tmp_path),
|
|
476
|
+
tmp_path,
|
|
477
|
+
out_name="result",
|
|
478
|
+
)
|
|
434
479
|
|
|
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
480
|
@pytest.mark.integrative
|
|
444
|
-
@
|
|
445
|
-
|
|
481
|
+
@needs_docker
|
|
482
|
+
@pytest.mark.docker
|
|
483
|
+
@pytest.mark.online
|
|
484
|
+
def test_run_dockstore_trs(self, tmp_path: Path) -> None:
|
|
446
485
|
from toil.cwl import cwltoil
|
|
447
486
|
|
|
448
487
|
stdout = StringIO()
|
|
449
488
|
main_args = [
|
|
450
489
|
"--outdir",
|
|
451
|
-
|
|
452
|
-
"#workflow/github.com/dockstore-testing/md5sum-checker",
|
|
453
|
-
"https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/refs/heads/master/md5sum/md5sum-input-cwl.json"
|
|
490
|
+
str(tmp_path),
|
|
491
|
+
"#workflow/github.com/dockstore-testing/md5sum-checker:master",
|
|
492
|
+
"https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/refs/heads/master/md5sum/md5sum-input-cwl.json",
|
|
454
493
|
]
|
|
455
494
|
cwltoil.main(main_args, stdout=stdout)
|
|
456
495
|
out = json.loads(stdout.getvalue())
|
|
457
496
|
with open(out.get("output_file", {}).get("location")[len("file://") :]) as f:
|
|
458
497
|
computed_hash = f.read().strip()
|
|
459
|
-
|
|
498
|
+
assert computed_hash == "00579a00e3e7fa0674428ac7049423e2"
|
|
460
499
|
|
|
461
|
-
def test_glob_dir_bypass_file_store(self) -> None:
|
|
500
|
+
def test_glob_dir_bypass_file_store(self, tmp_path: Path) -> None:
|
|
462
501
|
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:
|
|
502
|
+
with get_data("test/cwl/glob_dir.cwl") as cwl_file:
|
|
503
|
+
with get_data("test/cwl/empty.json") as inputs_file:
|
|
504
|
+
self._tester(
|
|
505
|
+
cwl_file,
|
|
506
|
+
inputs_file,
|
|
507
|
+
self._expected_glob_dir_output(tmp_path),
|
|
508
|
+
tmp_path,
|
|
509
|
+
main_args=["--bypass-file-store"],
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def test_required_input_condition_protection(self, tmp_path: Path) -> None:
|
|
481
513
|
# This doesn't run containerized
|
|
482
|
-
|
|
483
|
-
"
|
|
484
|
-
|
|
485
|
-
{},
|
|
486
|
-
)
|
|
514
|
+
with get_data("test/cwl/not_run_required_input.cwl") as cwl_file:
|
|
515
|
+
with get_data("test/cwl/empty.json") as inputs_file:
|
|
516
|
+
self._tester(cwl_file, inputs_file, {}, tmp_path)
|
|
487
517
|
|
|
488
518
|
@needs_slurm
|
|
489
|
-
|
|
519
|
+
@pytest.mark.slurm
|
|
520
|
+
def test_slurm_node_memory(self, tmp_path: Path) -> None:
|
|
490
521
|
pass
|
|
491
522
|
|
|
492
523
|
# Run the workflow. This will either finish quickly and tell us the
|
|
@@ -497,28 +528,29 @@ class CWLWorkflowTest(ToilTest):
|
|
|
497
528
|
# And if we run out of time we need to stop the workflow gracefully and
|
|
498
529
|
# cancel the Slurm jobs.
|
|
499
530
|
|
|
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
531
|
try:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
532
|
+
with get_data("test/cwl/measure_default_memory.cwl") as cwl_file:
|
|
533
|
+
main_args = [
|
|
534
|
+
f"--jobStore={str(tmp_path / 'jobStoreDir')}",
|
|
535
|
+
# Avoid racing to toil kill before the jobstore is removed
|
|
536
|
+
"--clean=never",
|
|
537
|
+
"--batchSystem=slurm",
|
|
538
|
+
"--no-cwl-default-ram",
|
|
539
|
+
"--slurmDefaultAllMem=True",
|
|
540
|
+
"--outdir",
|
|
541
|
+
str(tmp_path / "outdir"),
|
|
542
|
+
str(cwl_file),
|
|
543
|
+
]
|
|
544
|
+
log.debug("Start test workflow")
|
|
545
|
+
child = subprocess.Popen(
|
|
546
|
+
["toil-cwl-runner"] + main_args, stdout=subprocess.PIPE
|
|
547
|
+
)
|
|
548
|
+
output, _ = child.communicate(timeout=60)
|
|
517
549
|
except subprocess.TimeoutExpired:
|
|
518
550
|
# The job didn't finish quickly; presumably waiting for a full node.
|
|
519
551
|
# Stop the workflow
|
|
520
552
|
log.debug("Workflow might be waiting for a full node. Stop it.")
|
|
521
|
-
subprocess.check_call(["toil", "kill",
|
|
553
|
+
subprocess.check_call(["toil", "kill", str(tmp_path / "jobStoreDir")])
|
|
522
554
|
# Wait another little bit for it to clean up, making sure to collect output in case it is blocked on writing
|
|
523
555
|
child.communicate(timeout=20)
|
|
524
556
|
# Kill it off in case it is still running
|
|
@@ -538,113 +570,151 @@ class CWLWorkflowTest(ToilTest):
|
|
|
538
570
|
else:
|
|
539
571
|
result = int(memory_string)
|
|
540
572
|
# We should see more than the CWL default or the Toil default, assuming Slurm nodes of reasonable size (3 GiB).
|
|
541
|
-
|
|
573
|
+
assert result > (3 * 1024 * 1024)
|
|
542
574
|
|
|
543
575
|
@needs_aws_s3
|
|
544
|
-
|
|
545
|
-
|
|
576
|
+
@pytest.mark.aws_s3
|
|
577
|
+
@pytest.mark.online
|
|
578
|
+
def test_download_s3(self, tmp_path: Path) -> None:
|
|
579
|
+
self.download("download_s3.json", self._tester, tmp_path)
|
|
546
580
|
|
|
547
|
-
def test_download_http(self) -> None:
|
|
548
|
-
self.download("download_http.json", self._tester)
|
|
581
|
+
def test_download_http(self, tmp_path: Path) -> None:
|
|
582
|
+
self.download("download_http.json", self._tester, tmp_path)
|
|
549
583
|
|
|
550
|
-
def test_download_https(self) -> None:
|
|
551
|
-
self.download("download_https.json", self._tester)
|
|
584
|
+
def test_download_https(self, tmp_path: Path) -> None:
|
|
585
|
+
self.download("download_https.json", self._tester, tmp_path)
|
|
552
586
|
|
|
553
|
-
def test_download_https_reference(self) -> None:
|
|
587
|
+
def test_download_https_reference(self, tmp_path: Path) -> None:
|
|
554
588
|
self.download(
|
|
555
589
|
"download_https.json",
|
|
556
590
|
partial(self._tester, main_args=["--reference-inputs"]),
|
|
591
|
+
tmp_path,
|
|
557
592
|
)
|
|
558
593
|
|
|
559
|
-
def test_download_file(self) -> None:
|
|
560
|
-
self.download("download_file.json", self._tester)
|
|
594
|
+
def test_download_file(self, tmp_path: Path) -> None:
|
|
595
|
+
self.download("download_file.json", self._tester, tmp_path)
|
|
561
596
|
|
|
562
597
|
@needs_aws_s3
|
|
563
|
-
|
|
564
|
-
|
|
598
|
+
@pytest.mark.aws_s3
|
|
599
|
+
@pytest.mark.online
|
|
600
|
+
def test_download_directory_s3(self, tmp_path: Path) -> None:
|
|
601
|
+
self.download_directory("download_directory_s3.json", self._tester, tmp_path)
|
|
565
602
|
|
|
566
603
|
@needs_aws_s3
|
|
567
|
-
|
|
604
|
+
@pytest.mark.aws_s3
|
|
605
|
+
@pytest.mark.online
|
|
606
|
+
def test_download_directory_s3_reference(self, tmp_path: Path) -> None:
|
|
568
607
|
self.download_directory(
|
|
569
608
|
"download_directory_s3.json",
|
|
570
609
|
partial(self._tester, main_args=["--reference-inputs"]),
|
|
610
|
+
tmp_path,
|
|
571
611
|
)
|
|
572
612
|
|
|
573
|
-
def test_download_directory_file(self) -> None:
|
|
574
|
-
self.download_directory("download_directory_file.json", self._tester)
|
|
613
|
+
def test_download_directory_file(self, tmp_path: Path) -> None:
|
|
614
|
+
self.download_directory("download_directory_file.json", self._tester, tmp_path)
|
|
575
615
|
|
|
576
616
|
@needs_aws_s3
|
|
577
|
-
|
|
578
|
-
|
|
617
|
+
@pytest.mark.aws_s3
|
|
618
|
+
@pytest.mark.online
|
|
619
|
+
def test_download_subdirectory_s3(self, tmp_path: Path) -> None:
|
|
620
|
+
self.download_subdirectory(
|
|
621
|
+
"download_subdirectory_s3.json", self._tester, tmp_path
|
|
622
|
+
)
|
|
579
623
|
|
|
580
|
-
def test_download_subdirectory_file(self) -> None:
|
|
581
|
-
self.download_subdirectory(
|
|
624
|
+
def test_download_subdirectory_file(self, tmp_path: Path) -> None:
|
|
625
|
+
self.download_subdirectory(
|
|
626
|
+
"download_subdirectory_file.json", self._tester, tmp_path
|
|
627
|
+
)
|
|
582
628
|
|
|
583
629
|
# 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
630
|
|
|
585
631
|
@needs_aws_s3
|
|
586
|
-
|
|
587
|
-
|
|
632
|
+
@pytest.mark.aws_s3
|
|
633
|
+
@pytest.mark.online
|
|
634
|
+
def test_load_contents_s3(self, tmp_path: Path) -> None:
|
|
635
|
+
self.load_contents("download_s3.json", self._tester, tmp_path)
|
|
588
636
|
|
|
589
|
-
def test_load_contents_http(self) -> None:
|
|
590
|
-
self.load_contents("download_http.json", self._tester)
|
|
637
|
+
def test_load_contents_http(self, tmp_path: Path) -> None:
|
|
638
|
+
self.load_contents("download_http.json", self._tester, tmp_path)
|
|
591
639
|
|
|
592
|
-
def test_load_contents_https(self) -> None:
|
|
593
|
-
self.load_contents("download_https.json", self._tester)
|
|
640
|
+
def test_load_contents_https(self, tmp_path: Path) -> None:
|
|
641
|
+
self.load_contents("download_https.json", self._tester, tmp_path)
|
|
594
642
|
|
|
595
|
-
def test_load_contents_file(self) -> None:
|
|
596
|
-
self.load_contents("download_file.json", self._tester)
|
|
643
|
+
def test_load_contents_file(self, tmp_path: Path) -> None:
|
|
644
|
+
self.load_contents("download_file.json", self._tester, tmp_path)
|
|
597
645
|
|
|
598
646
|
@slow
|
|
599
647
|
@pytest.mark.integrative
|
|
600
|
-
@
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
"
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
648
|
+
@pytest.mark.slow
|
|
649
|
+
@pytest.mark.skip("Fails too often due to remote service")
|
|
650
|
+
def test_bioconda(self, tmp_path: Path) -> None:
|
|
651
|
+
with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
|
|
652
|
+
with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
|
|
653
|
+
self._tester(
|
|
654
|
+
cwl_file,
|
|
655
|
+
inputs_file,
|
|
656
|
+
self._expected_seqtk_output(tmp_path),
|
|
657
|
+
tmp_path,
|
|
658
|
+
main_args=["--beta-conda-dependencies"],
|
|
659
|
+
out_name="output1",
|
|
660
|
+
)
|
|
609
661
|
|
|
610
662
|
@needs_docker
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
663
|
+
@pytest.mark.docker
|
|
664
|
+
@pytest.mark.online
|
|
665
|
+
def test_default_args(self, tmp_path: Path) -> None:
|
|
666
|
+
with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
|
|
667
|
+
with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
|
|
668
|
+
self._tester(
|
|
669
|
+
cwl_file,
|
|
670
|
+
inputs_file,
|
|
671
|
+
self._expected_seqtk_output(tmp_path),
|
|
672
|
+
tmp_path,
|
|
673
|
+
main_args=[
|
|
674
|
+
"--default-container",
|
|
675
|
+
"quay.io/biocontainers/seqtk:1.4--he4a0461_1",
|
|
676
|
+
],
|
|
677
|
+
out_name="output1",
|
|
678
|
+
)
|
|
622
679
|
|
|
623
680
|
@needs_docker
|
|
681
|
+
@pytest.mark.docker
|
|
624
682
|
@pytest.mark.integrative
|
|
625
|
-
@
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
"
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
683
|
+
@pytest.mark.online
|
|
684
|
+
@pytest.mark.skip(reason="Fails too often due to remote service")
|
|
685
|
+
def test_biocontainers(self, tmp_path: Path) -> None:
|
|
686
|
+
with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
|
|
687
|
+
with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
|
|
688
|
+
self._tester(
|
|
689
|
+
cwl_file,
|
|
690
|
+
inputs_file,
|
|
691
|
+
self._expected_seqtk_output(tmp_path),
|
|
692
|
+
tmp_path,
|
|
693
|
+
main_args=["--beta-use-biocontainers"],
|
|
694
|
+
out_name="output1",
|
|
695
|
+
)
|
|
634
696
|
|
|
635
697
|
@needs_docker
|
|
636
698
|
@needs_docker_cuda
|
|
637
699
|
@needs_local_cuda
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
700
|
+
@pytest.mark.docker
|
|
701
|
+
@pytest.mark.online
|
|
702
|
+
@pytest.mark.docker_cuda
|
|
703
|
+
@pytest.mark.local_cuda
|
|
704
|
+
def test_cuda(self, tmp_path: Path) -> None:
|
|
705
|
+
with get_data("test/cwl/nvidia_smi.cwl") as cwl_file:
|
|
706
|
+
with get_data("test/cwl/empty.json") as inputs_file:
|
|
707
|
+
self._tester(
|
|
708
|
+
cwl_file,
|
|
709
|
+
inputs_file,
|
|
710
|
+
{},
|
|
711
|
+
tmp_path,
|
|
712
|
+
out_name="result",
|
|
713
|
+
)
|
|
645
714
|
|
|
646
715
|
@slow
|
|
647
|
-
|
|
716
|
+
@pytest.mark.slow
|
|
717
|
+
def test_restart(self, tmp_path: Path) -> None:
|
|
648
718
|
"""
|
|
649
719
|
Enable restarts with toil-cwl-runner -- run failing test, re-run correct test.
|
|
650
720
|
Only implemented for single machine.
|
|
@@ -652,151 +722,203 @@ class CWLWorkflowTest(ToilTest):
|
|
|
652
722
|
log.info("Running CWL Test Restart. Expecting failure, then success.")
|
|
653
723
|
from toil.cwl import cwltoil
|
|
654
724
|
|
|
655
|
-
outDir =
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
725
|
+
outDir = tmp_path / "outDir"
|
|
726
|
+
outDir.mkdir()
|
|
727
|
+
jobStore = tmp_path / "jobStore"
|
|
728
|
+
with get_data("test/cwl/revsort.cwl") as cwl_file:
|
|
729
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
730
|
+
cmd = [
|
|
731
|
+
"--outdir",
|
|
732
|
+
str(outDir),
|
|
733
|
+
"--jobStore",
|
|
734
|
+
str(jobStore),
|
|
735
|
+
"--no-container",
|
|
736
|
+
str(cwl_file),
|
|
737
|
+
str(job_file),
|
|
738
|
+
]
|
|
739
|
+
|
|
740
|
+
# create a fake rev bin that actually points to the "date" binary
|
|
741
|
+
cal_path = [
|
|
742
|
+
d
|
|
743
|
+
for d in os.environ["PATH"].split(":")
|
|
744
|
+
if os.path.exists(os.path.join(d, "date"))
|
|
745
|
+
][-1]
|
|
746
|
+
os.symlink(
|
|
747
|
+
os.path.realpath(os.path.join(cal_path, "date")), outDir / "rev"
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
def path_with_bogus_rev() -> str:
|
|
751
|
+
# append to the front of the PATH so that we check there first
|
|
752
|
+
return f"{str(outDir)}:" + os.environ["PATH"]
|
|
753
|
+
|
|
754
|
+
orig_path = os.environ["PATH"]
|
|
755
|
+
# Force a failure by trying to use an incorrect version of `rev` from the PATH
|
|
756
|
+
os.environ["PATH"] = path_with_bogus_rev()
|
|
757
|
+
try:
|
|
758
|
+
subprocess.check_output(
|
|
759
|
+
["toil-cwl-runner"] + cmd,
|
|
760
|
+
env=os.environ.copy(),
|
|
761
|
+
stderr=subprocess.STDOUT,
|
|
762
|
+
)
|
|
763
|
+
pytest.fail("Expected problem job with incorrect PATH did not fail")
|
|
764
|
+
except subprocess.CalledProcessError:
|
|
765
|
+
pass
|
|
766
|
+
# Finish the job with a correct PATH
|
|
767
|
+
os.environ["PATH"] = orig_path
|
|
768
|
+
cmd.insert(0, "--restart")
|
|
769
|
+
cwltoil.main(cmd)
|
|
770
|
+
# Should fail because previous job completed successfully
|
|
771
|
+
try:
|
|
772
|
+
subprocess.check_output(
|
|
773
|
+
["toil-cwl-runner"] + cmd,
|
|
774
|
+
env=os.environ.copy(),
|
|
775
|
+
stderr=subprocess.STDOUT,
|
|
776
|
+
)
|
|
777
|
+
pytest.fail("Restart with missing directory did not fail")
|
|
778
|
+
except subprocess.CalledProcessError:
|
|
779
|
+
pass
|
|
780
|
+
|
|
781
|
+
def test_caching(self, tmp_path: Path) -> None:
|
|
782
|
+
log.info("Running CWL caching test.")
|
|
783
|
+
from toil.cwl import cwltoil
|
|
666
784
|
|
|
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
|
-
|
|
785
|
+
outDir = tmp_path / "outDir"
|
|
786
|
+
cacheDir = tmp_path / "cacheDir"
|
|
787
|
+
log_path = outDir / "log"
|
|
788
|
+
|
|
789
|
+
with get_data("test/cwl/revsort.cwl") as cwl_file:
|
|
790
|
+
with get_data("test/cwl/revsort-job.json") as job_file:
|
|
791
|
+
cmd = [
|
|
792
|
+
"--outdir",
|
|
793
|
+
str(outDir),
|
|
794
|
+
"--jobStore",
|
|
795
|
+
str(tmp_path / "jobStore"),
|
|
796
|
+
"--clean=always",
|
|
797
|
+
"--no-container",
|
|
798
|
+
"--cachedir",
|
|
799
|
+
str(cacheDir),
|
|
800
|
+
str(cwl_file),
|
|
801
|
+
str(job_file),
|
|
802
|
+
]
|
|
803
|
+
st = StringIO()
|
|
804
|
+
ret = cwltoil.main(cmd, stdout=st)
|
|
805
|
+
assert ret == 0
|
|
806
|
+
# cwltool hashes certain steps into directories, ensure it exists
|
|
807
|
+
# since cwltool caches per task and revsort has 2 cwl tasks, there should be 2 directories and 2 status files
|
|
808
|
+
assert sum(1 for _ in cacheDir.iterdir()) == 4
|
|
809
|
+
|
|
810
|
+
# Rerun the workflow to ensure there is a cache hit and that we don't rerun the tools
|
|
811
|
+
st = StringIO()
|
|
812
|
+
cmd = [
|
|
813
|
+
"--writeLogsFromAllJobs=True",
|
|
814
|
+
"--writeLogs",
|
|
815
|
+
str(log_path),
|
|
816
|
+
] + cmd
|
|
817
|
+
ret = cwltoil.main(cmd, stdout=st)
|
|
818
|
+
assert ret == 0
|
|
819
|
+
|
|
820
|
+
# Ensure all of the worker logs are using their cached outputs
|
|
821
|
+
for file in log_path.iterdir():
|
|
822
|
+
assert "Using cached output" in file.read_text(encoding="utf-8")
|
|
705
823
|
|
|
706
824
|
@needs_aws_s3
|
|
707
|
-
|
|
825
|
+
@pytest.mark.aws_s3
|
|
826
|
+
@pytest.mark.online
|
|
827
|
+
def test_streamable(
|
|
828
|
+
self, tmp_path: Path, extra_args: Optional[list[str]] = None
|
|
829
|
+
) -> None:
|
|
708
830
|
"""
|
|
709
831
|
Test that a file with 'streamable'=True is a named pipe.
|
|
710
832
|
This is a CWL1.2 feature.
|
|
711
833
|
"""
|
|
712
|
-
cwlfile = "src/toil/test/cwl/stream.cwl"
|
|
713
|
-
jobfile = "src/toil/test/cwl/stream.json"
|
|
714
|
-
out_name = "output"
|
|
715
|
-
jobstore = f"--jobStore=aws:us-west-1:toil-stream-{uuid.uuid4()}"
|
|
716
|
-
from toil.cwl import cwltoil
|
|
717
|
-
|
|
718
834
|
st = StringIO()
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
"
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
835
|
+
outDir = tmp_path / "outDir"
|
|
836
|
+
with get_data("test/cwl/stream.cwl") as cwlfile:
|
|
837
|
+
with get_data("test/cwl/stream.json") as jobfile:
|
|
838
|
+
out_name = "output"
|
|
839
|
+
jobstore = f"--jobStore=aws:us-west-1:toil-stream-{uuid.uuid4()}"
|
|
840
|
+
from toil.cwl import cwltoil
|
|
841
|
+
|
|
842
|
+
args = [
|
|
843
|
+
"--logDebug",
|
|
844
|
+
"--outdir",
|
|
845
|
+
str(outDir),
|
|
846
|
+
jobstore,
|
|
847
|
+
str(cwlfile),
|
|
848
|
+
str(jobfile),
|
|
849
|
+
]
|
|
850
|
+
if extra_args:
|
|
851
|
+
args = extra_args + args
|
|
852
|
+
log.info("Run CWL run: %s", " ".join(args))
|
|
853
|
+
cwltoil.main(args, stdout=st)
|
|
731
854
|
out = json.loads(st.getvalue())
|
|
732
855
|
out[out_name].pop("http://commonwl.org/cwltool#generation", None)
|
|
733
856
|
out[out_name].pop("nameext", None)
|
|
734
857
|
out[out_name].pop("nameroot", None)
|
|
735
|
-
|
|
858
|
+
assert out == self._expected_streaming_output(outDir)
|
|
736
859
|
with open(out[out_name]["location"][len("file://") :]) as f:
|
|
737
|
-
|
|
860
|
+
assert f.read().strip() == "When is s4 coming out?"
|
|
738
861
|
|
|
739
862
|
@needs_aws_s3
|
|
740
|
-
|
|
863
|
+
@pytest.mark.aws_s3
|
|
864
|
+
@pytest.mark.online
|
|
865
|
+
def test_streamable_reference(self, tmp_path: Path) -> None:
|
|
741
866
|
"""
|
|
742
867
|
Test that a streamable file is a stream even when passed around by URI.
|
|
743
868
|
"""
|
|
744
|
-
self.test_streamable(extra_args=["--reference-inputs"])
|
|
869
|
+
self.test_streamable(tmp_path=tmp_path, extra_args=["--reference-inputs"])
|
|
745
870
|
|
|
746
|
-
def test_preemptible(self) -> None:
|
|
871
|
+
def test_preemptible(self, tmp_path: Path) -> None:
|
|
747
872
|
"""
|
|
748
873
|
Tests that the http://arvados.org/cwl#UsePreemptible extension is supported.
|
|
749
874
|
"""
|
|
750
|
-
cwlfile = "src/toil/test/cwl/preemptible.cwl"
|
|
751
|
-
jobfile = "src/toil/test/cwl/empty.json"
|
|
752
|
-
out_name = "output"
|
|
753
875
|
from toil.cwl import cwltoil
|
|
754
876
|
|
|
755
877
|
st = StringIO()
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
878
|
+
out_name = "output"
|
|
879
|
+
with get_data("test/cwl/preemptible.cwl") as cwlfile:
|
|
880
|
+
with get_data("test/cwl/empty.json") as jobfile:
|
|
881
|
+
args = [
|
|
882
|
+
"--outdir",
|
|
883
|
+
str(tmp_path / "outDir"),
|
|
884
|
+
str(cwlfile),
|
|
885
|
+
str(jobfile),
|
|
886
|
+
]
|
|
887
|
+
cwltoil.main(args, stdout=st)
|
|
763
888
|
out = json.loads(st.getvalue())
|
|
764
889
|
out[out_name].pop("http://commonwl.org/cwltool#generation", None)
|
|
765
890
|
out[out_name].pop("nameext", None)
|
|
766
891
|
out[out_name].pop("nameroot", None)
|
|
767
892
|
with open(out[out_name]["location"][len("file://") :]) as f:
|
|
768
|
-
|
|
893
|
+
assert f.read().strip() == "hello"
|
|
769
894
|
|
|
770
|
-
def test_preemptible_expression(self) -> None:
|
|
895
|
+
def test_preemptible_expression(self, tmp_path: Path) -> None:
|
|
771
896
|
"""
|
|
772
897
|
Tests that the http://arvados.org/cwl#UsePreemptible extension is validated.
|
|
773
898
|
"""
|
|
774
|
-
cwlfile = "src/toil/test/cwl/preemptible_expression.cwl"
|
|
775
|
-
jobfile = "src/toil/test/cwl/preemptible_expression.json"
|
|
776
899
|
from toil.cwl import cwltoil
|
|
777
900
|
|
|
778
901
|
st = StringIO()
|
|
779
|
-
|
|
780
|
-
"
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
902
|
+
with get_data("test/cwl/preemptible_expression.cwl") as cwlfile:
|
|
903
|
+
with get_data("test/cwl/preemptible_expression.json") as jobfile:
|
|
904
|
+
args = [
|
|
905
|
+
"--outdir",
|
|
906
|
+
str(tmp_path),
|
|
907
|
+
str(cwlfile),
|
|
908
|
+
str(jobfile),
|
|
909
|
+
]
|
|
910
|
+
with pytest.raises(
|
|
911
|
+
ValidationException, match=re.escape("expressions are not allowed")
|
|
912
|
+
):
|
|
913
|
+
cwltoil.main(args, stdout=st)
|
|
791
914
|
|
|
792
915
|
@staticmethod
|
|
793
|
-
def _expected_seqtk_output(outDir:
|
|
794
|
-
path =
|
|
795
|
-
loc = "file://" + path
|
|
916
|
+
def _expected_seqtk_output(outDir: Path) -> "CWLObjectType":
|
|
917
|
+
path = outDir / "out"
|
|
796
918
|
return {
|
|
797
919
|
"output1": {
|
|
798
|
-
"location":
|
|
799
|
-
"path": path,
|
|
920
|
+
"location": path.as_uri(),
|
|
921
|
+
"path": str(path),
|
|
800
922
|
"checksum": "sha1$322e001e5a99f19abdce9f02ad0f02a17b5066c2",
|
|
801
923
|
"basename": "out",
|
|
802
924
|
"class": "File",
|
|
@@ -805,13 +927,12 @@ class CWLWorkflowTest(ToilTest):
|
|
|
805
927
|
}
|
|
806
928
|
|
|
807
929
|
@staticmethod
|
|
808
|
-
def _expected_revsort_output(outDir:
|
|
809
|
-
path =
|
|
810
|
-
loc = "file://" + path
|
|
930
|
+
def _expected_revsort_output(outDir: Path) -> "CWLObjectType":
|
|
931
|
+
path = outDir / "output.txt"
|
|
811
932
|
return {
|
|
812
933
|
"output": {
|
|
813
|
-
"location":
|
|
814
|
-
"path": path,
|
|
934
|
+
"location": path.as_uri(),
|
|
935
|
+
"path": str(path),
|
|
815
936
|
"basename": "output.txt",
|
|
816
937
|
"size": 1111,
|
|
817
938
|
"class": "File",
|
|
@@ -820,13 +941,12 @@ class CWLWorkflowTest(ToilTest):
|
|
|
820
941
|
}
|
|
821
942
|
|
|
822
943
|
@staticmethod
|
|
823
|
-
def _expected_revsort_nochecksum_output(outDir:
|
|
824
|
-
path =
|
|
825
|
-
loc = "file://" + path
|
|
944
|
+
def _expected_revsort_nochecksum_output(outDir: Path) -> "CWLObjectType":
|
|
945
|
+
path = outDir / "output.txt"
|
|
826
946
|
return {
|
|
827
947
|
"output": {
|
|
828
|
-
"location":
|
|
829
|
-
"path": path,
|
|
948
|
+
"location": path.as_uri(),
|
|
949
|
+
"path": str(path),
|
|
830
950
|
"basename": "output.txt",
|
|
831
951
|
"size": 1111,
|
|
832
952
|
"class": "File",
|
|
@@ -834,30 +954,29 @@ class CWLWorkflowTest(ToilTest):
|
|
|
834
954
|
}
|
|
835
955
|
|
|
836
956
|
@staticmethod
|
|
837
|
-
def _expected_download_output(outDir:
|
|
838
|
-
path =
|
|
839
|
-
loc = "file://" + path
|
|
957
|
+
def _expected_download_output(outDir: Path) -> "CWLObjectType":
|
|
958
|
+
path = outDir / "output.txt"
|
|
840
959
|
return {
|
|
841
960
|
"output": {
|
|
842
|
-
"location":
|
|
961
|
+
"location": path.as_uri(),
|
|
843
962
|
"basename": "output.txt",
|
|
844
963
|
"size": 0,
|
|
845
964
|
"class": "File",
|
|
846
965
|
"checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
847
|
-
"path": path,
|
|
966
|
+
"path": str(path),
|
|
848
967
|
}
|
|
849
968
|
}
|
|
850
969
|
|
|
851
970
|
@staticmethod
|
|
852
|
-
def _expected_glob_dir_output(out_dir:
|
|
853
|
-
dir_path =
|
|
854
|
-
dir_loc =
|
|
855
|
-
file_path =
|
|
856
|
-
file_loc =
|
|
971
|
+
def _expected_glob_dir_output(out_dir: Path) -> "CWLObjectType":
|
|
972
|
+
dir_path = out_dir / "shouldmake"
|
|
973
|
+
dir_loc = dir_path.as_uri()
|
|
974
|
+
file_path = dir_path / "test.txt"
|
|
975
|
+
file_loc = file_path.as_uri()
|
|
857
976
|
return {
|
|
858
977
|
"shouldmake": {
|
|
859
978
|
"location": dir_loc,
|
|
860
|
-
"path": dir_path,
|
|
979
|
+
"path": str(dir_path),
|
|
861
980
|
"basename": "shouldmake",
|
|
862
981
|
"nameroot": "shouldmake",
|
|
863
982
|
"nameext": "",
|
|
@@ -866,7 +985,7 @@ class CWLWorkflowTest(ToilTest):
|
|
|
866
985
|
{
|
|
867
986
|
"class": "File",
|
|
868
987
|
"location": file_loc,
|
|
869
|
-
"path": file_path,
|
|
988
|
+
"path": str(file_path),
|
|
870
989
|
"basename": "test.txt",
|
|
871
990
|
"checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
|
|
872
991
|
"size": 0,
|
|
@@ -878,7 +997,7 @@ class CWLWorkflowTest(ToilTest):
|
|
|
878
997
|
}
|
|
879
998
|
|
|
880
999
|
@classmethod
|
|
881
|
-
def _expected_load_contents_output(cls, out_dir:
|
|
1000
|
+
def _expected_load_contents_output(cls, out_dir: Path) -> "CWLObjectType":
|
|
882
1001
|
"""
|
|
883
1002
|
Generate the putput we expect from load_contents.cwl, when sending
|
|
884
1003
|
output files to the given directory.
|
|
@@ -888,13 +1007,15 @@ class CWLWorkflowTest(ToilTest):
|
|
|
888
1007
|
return expected
|
|
889
1008
|
|
|
890
1009
|
@staticmethod
|
|
891
|
-
def _expected_colon_output(outDir:
|
|
892
|
-
path =
|
|
893
|
-
loc = "file://" + os.path.join(
|
|
1010
|
+
def _expected_colon_output(outDir: Path) -> "CWLObjectType":
|
|
1011
|
+
path = outDir / "A:Gln2Cys_result"
|
|
1012
|
+
loc = "file://" + os.path.join(
|
|
1013
|
+
outDir, "A%3AGln2Cys_result"
|
|
1014
|
+
) # not using .as_uri to ensure the expected escaping
|
|
894
1015
|
return {
|
|
895
1016
|
"result": {
|
|
896
1017
|
"location": loc,
|
|
897
|
-
"path": path,
|
|
1018
|
+
"path": str(path),
|
|
898
1019
|
"basename": "A:Gln2Cys_result",
|
|
899
1020
|
"class": "Directory",
|
|
900
1021
|
"listing": [
|
|
@@ -912,13 +1033,12 @@ class CWLWorkflowTest(ToilTest):
|
|
|
912
1033
|
}
|
|
913
1034
|
}
|
|
914
1035
|
|
|
915
|
-
def _expected_streaming_output(self, outDir:
|
|
916
|
-
path =
|
|
917
|
-
loc = "file://" + path
|
|
1036
|
+
def _expected_streaming_output(self, outDir: Path) -> "CWLObjectType":
|
|
1037
|
+
path = outDir / "output.txt"
|
|
918
1038
|
return {
|
|
919
1039
|
"output": {
|
|
920
|
-
"location":
|
|
921
|
-
"path": path,
|
|
1040
|
+
"location": path.as_uri(),
|
|
1041
|
+
"path": str(path),
|
|
922
1042
|
"basename": "output.txt",
|
|
923
1043
|
"size": 24,
|
|
924
1044
|
"class": "File",
|
|
@@ -926,50 +1046,100 @@ class CWLWorkflowTest(ToilTest):
|
|
|
926
1046
|
}
|
|
927
1047
|
}
|
|
928
1048
|
|
|
1049
|
+
@needs_docker
|
|
1050
|
+
@pytest.mark.docker
|
|
1051
|
+
@pytest.mark.online
|
|
1052
|
+
def test_missing_import(self, tmp_path: Path) -> None:
|
|
1053
|
+
with get_data("test/cwl/revsort.cwl") as cwl_file:
|
|
1054
|
+
with get_data("test/cwl/revsort-job-missing.json") as inputs_file:
|
|
1055
|
+
cmd = [
|
|
1056
|
+
"toil-cwl-runner",
|
|
1057
|
+
f"--outdir={str(tmp_path)}",
|
|
1058
|
+
"--clean=always",
|
|
1059
|
+
str(cwl_file),
|
|
1060
|
+
str(inputs_file),
|
|
1061
|
+
]
|
|
1062
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1063
|
+
# Make sure that the missing file is mentioned in the log so the user knows
|
|
1064
|
+
assert p.returncode == 1, p.stderr
|
|
1065
|
+
assert "missing.txt" in p.stderr
|
|
1066
|
+
|
|
1067
|
+
@needs_aws_s3
|
|
1068
|
+
@pytest.mark.aws_s3
|
|
1069
|
+
@pytest.mark.online
|
|
1070
|
+
def test_optional_secondary_files_exists(self, tmp_path: Path) -> None:
|
|
1071
|
+
from toil.cwl import cwltoil
|
|
1072
|
+
|
|
1073
|
+
with get_data("test/cwl/optional-file.cwl") as cwlfile:
|
|
1074
|
+
with get_data("test/cwl/optional-file-exists.json") as jobfile:
|
|
1075
|
+
args = [str(cwlfile), str(jobfile), f"--outdir={str(tmp_path)}"]
|
|
1076
|
+
ret = cwltoil.main(args)
|
|
1077
|
+
assert ret == 0
|
|
1078
|
+
assert (tmp_path / "wdl_templates_old.zip").exists()
|
|
1079
|
+
|
|
1080
|
+
@needs_aws_s3
|
|
1081
|
+
@pytest.mark.aws_s3
|
|
1082
|
+
@pytest.mark.online
|
|
1083
|
+
def test_optional_secondary_files_missing(self, tmp_path: Path) -> None:
|
|
1084
|
+
from toil.cwl import cwltoil
|
|
1085
|
+
|
|
1086
|
+
with get_data("test/cwl/optional-file.cwl") as cwlfile:
|
|
1087
|
+
with get_data("test/cwl/optional-file-missing.json") as jobfile:
|
|
1088
|
+
args = [str(cwlfile), str(jobfile), f"--outdir={str(tmp_path)}"]
|
|
1089
|
+
ret = cwltoil.main(args)
|
|
1090
|
+
assert ret == 0
|
|
1091
|
+
assert not (tmp_path / "hello_old.zip").exists()
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
@pytest.fixture(scope="function")
|
|
1095
|
+
def cwl_v1_0_spec(tmp_path: Path) -> Generator[Path]:
|
|
1096
|
+
# The latest cwl git commit hash from https://github.com/common-workflow-language/common-workflow-language.
|
|
1097
|
+
# Update it to get the latest tests.
|
|
1098
|
+
testhash = (
|
|
1099
|
+
"6a955874ade22080b8ef962b4e0d6e408112c1ef" # Date: Tue Dec 16 2020 8:43pm PST
|
|
1100
|
+
)
|
|
1101
|
+
url = (
|
|
1102
|
+
"https://github.com/common-workflow-language/common-workflow-language/archive/%s.zip"
|
|
1103
|
+
% testhash
|
|
1104
|
+
)
|
|
1105
|
+
urlretrieve(url, "spec.zip")
|
|
1106
|
+
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1107
|
+
z.extractall()
|
|
1108
|
+
shutil.move("common-workflow-language-%s" % testhash, str(tmp_path))
|
|
1109
|
+
os.remove("spec.zip")
|
|
1110
|
+
try:
|
|
1111
|
+
yield tmp_path / ("common-workflow-language-%s" % testhash)
|
|
1112
|
+
finally:
|
|
1113
|
+
pass # no cleanup
|
|
1114
|
+
|
|
929
1115
|
|
|
930
1116
|
@needs_cwl
|
|
931
1117
|
@needs_online
|
|
932
|
-
|
|
1118
|
+
@pytest.mark.cwl
|
|
1119
|
+
@pytest.mark.online
|
|
1120
|
+
class TestCWLv10:
|
|
933
1121
|
"""
|
|
934
1122
|
Run the CWL 1.0 conformance tests in various environments.
|
|
935
1123
|
"""
|
|
936
1124
|
|
|
937
|
-
def setUp(self) -> None:
|
|
938
|
-
"""Runs anew before each test to create farm fresh temp dirs."""
|
|
939
|
-
self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
|
|
940
|
-
os.makedirs(self.outDir)
|
|
941
|
-
self.rootDir = self._projectRootPath()
|
|
942
|
-
self.cwlSpec = os.path.join(self.rootDir, "src/toil/test/cwl/spec")
|
|
943
|
-
self.workDir = os.path.join(self.cwlSpec, "v1.0")
|
|
944
|
-
# The latest cwl git commit hash from https://github.com/common-workflow-language/common-workflow-language.
|
|
945
|
-
# Update it to get the latest tests.
|
|
946
|
-
testhash = "6a955874ade22080b8ef962b4e0d6e408112c1ef" # Date: Tue Dec 16 2020 8:43pm PST
|
|
947
|
-
url = (
|
|
948
|
-
"https://github.com/common-workflow-language/common-workflow-language/archive/%s.zip"
|
|
949
|
-
% testhash
|
|
950
|
-
)
|
|
951
|
-
if not os.path.exists(self.cwlSpec):
|
|
952
|
-
urlretrieve(url, "spec.zip")
|
|
953
|
-
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
954
|
-
z.extractall()
|
|
955
|
-
shutil.move("common-workflow-language-%s" % testhash, self.cwlSpec)
|
|
956
|
-
os.remove("spec.zip")
|
|
957
|
-
|
|
958
|
-
def tearDown(self) -> None:
|
|
959
|
-
"""Clean up outputs."""
|
|
960
|
-
if os.path.exists(self.outDir):
|
|
961
|
-
shutil.rmtree(self.outDir)
|
|
962
|
-
unittest.TestCase.tearDown(self)
|
|
963
|
-
|
|
964
1125
|
@slow
|
|
1126
|
+
@needs_docker
|
|
1127
|
+
@pytest.mark.slow
|
|
1128
|
+
@pytest.mark.docker
|
|
1129
|
+
@pytest.mark.online
|
|
965
1130
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
966
|
-
def test_run_conformance_with_caching(self) -> None:
|
|
967
|
-
self.test_run_conformance(caching=True)
|
|
1131
|
+
def test_run_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1132
|
+
self.test_run_conformance(cwl_v1_0_spec, caching=True)
|
|
968
1133
|
|
|
969
1134
|
@slow
|
|
1135
|
+
@needs_docker
|
|
1136
|
+
@pytest.mark.slow
|
|
1137
|
+
@pytest.mark.docker
|
|
1138
|
+
@pytest.mark.online
|
|
970
1139
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
971
1140
|
def test_run_conformance(
|
|
972
1141
|
self,
|
|
1142
|
+
cwl_v1_0_spec: Path,
|
|
973
1143
|
batchSystem: Optional[str] = None,
|
|
974
1144
|
caching: bool = False,
|
|
975
1145
|
selected_tests: Optional[str] = None,
|
|
@@ -977,8 +1147,8 @@ class CWLv10Test(ToilTest):
|
|
|
977
1147
|
extra_args: Optional[list[str]] = None,
|
|
978
1148
|
) -> None:
|
|
979
1149
|
run_conformance_tests(
|
|
980
|
-
workDir=
|
|
981
|
-
yml="conformance_test_v1.0.yaml",
|
|
1150
|
+
workDir=str(cwl_v1_0_spec / "v1.0"),
|
|
1151
|
+
yml=str(cwl_v1_0_spec / "v1.0" / "conformance_test_v1.0.yaml"),
|
|
982
1152
|
caching=caching,
|
|
983
1153
|
batchSystem=batchSystem,
|
|
984
1154
|
selected_tests=selected_tests,
|
|
@@ -988,38 +1158,66 @@ class CWLv10Test(ToilTest):
|
|
|
988
1158
|
|
|
989
1159
|
@slow
|
|
990
1160
|
@needs_lsf
|
|
991
|
-
@
|
|
992
|
-
|
|
993
|
-
|
|
1161
|
+
@pytest.mark.slow
|
|
1162
|
+
@pytest.mark.lsf
|
|
1163
|
+
@pytest.mark.skip("Not run")
|
|
1164
|
+
def test_lsf_cwl_conformance(
|
|
1165
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1166
|
+
) -> None:
|
|
1167
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="lsf", caching=caching)
|
|
994
1168
|
|
|
995
1169
|
@slow
|
|
996
1170
|
@needs_slurm
|
|
997
|
-
@
|
|
998
|
-
|
|
999
|
-
|
|
1171
|
+
@pytest.mark.slow
|
|
1172
|
+
@pytest.mark.slurm
|
|
1173
|
+
@pytest.mark.skip("Not run")
|
|
1174
|
+
def test_slurm_cwl_conformance(
|
|
1175
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1176
|
+
) -> None:
|
|
1177
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="slurm", caching=caching)
|
|
1000
1178
|
|
|
1001
1179
|
@slow
|
|
1002
1180
|
@needs_torque
|
|
1003
|
-
@
|
|
1004
|
-
|
|
1005
|
-
|
|
1181
|
+
@pytest.mark.slow
|
|
1182
|
+
@pytest.mark.torque
|
|
1183
|
+
@pytest.mark.skip("Not run")
|
|
1184
|
+
def test_torque_cwl_conformance(
|
|
1185
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1186
|
+
) -> None:
|
|
1187
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="torque", caching=caching)
|
|
1006
1188
|
|
|
1007
1189
|
@slow
|
|
1008
1190
|
@needs_gridengine
|
|
1009
|
-
@
|
|
1010
|
-
|
|
1011
|
-
|
|
1191
|
+
@pytest.mark.slow
|
|
1192
|
+
@pytest.mark.gridengine
|
|
1193
|
+
@pytest.mark.skip("Not run")
|
|
1194
|
+
def test_gridengine_cwl_conformance(
|
|
1195
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1196
|
+
) -> None:
|
|
1197
|
+
self.test_run_conformance(
|
|
1198
|
+
cwl_v1_0_spec, batchSystem="grid_engine", caching=caching
|
|
1199
|
+
)
|
|
1012
1200
|
|
|
1013
1201
|
@slow
|
|
1014
1202
|
@needs_mesos
|
|
1015
|
-
@
|
|
1016
|
-
|
|
1017
|
-
|
|
1203
|
+
@pytest.mark.slow
|
|
1204
|
+
@pytest.mark.mesos
|
|
1205
|
+
@pytest.mark.skip("Not run")
|
|
1206
|
+
def test_mesos_cwl_conformance(
|
|
1207
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1208
|
+
) -> None:
|
|
1209
|
+
self.test_run_conformance(cwl_v1_0_spec, batchSystem="mesos", caching=caching)
|
|
1018
1210
|
|
|
1019
1211
|
@slow
|
|
1020
1212
|
@needs_kubernetes
|
|
1021
|
-
|
|
1213
|
+
@pytest.mark.slow
|
|
1214
|
+
@pytest.mark.kubernetes
|
|
1215
|
+
@pytest.mark.online
|
|
1216
|
+
def test_kubernetes_cwl_conformance(
|
|
1217
|
+
self, cwl_v1_0_spec: Path, caching: bool = False
|
|
1218
|
+
) -> None:
|
|
1022
1219
|
self.test_run_conformance(
|
|
1220
|
+
cwl_v1_0_spec,
|
|
1023
1221
|
caching=caching,
|
|
1024
1222
|
batchSystem="kubernetes",
|
|
1025
1223
|
extra_args=["--retryCount=3"],
|
|
@@ -1031,82 +1229,98 @@ class CWLv10Test(ToilTest):
|
|
|
1031
1229
|
|
|
1032
1230
|
@slow
|
|
1033
1231
|
@needs_lsf
|
|
1034
|
-
@
|
|
1035
|
-
|
|
1036
|
-
|
|
1232
|
+
@pytest.mark.slow
|
|
1233
|
+
@pytest.mark.lsf
|
|
1234
|
+
@pytest.mark.skip(reason="Not run")
|
|
1235
|
+
def test_lsf_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1236
|
+
self.test_lsf_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1037
1237
|
|
|
1038
1238
|
@slow
|
|
1039
1239
|
@needs_slurm
|
|
1040
|
-
@
|
|
1041
|
-
|
|
1042
|
-
|
|
1240
|
+
@pytest.mark.slow
|
|
1241
|
+
@pytest.mark.slurm
|
|
1242
|
+
@pytest.mark.skip(reason="Not run")
|
|
1243
|
+
def test_slurm_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1244
|
+
self.test_slurm_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1043
1245
|
|
|
1044
1246
|
@slow
|
|
1045
1247
|
@needs_torque
|
|
1046
|
-
@
|
|
1047
|
-
|
|
1048
|
-
|
|
1248
|
+
@pytest.mark.slow
|
|
1249
|
+
@pytest.mark.torque
|
|
1250
|
+
@pytest.mark.skip(reason="Not run")
|
|
1251
|
+
def test_torque_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1252
|
+
self.test_torque_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1049
1253
|
|
|
1050
1254
|
@slow
|
|
1051
1255
|
@needs_gridengine
|
|
1052
|
-
@
|
|
1053
|
-
|
|
1054
|
-
|
|
1256
|
+
@pytest.mark.slow
|
|
1257
|
+
@pytest.mark.gridengine
|
|
1258
|
+
@pytest.mark.skip(reason="Not run")
|
|
1259
|
+
def test_gridengine_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1260
|
+
self.test_gridengine_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1055
1261
|
|
|
1056
1262
|
@slow
|
|
1057
1263
|
@needs_mesos
|
|
1058
|
-
@
|
|
1059
|
-
|
|
1060
|
-
|
|
1264
|
+
@pytest.mark.slow
|
|
1265
|
+
@pytest.mark.mesos
|
|
1266
|
+
@pytest.mark.skip(reason="Not run")
|
|
1267
|
+
def test_mesos_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1268
|
+
self.test_mesos_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1061
1269
|
|
|
1062
1270
|
@slow
|
|
1063
1271
|
@needs_kubernetes
|
|
1064
|
-
|
|
1065
|
-
|
|
1272
|
+
@pytest.mark.slow
|
|
1273
|
+
@pytest.mark.kubernetes
|
|
1274
|
+
@pytest.mark.online
|
|
1275
|
+
def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
|
|
1276
|
+
self.test_kubernetes_cwl_conformance(cwl_v1_0_spec, caching=True)
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
@pytest.fixture(scope="function")
|
|
1280
|
+
def cwl_v1_1_spec(tmp_path: Path) -> Generator[Path]:
|
|
1281
|
+
# The latest cwl git commit hash from https://github.com/common-workflow-language/cwl-v1.1
|
|
1282
|
+
# Update it to get the latest tests.
|
|
1283
|
+
testhash = "664835e83eb5e57eee18a04ce7b05fb9d70d77b7"
|
|
1284
|
+
url = (
|
|
1285
|
+
"https://github.com/common-workflow-language/cwl-v1.1/archive/%s.zip" % testhash
|
|
1286
|
+
)
|
|
1287
|
+
urlretrieve(url, "spec.zip")
|
|
1288
|
+
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1289
|
+
z.extractall()
|
|
1290
|
+
shutil.move("cwl-v1.1-%s" % testhash, str(tmp_path))
|
|
1291
|
+
os.remove("spec.zip")
|
|
1292
|
+
try:
|
|
1293
|
+
yield tmp_path / ("cwl-v1.1-%s" % testhash)
|
|
1294
|
+
finally:
|
|
1295
|
+
pass # no cleanup
|
|
1066
1296
|
|
|
1067
1297
|
|
|
1068
1298
|
@needs_cwl
|
|
1069
1299
|
@needs_online
|
|
1070
|
-
|
|
1300
|
+
@pytest.mark.cwl
|
|
1301
|
+
@pytest.mark.online
|
|
1302
|
+
class TestCWLv11:
|
|
1071
1303
|
"""
|
|
1072
1304
|
Run the CWL 1.1 conformance tests in various environments.
|
|
1073
1305
|
"""
|
|
1074
1306
|
|
|
1075
|
-
rootDir: str
|
|
1076
|
-
cwlSpec: str
|
|
1077
|
-
test_yaml: str
|
|
1078
|
-
|
|
1079
|
-
@classmethod
|
|
1080
|
-
def setUpClass(cls) -> None:
|
|
1081
|
-
"""Runs anew before each test."""
|
|
1082
|
-
cls.rootDir = cls._projectRootPath()
|
|
1083
|
-
cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v11")
|
|
1084
|
-
cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
|
|
1085
|
-
# TODO: Use a commit zip in case someone decides to rewrite master's history?
|
|
1086
|
-
url = "https://github.com/common-workflow-language/cwl-v1.1.git"
|
|
1087
|
-
commit = "664835e83eb5e57eee18a04ce7b05fb9d70d77b7"
|
|
1088
|
-
p = subprocess.Popen(
|
|
1089
|
-
f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
|
|
1090
|
-
shell=True,
|
|
1091
|
-
)
|
|
1092
|
-
p.communicate()
|
|
1093
|
-
|
|
1094
|
-
def tearDown(self) -> None:
|
|
1095
|
-
"""Clean up outputs."""
|
|
1096
|
-
unittest.TestCase.tearDown(self)
|
|
1097
|
-
|
|
1098
1307
|
@slow
|
|
1308
|
+
@needs_docker
|
|
1309
|
+
@pytest.mark.slow
|
|
1310
|
+
@pytest.mark.docker
|
|
1311
|
+
@pytest.mark.online
|
|
1099
1312
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1100
1313
|
def test_run_conformance(
|
|
1101
1314
|
self,
|
|
1315
|
+
cwl_v1_1_spec: Path,
|
|
1102
1316
|
caching: bool = False,
|
|
1103
1317
|
batchSystem: Optional[str] = None,
|
|
1104
1318
|
skipped_tests: Optional[str] = None,
|
|
1105
1319
|
extra_args: Optional[list[str]] = None,
|
|
1106
1320
|
) -> None:
|
|
1107
1321
|
run_conformance_tests(
|
|
1108
|
-
workDir=
|
|
1109
|
-
yml=
|
|
1322
|
+
workDir=str(cwl_v1_1_spec),
|
|
1323
|
+
yml=str(cwl_v1_1_spec / "conformance_tests.yaml"),
|
|
1110
1324
|
caching=caching,
|
|
1111
1325
|
batchSystem=batchSystem,
|
|
1112
1326
|
skipped_tests=skipped_tests,
|
|
@@ -1114,14 +1328,24 @@ class CWLv11Test(ToilTest):
|
|
|
1114
1328
|
)
|
|
1115
1329
|
|
|
1116
1330
|
@slow
|
|
1331
|
+
@needs_docker
|
|
1332
|
+
@pytest.mark.slow
|
|
1333
|
+
@pytest.mark.docker
|
|
1334
|
+
@pytest.mark.online
|
|
1117
1335
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1118
|
-
def test_run_conformance_with_caching(self) -> None:
|
|
1119
|
-
self.test_run_conformance(caching=True)
|
|
1336
|
+
def test_run_conformance_with_caching(self, cwl_v1_1_spec: Path) -> None:
|
|
1337
|
+
self.test_run_conformance(cwl_v1_1_spec, caching=True)
|
|
1120
1338
|
|
|
1121
1339
|
@slow
|
|
1122
1340
|
@needs_kubernetes
|
|
1123
|
-
|
|
1341
|
+
@pytest.mark.slow
|
|
1342
|
+
@pytest.mark.kubernetes
|
|
1343
|
+
@pytest.mark.online
|
|
1344
|
+
def test_kubernetes_cwl_conformance(
|
|
1345
|
+
self, cwl_v1_1_spec: Path, caching: bool = False
|
|
1346
|
+
) -> None:
|
|
1124
1347
|
self.test_run_conformance(
|
|
1348
|
+
cwl_v1_1_spec,
|
|
1125
1349
|
batchSystem="kubernetes",
|
|
1126
1350
|
extra_args=["--retryCount=3"],
|
|
1127
1351
|
# These tests don't work with
|
|
@@ -1133,44 +1357,50 @@ class CWLv11Test(ToilTest):
|
|
|
1133
1357
|
|
|
1134
1358
|
@slow
|
|
1135
1359
|
@needs_kubernetes
|
|
1136
|
-
|
|
1137
|
-
|
|
1360
|
+
@pytest.mark.slow
|
|
1361
|
+
@pytest.mark.kubernetes
|
|
1362
|
+
@pytest.mark.online
|
|
1363
|
+
def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_1_spec: Path) -> None:
|
|
1364
|
+
self.test_kubernetes_cwl_conformance(cwl_v1_1_spec, caching=True)
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
@pytest.fixture(scope="function")
|
|
1368
|
+
def cwl_v1_2_spec(tmp_path: Path) -> Generator[Path]:
|
|
1369
|
+
# The latest cwl git commit hash from https://github.com/common-workflow-language/cwl-v1.2
|
|
1370
|
+
# Update it to get the latest tests.
|
|
1371
|
+
testhash = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
|
|
1372
|
+
url = (
|
|
1373
|
+
"https://github.com/common-workflow-language/cwl-v1.2/archive/%s.zip" % testhash
|
|
1374
|
+
)
|
|
1375
|
+
urlretrieve(url, "spec.zip")
|
|
1376
|
+
with zipfile.ZipFile("spec.zip", "r") as z:
|
|
1377
|
+
z.extractall()
|
|
1378
|
+
shutil.move("cwl-v1.2-%s" % testhash, str(tmp_path))
|
|
1379
|
+
os.remove("spec.zip")
|
|
1380
|
+
try:
|
|
1381
|
+
yield tmp_path / ("cwl-v1.2-%s" % testhash)
|
|
1382
|
+
finally:
|
|
1383
|
+
pass # no cleanup
|
|
1138
1384
|
|
|
1139
1385
|
|
|
1140
1386
|
@needs_cwl
|
|
1141
1387
|
@needs_online
|
|
1142
|
-
|
|
1388
|
+
@pytest.mark.cwl
|
|
1389
|
+
@pytest.mark.online
|
|
1390
|
+
class TestCWLv12:
|
|
1143
1391
|
"""
|
|
1144
1392
|
Run the CWL 1.2 conformance tests in various environments.
|
|
1145
1393
|
"""
|
|
1146
1394
|
|
|
1147
|
-
rootDir: str
|
|
1148
|
-
cwlSpec: str
|
|
1149
|
-
test_yaml: str
|
|
1150
|
-
|
|
1151
|
-
@classmethod
|
|
1152
|
-
def setUpClass(cls) -> None:
|
|
1153
|
-
"""Runs anew before each test."""
|
|
1154
|
-
cls.rootDir = cls._projectRootPath()
|
|
1155
|
-
cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v12")
|
|
1156
|
-
cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
|
|
1157
|
-
# TODO: Use a commit zip in case someone decides to rewrite master's history?
|
|
1158
|
-
url = "https://github.com/common-workflow-language/cwl-v1.2.git"
|
|
1159
|
-
commit = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
|
|
1160
|
-
p = subprocess.Popen(
|
|
1161
|
-
f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
|
|
1162
|
-
shell=True,
|
|
1163
|
-
)
|
|
1164
|
-
p.communicate()
|
|
1165
|
-
|
|
1166
|
-
def tearDown(self) -> None:
|
|
1167
|
-
"""Clean up outputs."""
|
|
1168
|
-
unittest.TestCase.tearDown(self)
|
|
1169
|
-
|
|
1170
1395
|
@slow
|
|
1396
|
+
@needs_docker
|
|
1397
|
+
@pytest.mark.slow
|
|
1398
|
+
@pytest.mark.docker
|
|
1399
|
+
@pytest.mark.online
|
|
1171
1400
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1172
1401
|
def test_run_conformance(
|
|
1173
1402
|
self,
|
|
1403
|
+
cwl_v1_2_spec: Path,
|
|
1174
1404
|
runner: Optional[str] = None,
|
|
1175
1405
|
caching: bool = False,
|
|
1176
1406
|
batchSystem: Optional[str] = None,
|
|
@@ -1181,10 +1411,10 @@ class CWLv12Test(ToilTest):
|
|
|
1181
1411
|
junit_file: Optional[str] = None,
|
|
1182
1412
|
) -> None:
|
|
1183
1413
|
if junit_file is None:
|
|
1184
|
-
junit_file = os.path.
|
|
1414
|
+
junit_file = os.path.abspath("conformance-1.2.junit.xml")
|
|
1185
1415
|
run_conformance_tests(
|
|
1186
|
-
workDir=
|
|
1187
|
-
yml=
|
|
1416
|
+
workDir=str(cwl_v1_2_spec),
|
|
1417
|
+
yml=str(cwl_v1_2_spec / "conformance_tests.yaml"),
|
|
1188
1418
|
runner=runner,
|
|
1189
1419
|
caching=caching,
|
|
1190
1420
|
batchSystem=batchSystem,
|
|
@@ -1196,39 +1426,65 @@ class CWLv12Test(ToilTest):
|
|
|
1196
1426
|
)
|
|
1197
1427
|
|
|
1198
1428
|
@slow
|
|
1429
|
+
@needs_docker
|
|
1430
|
+
@pytest.mark.slow
|
|
1431
|
+
@pytest.mark.docker
|
|
1432
|
+
@pytest.mark.online
|
|
1199
1433
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1200
|
-
def test_run_conformance_with_caching(self) -> None:
|
|
1434
|
+
def test_run_conformance_with_caching(self, cwl_v1_2_spec: Path) -> None:
|
|
1201
1435
|
self.test_run_conformance(
|
|
1436
|
+
cwl_v1_2_spec,
|
|
1202
1437
|
caching=True,
|
|
1203
|
-
junit_file=os.path.
|
|
1438
|
+
junit_file=os.path.abspath("caching-conformance-1.2.junit.xml"),
|
|
1439
|
+
)
|
|
1440
|
+
|
|
1441
|
+
@slow
|
|
1442
|
+
@needs_docker
|
|
1443
|
+
@pytest.mark.slow
|
|
1444
|
+
@pytest.mark.docker
|
|
1445
|
+
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1446
|
+
def test_run_conformance_with_task_caching(
|
|
1447
|
+
self, cwl_v1_2_spec: Path, tmp_path: Path
|
|
1448
|
+
) -> None:
|
|
1449
|
+
self.test_run_conformance(
|
|
1450
|
+
cwl_v1_2_spec,
|
|
1451
|
+
junit_file=os.path.abspath("task-caching-conformance-1.2.junit.xml"),
|
|
1452
|
+
extra_args=["--cachedir", str(tmp_path / "task_cache")],
|
|
1204
1453
|
)
|
|
1205
1454
|
|
|
1206
1455
|
@slow
|
|
1456
|
+
@needs_docker
|
|
1457
|
+
@pytest.mark.slow
|
|
1458
|
+
@pytest.mark.docker
|
|
1207
1459
|
@pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
|
|
1208
|
-
def test_run_conformance_with_in_place_update(self) -> None:
|
|
1460
|
+
def test_run_conformance_with_in_place_update(self, cwl_v1_2_spec: Path) -> None:
|
|
1209
1461
|
"""
|
|
1210
1462
|
Make sure that with --bypass-file-store we properly support in place
|
|
1211
1463
|
update on a single node, and that this doesn't break any other
|
|
1212
1464
|
features.
|
|
1213
1465
|
"""
|
|
1214
1466
|
self.test_run_conformance(
|
|
1467
|
+
cwl_v1_2_spec,
|
|
1215
1468
|
extra_args=["--bypass-file-store"],
|
|
1216
1469
|
must_support_all_features=True,
|
|
1217
|
-
junit_file=os.path.
|
|
1218
|
-
self.rootDir, "in-place-update-conformance-1.2.junit.xml"
|
|
1219
|
-
),
|
|
1470
|
+
junit_file=os.path.abspath("in-place-update-conformance-1.2.junit.xml"),
|
|
1220
1471
|
)
|
|
1221
1472
|
|
|
1222
1473
|
@slow
|
|
1223
1474
|
@needs_kubernetes
|
|
1475
|
+
@pytest.mark.slow
|
|
1476
|
+
@pytest.mark.kubernetes
|
|
1477
|
+
@pytest.mark.online
|
|
1224
1478
|
def test_kubernetes_cwl_conformance(
|
|
1225
|
-
self,
|
|
1479
|
+
self,
|
|
1480
|
+
cwl_v1_2_spec: Path,
|
|
1481
|
+
caching: bool = False,
|
|
1482
|
+
junit_file: Optional[str] = None,
|
|
1226
1483
|
) -> None:
|
|
1227
1484
|
if junit_file is None:
|
|
1228
|
-
junit_file = os.path.
|
|
1229
|
-
self.rootDir, "kubernetes-conformance-1.2.junit.xml"
|
|
1230
|
-
)
|
|
1485
|
+
junit_file = os.path.abspath("kubernetes-conformance-1.2.junit.xml")
|
|
1231
1486
|
self.test_run_conformance(
|
|
1487
|
+
cwl_v1_2_spec,
|
|
1232
1488
|
caching=caching,
|
|
1233
1489
|
batchSystem="kubernetes",
|
|
1234
1490
|
extra_args=["--retryCount=3"],
|
|
@@ -1243,17 +1499,22 @@ class CWLv12Test(ToilTest):
|
|
|
1243
1499
|
|
|
1244
1500
|
@slow
|
|
1245
1501
|
@needs_kubernetes
|
|
1246
|
-
|
|
1502
|
+
@pytest.mark.slow
|
|
1503
|
+
@pytest.mark.kubernetes
|
|
1504
|
+
@pytest.mark.online
|
|
1505
|
+
def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_2_spec: Path) -> None:
|
|
1247
1506
|
self.test_kubernetes_cwl_conformance(
|
|
1507
|
+
cwl_v1_2_spec,
|
|
1248
1508
|
caching=True,
|
|
1249
|
-
junit_file=os.path.
|
|
1250
|
-
self.rootDir, "kubernetes-caching-conformance-1.2.junit.xml"
|
|
1251
|
-
),
|
|
1509
|
+
junit_file=os.path.abspath("kubernetes-caching-conformance-1.2.junit.xml"),
|
|
1252
1510
|
)
|
|
1253
1511
|
|
|
1254
1512
|
@slow
|
|
1255
1513
|
@needs_wes_server
|
|
1256
|
-
|
|
1514
|
+
@pytest.mark.slow
|
|
1515
|
+
@pytest.mark.wes_server
|
|
1516
|
+
@pytest.mark.online
|
|
1517
|
+
def test_wes_server_cwl_conformance(self, cwl_v1_2_spec: Path) -> None:
|
|
1257
1518
|
"""
|
|
1258
1519
|
Run the CWL conformance tests via WES. TOIL_WES_ENDPOINT must be
|
|
1259
1520
|
specified. If the WES server requires authentication, set TOIL_WES_USER
|
|
@@ -1264,7 +1525,7 @@ class CWLv12Test(ToilTest):
|
|
|
1264
1525
|
TOIL_WES_ENDPOINT=http://localhost:8080 \
|
|
1265
1526
|
TOIL_WES_USER=test \
|
|
1266
1527
|
TOIL_WES_PASSWORD=password \
|
|
1267
|
-
python -m pytest src/toil/test/cwl/cwlTest.py::
|
|
1528
|
+
python -m pytest src/toil/test/cwl/cwlTest.py::TestCWLv12::test_wes_server_cwl_conformance -vv --log-level INFO --log-cli-level INFO
|
|
1268
1529
|
"""
|
|
1269
1530
|
endpoint = os.environ.get("TOIL_WES_ENDPOINT")
|
|
1270
1531
|
extra_args = [f"--wes_endpoint={endpoint}"]
|
|
@@ -1279,6 +1540,7 @@ class CWLv12Test(ToilTest):
|
|
|
1279
1540
|
# e.g.: https://github.com/common-workflow-language/cwl-v1.2/blob/1.2.1_proposed/tests/mixed-versions/wf-v10.cwl#L4-L10
|
|
1280
1541
|
|
|
1281
1542
|
self.test_run_conformance(
|
|
1543
|
+
cwl_v1_2_spec,
|
|
1282
1544
|
runner="toil-wes-cwl-runner",
|
|
1283
1545
|
selected_tests="1-309,313-337",
|
|
1284
1546
|
extra_args=extra_args,
|
|
@@ -1286,183 +1548,162 @@ class CWLv12Test(ToilTest):
|
|
|
1286
1548
|
|
|
1287
1549
|
|
|
1288
1550
|
@needs_cwl
|
|
1551
|
+
@pytest.mark.cwl
|
|
1289
1552
|
@pytest.mark.cwl_small_log_dir
|
|
1290
1553
|
def test_workflow_echo_string_scatter_stderr_log_dir(tmp_path: Path) -> None:
|
|
1291
1554
|
log_dir = tmp_path / "cwl-logs"
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
new_file_loc = out_base["location"]
|
|
1316
|
-
assert (
|
|
1317
|
-
new_file_loc == file["location"]
|
|
1318
|
-
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1555
|
+
with get_data("test/cwl/echo_string_scatter_capture_stdout.cwl") as cwl_file:
|
|
1556
|
+
cmd = [
|
|
1557
|
+
"toil-cwl-runner",
|
|
1558
|
+
f"--jobStore={tmp_path / 'jobstore'}",
|
|
1559
|
+
"--strict-memory-limit",
|
|
1560
|
+
f"--log-dir={log_dir}",
|
|
1561
|
+
str(cwl_file),
|
|
1562
|
+
]
|
|
1563
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1564
|
+
outputs = json.loads(p.stdout)
|
|
1565
|
+
out_list = outputs["list_out"]
|
|
1566
|
+
assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
|
|
1567
|
+
out_base = outputs["list_out"][0]
|
|
1568
|
+
# This is a test on the scatter functionality and stdout.
|
|
1569
|
+
# Each value of scatter should generate a separate file in the output.
|
|
1570
|
+
for index, file in enumerate(out_list):
|
|
1571
|
+
if index > 0:
|
|
1572
|
+
new_file_loc = out_base["location"] + f"_{index + 1}"
|
|
1573
|
+
else:
|
|
1574
|
+
new_file_loc = out_base["location"]
|
|
1575
|
+
assert (
|
|
1576
|
+
new_file_loc == file["location"]
|
|
1577
|
+
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1319
1578
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1579
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1580
|
+
assert p.returncode == 0
|
|
1322
1581
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1582
|
+
assert log_dir.exists()
|
|
1583
|
+
scatter_0 = log_dir / "echo-test-scatter.0.scatter"
|
|
1584
|
+
scatter_1 = log_dir / "echo-test-scatter.1.scatter"
|
|
1585
|
+
list_0 = log_dir / "echo-test-scatter.0.list"
|
|
1586
|
+
list_1 = log_dir / "echo-test-scatter.1.list"
|
|
1587
|
+
assert scatter_0.exists()
|
|
1588
|
+
assert scatter_1.exists()
|
|
1589
|
+
assert list_0.exists()
|
|
1590
|
+
assert list_1.exists()
|
|
1332
1591
|
|
|
1333
1592
|
|
|
1334
1593
|
@needs_cwl
|
|
1594
|
+
@pytest.mark.cwl
|
|
1335
1595
|
@pytest.mark.cwl_small_log_dir
|
|
1336
1596
|
def test_log_dir_echo_no_output(tmp_path: Path) -> None:
|
|
1337
1597
|
log_dir = tmp_path / "cwl-logs"
|
|
1338
|
-
job_store = "test_log_dir_echo_no_output"
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
result = next(subdir.iterdir())
|
|
1360
|
-
assert result.name == "out.txt"
|
|
1361
|
-
output = open(result).read()
|
|
1362
|
-
assert "hello" in output
|
|
1598
|
+
job_store = tmp_path / "test_log_dir_echo_no_output"
|
|
1599
|
+
with get_data("test/cwl/echo-stdout-log-dir.cwl") as cwl_file:
|
|
1600
|
+
cmd = [
|
|
1601
|
+
"toil-cwl-runner",
|
|
1602
|
+
f"--jobStore={job_store}",
|
|
1603
|
+
"--strict-memory-limit",
|
|
1604
|
+
f"--log-dir={str(log_dir)}",
|
|
1605
|
+
str(cwl_file),
|
|
1606
|
+
]
|
|
1607
|
+
subprocess.run(cmd)
|
|
1608
|
+
|
|
1609
|
+
assert log_dir.exists()
|
|
1610
|
+
assert sum(1 for _ in log_dir.iterdir()) == 1
|
|
1611
|
+
|
|
1612
|
+
subdir = next(log_dir.iterdir())
|
|
1613
|
+
assert subdir.name == "echo"
|
|
1614
|
+
assert subdir.is_dir()
|
|
1615
|
+
assert sum(1 for _ in subdir.iterdir()) == 1
|
|
1616
|
+
result = next(subdir.iterdir())
|
|
1617
|
+
assert result.name == "out.txt"
|
|
1618
|
+
assert "hello" in result.read_text()
|
|
1363
1619
|
|
|
1364
1620
|
|
|
1365
1621
|
@needs_cwl
|
|
1622
|
+
@pytest.mark.cwl
|
|
1366
1623
|
@pytest.mark.cwl_small_log_dir
|
|
1367
1624
|
def test_log_dir_echo_stderr(tmp_path: Path) -> None:
|
|
1368
1625
|
log_dir = tmp_path / "cwl-logs"
|
|
1626
|
+
log_dir.mkdir()
|
|
1627
|
+
with get_data("test/cwl/echo-stderr.cwl") as cwl_file:
|
|
1628
|
+
cmd = [
|
|
1629
|
+
"toil-cwl-runner",
|
|
1630
|
+
f"--jobStore={str(tmp_path / 'test_log_dir_echo_stderr')}",
|
|
1631
|
+
"--strict-memory-limit",
|
|
1632
|
+
"--force-docker-pull",
|
|
1633
|
+
"--clean=always",
|
|
1634
|
+
f"--log-dir={str(log_dir)}",
|
|
1635
|
+
str(cwl_file),
|
|
1636
|
+
]
|
|
1637
|
+
subprocess.run(cmd)
|
|
1638
|
+
tmp_path = log_dir
|
|
1369
1639
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
stdout, stderr = p.communicate()
|
|
1381
|
-
tmp_path = log_dir
|
|
1382
|
-
|
|
1383
|
-
assert len(list(tmp_path.iterdir())) == 1
|
|
1384
|
-
|
|
1385
|
-
subdir = next(tmp_path.iterdir())
|
|
1386
|
-
assert subdir.name == "echo-stderr.cwl"
|
|
1387
|
-
assert subdir.is_dir()
|
|
1388
|
-
assert len(list(subdir.iterdir())) == 1
|
|
1389
|
-
result = next(subdir.iterdir())
|
|
1390
|
-
assert result.name == "out.txt"
|
|
1391
|
-
output = open(result).read()
|
|
1392
|
-
assert output == "hello\n"
|
|
1640
|
+
assert len(list(tmp_path.iterdir())) == 1
|
|
1641
|
+
|
|
1642
|
+
subdir = next(tmp_path.iterdir())
|
|
1643
|
+
assert subdir.name == "echo-stderr.cwl"
|
|
1644
|
+
assert subdir.is_dir()
|
|
1645
|
+
assert len(list(subdir.iterdir())) == 1
|
|
1646
|
+
result = next(subdir.iterdir())
|
|
1647
|
+
assert result.name == "out.txt"
|
|
1648
|
+
output = open(result).read()
|
|
1649
|
+
assert output == "hello\n"
|
|
1393
1650
|
|
|
1394
1651
|
|
|
1395
1652
|
# TODO: It's not clear how this test tests filename conflict resolution; it
|
|
1396
1653
|
# seems like it runs a python script to copy some files and makes sure the
|
|
1397
1654
|
# workflow doesn't fail.
|
|
1398
1655
|
@needs_cwl
|
|
1656
|
+
@pytest.mark.cwl
|
|
1399
1657
|
@pytest.mark.cwl_small_log_dir
|
|
1400
1658
|
def test_filename_conflict_resolution(tmp_path: Path) -> None:
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
cwl_inputs = ["--msin", input]
|
|
1414
|
-
cmd = [toil] + options + [cwl] + cwl_inputs
|
|
1415
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1416
|
-
stdout, stderr = p.communicate()
|
|
1417
|
-
assert b"Finished toil run successfully" in stderr
|
|
1418
|
-
assert p.returncode == 0
|
|
1659
|
+
with get_data("test/cwl/test_filename_conflict_resolution.cwl") as cwl_file:
|
|
1660
|
+
with get_data("test/cwl/test_filename_conflict_resolution.ms") as msin:
|
|
1661
|
+
cmd = [
|
|
1662
|
+
"toil-cwl-runner",
|
|
1663
|
+
f"--outdir={tmp_path}",
|
|
1664
|
+
str(cwl_file),
|
|
1665
|
+
"--msin",
|
|
1666
|
+
str(msin),
|
|
1667
|
+
]
|
|
1668
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1669
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1670
|
+
assert p.returncode == 0
|
|
1419
1671
|
|
|
1420
1672
|
|
|
1421
1673
|
@needs_cwl
|
|
1674
|
+
@pytest.mark.cwl
|
|
1422
1675
|
@pytest.mark.cwl_small_log_dir
|
|
1423
1676
|
def test_filename_conflict_resolution_3_or_more(tmp_path: Path) -> None:
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1433
|
-
stdout, stderr = p.communicate()
|
|
1434
|
-
assert b"Finished toil run successfully" in stderr
|
|
1435
|
-
assert p.returncode == 0
|
|
1436
|
-
assert (
|
|
1437
|
-
len(os.listdir(out_dir)) == 9
|
|
1438
|
-
), "All 9 files made by the scatter should be in the directory"
|
|
1677
|
+
with get_data("test/cwl/scatter_duplicate_outputs.cwl") as cwl_file:
|
|
1678
|
+
cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
|
|
1679
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1680
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1681
|
+
assert p.returncode == 0
|
|
1682
|
+
assert (
|
|
1683
|
+
sum(1 for _ in tmp_path.iterdir()) == 9
|
|
1684
|
+
), f"All 9 files made by the scatter should be in the directory: {tmp_path}"
|
|
1439
1685
|
|
|
1440
1686
|
|
|
1441
1687
|
@needs_cwl
|
|
1442
1688
|
@needs_docker
|
|
1689
|
+
@pytest.mark.cwl
|
|
1690
|
+
@pytest.mark.docker
|
|
1443
1691
|
@pytest.mark.cwl_small_log_dir
|
|
1444
1692
|
def test_filename_conflict_detection(tmp_path: Path) -> None:
|
|
1445
1693
|
"""
|
|
1446
1694
|
Make sure we don't just stage files over each other when using a container.
|
|
1447
1695
|
"""
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
]
|
|
1454
|
-
cwl = os.path.join(
|
|
1455
|
-
os.path.dirname(__file__), "test_filename_conflict_detection.cwl"
|
|
1456
|
-
)
|
|
1457
|
-
cmd = [toil] + options + [cwl]
|
|
1458
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1459
|
-
stdout, stderr = p.communicate()
|
|
1460
|
-
assert b"File staging conflict" in stderr
|
|
1461
|
-
assert p.returncode != 0
|
|
1696
|
+
with get_data("test/cwl/test_filename_conflict_detection.cwl") as cwl_file:
|
|
1697
|
+
cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
|
|
1698
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1699
|
+
assert "File staging conflict" in p.stderr
|
|
1700
|
+
assert p.returncode != 0
|
|
1462
1701
|
|
|
1463
1702
|
|
|
1464
1703
|
@needs_cwl
|
|
1465
1704
|
@needs_docker
|
|
1705
|
+
@pytest.mark.cwl
|
|
1706
|
+
@pytest.mark.docker
|
|
1466
1707
|
@pytest.mark.cwl_small_log_dir
|
|
1467
1708
|
def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
|
|
1468
1709
|
"""
|
|
@@ -1470,101 +1711,89 @@ def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
|
|
|
1470
1711
|
|
|
1471
1712
|
Specifically, when using a container and the files are at the root of the work dir.
|
|
1472
1713
|
"""
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
]
|
|
1479
|
-
cwl = os.path.join(
|
|
1480
|
-
os.path.dirname(__file__), "test_filename_conflict_detection_at_root.cwl"
|
|
1481
|
-
)
|
|
1482
|
-
cmd = [toil] + options + [cwl]
|
|
1483
|
-
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1484
|
-
stdout, stderr = p.communicate()
|
|
1485
|
-
assert b"File staging conflict" in stderr
|
|
1486
|
-
assert p.returncode != 0
|
|
1714
|
+
with get_data("test/cwl/test_filename_conflict_detection_at_root.cwl") as cwl_file:
|
|
1715
|
+
cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
|
|
1716
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1717
|
+
assert "File staging conflict" in p.stderr
|
|
1718
|
+
assert p.returncode != 0
|
|
1487
1719
|
|
|
1488
1720
|
|
|
1489
1721
|
@needs_cwl
|
|
1722
|
+
@pytest.mark.cwl
|
|
1490
1723
|
@pytest.mark.cwl_small
|
|
1491
|
-
def test_pick_value_with_one_null_value(
|
|
1724
|
+
def test_pick_value_with_one_null_value(
|
|
1725
|
+
caplog: pytest.LogCaptureFixture, tmp_path: Path
|
|
1726
|
+
) -> None:
|
|
1492
1727
|
"""
|
|
1493
1728
|
Make sure toil-cwl-runner does not false log a warning when pickValue is
|
|
1494
1729
|
used but outputSource only contains one null value. See: #3991.
|
|
1495
1730
|
"""
|
|
1496
1731
|
from toil.cwl import cwltoil
|
|
1497
1732
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
not in line
|
|
1508
|
-
)
|
|
1733
|
+
with get_data("test/cwl/conditional_wf.cwl") as cwl_file:
|
|
1734
|
+
with get_data("test/cwl/conditional_wf.yaml") as job_file:
|
|
1735
|
+
with caplog.at_level(logging.WARNING, logger="toil.cwl.cwltoil"):
|
|
1736
|
+
cwltoil.main([f"--outdir={tmp_path}", str(cwl_file), str(job_file)])
|
|
1737
|
+
for line in caplog.messages:
|
|
1738
|
+
assert (
|
|
1739
|
+
"You had a conditional step that did not run, but you did not use pickValue to handle the skipped input."
|
|
1740
|
+
not in line
|
|
1741
|
+
)
|
|
1509
1742
|
|
|
1510
1743
|
|
|
1511
1744
|
@needs_cwl
|
|
1745
|
+
@pytest.mark.cwl
|
|
1512
1746
|
@pytest.mark.cwl_small
|
|
1513
|
-
def test_workflow_echo_string() -> None:
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
), f"Got wrong output: {stdout2}\nWith error: {stderr2}"
|
|
1528
|
-
assert "Finished toil run successfully" in stderr2
|
|
1529
|
-
assert p.returncode == 0
|
|
1747
|
+
def test_workflow_echo_string(tmp_path: Path) -> None:
|
|
1748
|
+
with get_data("test/cwl/echo_string.cwl") as cwl_file:
|
|
1749
|
+
cmd = [
|
|
1750
|
+
"toil-cwl-runner",
|
|
1751
|
+
f"--jobStore=file:{tmp_path / 'jobstore'}",
|
|
1752
|
+
"--strict-memory-limit",
|
|
1753
|
+
str(cwl_file),
|
|
1754
|
+
]
|
|
1755
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1756
|
+
assert (
|
|
1757
|
+
p.stdout.strip() == "{}"
|
|
1758
|
+
), f"Got wrong output: {p.stdout}\nWith error: {p.stderr}"
|
|
1759
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1760
|
+
assert p.returncode == 0
|
|
1530
1761
|
|
|
1531
1762
|
|
|
1532
1763
|
@needs_cwl
|
|
1764
|
+
@pytest.mark.cwl
|
|
1533
1765
|
@pytest.mark.cwl_small
|
|
1534
|
-
def test_workflow_echo_string_scatter_capture_stdout() -> None:
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
new_file_loc = out_base["location"]
|
|
1559
|
-
assert (
|
|
1560
|
-
new_file_loc == file["location"]
|
|
1561
|
-
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1766
|
+
def test_workflow_echo_string_scatter_capture_stdout(tmp_path: Path) -> None:
|
|
1767
|
+
with get_data("test/cwl/echo_string_scatter_capture_stdout.cwl") as cwl_file:
|
|
1768
|
+
cmd = [
|
|
1769
|
+
"toil-cwl-runner",
|
|
1770
|
+
f"--jobStore=file:{tmp_path / 'jobStore'}",
|
|
1771
|
+
"--strict-memory-limit",
|
|
1772
|
+
str(cwl_file),
|
|
1773
|
+
]
|
|
1774
|
+
p = subprocess.run(cmd, capture_output=True, text=True)
|
|
1775
|
+
assert len(p.stdout) > 0
|
|
1776
|
+
outputs = json.loads(p.stdout)
|
|
1777
|
+
out_list = outputs["list_out"]
|
|
1778
|
+
assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
|
|
1779
|
+
out_base = outputs["list_out"][0]
|
|
1780
|
+
# This is a test on the scatter functionality and stdout.
|
|
1781
|
+
# Each value of scatter should generate a separate file in the output.
|
|
1782
|
+
for index, file in enumerate(out_list):
|
|
1783
|
+
if index > 0:
|
|
1784
|
+
new_file_loc = out_base["location"] + f"_{index + 1}"
|
|
1785
|
+
else:
|
|
1786
|
+
new_file_loc = out_base["location"]
|
|
1787
|
+
assert (
|
|
1788
|
+
new_file_loc == file["location"]
|
|
1789
|
+
), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
|
|
1562
1790
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1791
|
+
assert "Finished toil run successfully" in p.stderr
|
|
1792
|
+
assert p.returncode == 0
|
|
1565
1793
|
|
|
1566
1794
|
|
|
1567
1795
|
@needs_cwl
|
|
1796
|
+
@pytest.mark.cwl
|
|
1568
1797
|
@pytest.mark.cwl_small
|
|
1569
1798
|
def test_visit_top_cwl_class() -> None:
|
|
1570
1799
|
structure = {
|
|
@@ -1616,6 +1845,7 @@ def test_visit_top_cwl_class() -> None:
|
|
|
1616
1845
|
|
|
1617
1846
|
|
|
1618
1847
|
@needs_cwl
|
|
1848
|
+
@pytest.mark.cwl
|
|
1619
1849
|
@pytest.mark.cwl_small
|
|
1620
1850
|
def test_visit_cwl_class_and_reduce() -> None:
|
|
1621
1851
|
structure = {
|
|
@@ -1677,6 +1907,7 @@ def test_visit_cwl_class_and_reduce() -> None:
|
|
|
1677
1907
|
|
|
1678
1908
|
|
|
1679
1909
|
@needs_cwl
|
|
1910
|
+
@pytest.mark.cwl
|
|
1680
1911
|
@pytest.mark.cwl_small
|
|
1681
1912
|
def test_download_structure(tmp_path: Path) -> None:
|
|
1682
1913
|
"""
|
|
@@ -1701,7 +1932,7 @@ def test_download_structure(tmp_path: Path) -> None:
|
|
|
1701
1932
|
}
|
|
1702
1933
|
|
|
1703
1934
|
# Say where to put it on the filesystem
|
|
1704
|
-
to_dir =
|
|
1935
|
+
to_dir = tmp_path
|
|
1705
1936
|
|
|
1706
1937
|
# Make a fake file store
|
|
1707
1938
|
file_store = Mock(AbstractFileStore)
|
|
@@ -1714,7 +1945,7 @@ def test_download_structure(tmp_path: Path) -> None:
|
|
|
1714
1945
|
existing: dict[str, str] = {}
|
|
1715
1946
|
|
|
1716
1947
|
# Do the download
|
|
1717
|
-
download_structure(file_store, index, existing, structure, to_dir)
|
|
1948
|
+
download_structure(file_store, index, existing, structure, str(to_dir))
|
|
1718
1949
|
|
|
1719
1950
|
# Check the results
|
|
1720
1951
|
# 3 files should be made
|
|
@@ -1723,61 +1954,53 @@ def test_download_structure(tmp_path: Path) -> None:
|
|
|
1723
1954
|
assert len(existing) == 2
|
|
1724
1955
|
|
|
1725
1956
|
# Make sure that the index contents (path to URI) are correct
|
|
1726
|
-
assert
|
|
1727
|
-
assert
|
|
1728
|
-
assert
|
|
1957
|
+
assert str(to_dir / "dir1/dir2/f1") in index
|
|
1958
|
+
assert str(to_dir / "dir1/dir2/f1again") in index
|
|
1959
|
+
assert str(to_dir / "anotherfile") in index
|
|
1729
1960
|
assert (
|
|
1730
|
-
index[
|
|
1961
|
+
index[str(to_dir / "dir1/dir2/f1")]
|
|
1731
1962
|
== cast(
|
|
1732
1963
|
DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
|
|
1733
1964
|
)["f1"]
|
|
1734
1965
|
)
|
|
1735
1966
|
assert (
|
|
1736
|
-
index[
|
|
1967
|
+
index[str(to_dir / "dir1/dir2/f1again")]
|
|
1737
1968
|
== cast(
|
|
1738
1969
|
DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
|
|
1739
1970
|
)["f1again"]
|
|
1740
1971
|
)
|
|
1741
|
-
assert index[
|
|
1972
|
+
assert index[str(to_dir / "anotherfile")] == structure["anotherfile"]
|
|
1742
1973
|
|
|
1743
1974
|
# And the existing contents (URI to path)
|
|
1744
1975
|
assert "toilfile:" + fid1.pack() in existing
|
|
1745
1976
|
assert "toilfile:" + fid2.pack() in existing
|
|
1746
1977
|
assert existing["toilfile:" + fid1.pack()] in [
|
|
1747
|
-
|
|
1748
|
-
|
|
1978
|
+
str(to_dir / "dir1/dir2/f1"),
|
|
1979
|
+
str(to_dir / "dir1/dir2/f1again"),
|
|
1749
1980
|
]
|
|
1750
|
-
assert existing["toilfile:" + fid2.pack()] ==
|
|
1981
|
+
assert existing["toilfile:" + fid2.pack()] == str(to_dir / "anotherfile")
|
|
1751
1982
|
|
|
1752
1983
|
# The directory structure should be created for real
|
|
1753
|
-
assert
|
|
1754
|
-
assert
|
|
1755
|
-
assert
|
|
1756
|
-
assert
|
|
1984
|
+
assert (to_dir / "dir1").is_dir()
|
|
1985
|
+
assert (to_dir / "dir1/dir2").is_dir()
|
|
1986
|
+
assert (to_dir / "dir1/dir2/dir2sub").is_dir()
|
|
1987
|
+
assert (to_dir / "dir1/dir3").is_dir()
|
|
1757
1988
|
|
|
1758
1989
|
# The file store should have been asked to do the download
|
|
1759
1990
|
file_store.readGlobalFile.assert_has_calls(
|
|
1760
1991
|
[
|
|
1761
|
-
call(fid1,
|
|
1762
|
-
call(fid1,
|
|
1763
|
-
call(fid2,
|
|
1992
|
+
call(fid1, str(to_dir / "dir1/dir2/f1"), symlink=False),
|
|
1993
|
+
call(fid1, str(to_dir / "dir1/dir2/f1again"), symlink=False),
|
|
1994
|
+
call(fid2, str(to_dir / "anotherfile"), symlink=False),
|
|
1764
1995
|
],
|
|
1765
1996
|
any_order=True,
|
|
1766
1997
|
)
|
|
1767
1998
|
|
|
1768
1999
|
|
|
1769
2000
|
@needs_cwl
|
|
2001
|
+
@pytest.mark.cwl
|
|
1770
2002
|
@pytest.mark.timeout(300)
|
|
1771
2003
|
def test_import_on_workers() -> None:
|
|
1772
|
-
args = [
|
|
1773
|
-
"src/toil/test/cwl/download.cwl",
|
|
1774
|
-
"src/toil/test/cwl/download_file.json",
|
|
1775
|
-
"--runImportsOnWorkers",
|
|
1776
|
-
"--importWorkersDisk=10MiB",
|
|
1777
|
-
"--realTimeLogging=True",
|
|
1778
|
-
"--logLevel=INFO",
|
|
1779
|
-
"--logColors=False",
|
|
1780
|
-
]
|
|
1781
2004
|
from toil.cwl import cwltoil
|
|
1782
2005
|
|
|
1783
2006
|
detector = ImportWorkersMessageHandler()
|
|
@@ -1785,9 +2008,21 @@ def test_import_on_workers() -> None:
|
|
|
1785
2008
|
# Set up a log message detector to the root logger
|
|
1786
2009
|
logging.getLogger().addHandler(detector)
|
|
1787
2010
|
|
|
1788
|
-
|
|
2011
|
+
with get_data("test/cwl/download.cwl") as cwl_file:
|
|
2012
|
+
with get_data("test/cwl/directory/directory/file.txt") as file_path:
|
|
2013
|
+
args = [
|
|
2014
|
+
"--runImportsOnWorkers",
|
|
2015
|
+
"--importWorkersDisk=10MiB",
|
|
2016
|
+
"--realTimeLogging=True",
|
|
2017
|
+
"--logLevel=INFO",
|
|
2018
|
+
"--logColors=False",
|
|
2019
|
+
str(cwl_file),
|
|
2020
|
+
"--input",
|
|
2021
|
+
str(file_path),
|
|
2022
|
+
]
|
|
2023
|
+
cwltoil.main(args)
|
|
1789
2024
|
|
|
1790
|
-
|
|
2025
|
+
assert detector.detected is True
|
|
1791
2026
|
|
|
1792
2027
|
|
|
1793
2028
|
# StreamHandler is generic, _typeshed doesn't exist at runtime, do a bit of typing trickery, see https://github.com/python/typeshed/issues/5680
|