toil 8.1.0b1__py3-none-any.whl → 8.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toil/__init__.py +0 -35
- toil/batchSystems/abstractBatchSystem.py +1 -1
- toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
- toil/batchSystems/awsBatch.py +1 -1
- toil/batchSystems/cleanup_support.py +1 -1
- toil/batchSystems/kubernetes.py +53 -7
- toil/batchSystems/local_support.py +1 -1
- toil/batchSystems/mesos/batchSystem.py +13 -8
- toil/batchSystems/mesos/test/__init__.py +3 -2
- toil/batchSystems/singleMachine.py +1 -1
- toil/batchSystems/slurm.py +27 -26
- toil/bus.py +5 -3
- toil/common.py +39 -11
- toil/cwl/cwltoil.py +1 -1
- toil/job.py +64 -49
- toil/jobStores/abstractJobStore.py +24 -3
- toil/jobStores/fileJobStore.py +25 -1
- toil/jobStores/googleJobStore.py +104 -30
- toil/leader.py +9 -0
- toil/lib/accelerators.py +3 -1
- toil/lib/aws/utils.py.orig +504 -0
- toil/lib/bioio.py +1 -1
- toil/lib/docker.py +252 -91
- toil/lib/dockstore.py +11 -3
- toil/lib/exceptions.py +5 -3
- toil/lib/history.py +87 -13
- toil/lib/history_submission.py +23 -9
- toil/lib/io.py +34 -22
- toil/lib/misc.py +7 -1
- toil/lib/resources.py +2 -1
- toil/lib/threading.py +11 -10
- toil/options/common.py +8 -0
- toil/options/wdl.py +11 -0
- toil/server/api_spec/LICENSE +201 -0
- toil/server/api_spec/README.rst +5 -0
- toil/server/cli/wes_cwl_runner.py +2 -1
- toil/test/__init__.py +275 -115
- toil/test/batchSystems/batchSystemTest.py +227 -205
- toil/test/batchSystems/test_slurm.py +27 -0
- toil/test/cactus/pestis.tar.gz +0 -0
- toil/test/conftest.py +7 -0
- toil/test/cwl/2.fasta +11 -0
- toil/test/cwl/2.fastq +12 -0
- toil/test/cwl/conftest.py +1 -1
- toil/test/cwl/cwlTest.py +999 -867
- toil/test/cwl/directory/directory/file.txt +15 -0
- toil/test/cwl/download_directory_file.json +4 -0
- toil/test/cwl/download_directory_s3.json +4 -0
- toil/test/cwl/download_file.json +6 -0
- toil/test/cwl/download_http.json +6 -0
- toil/test/cwl/download_https.json +6 -0
- toil/test/cwl/download_s3.json +6 -0
- toil/test/cwl/download_subdirectory_file.json +5 -0
- toil/test/cwl/download_subdirectory_s3.json +5 -0
- toil/test/cwl/empty.json +1 -0
- toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
- toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
- toil/test/cwl/optional-file-exists.json +6 -0
- toil/test/cwl/optional-file-missing.json +6 -0
- toil/test/cwl/preemptible_expression.json +1 -0
- toil/test/cwl/revsort-job-missing.json +6 -0
- toil/test/cwl/revsort-job.json +6 -0
- toil/test/cwl/s3_secondary_file.json +16 -0
- toil/test/cwl/seqtk_seq_job.json +6 -0
- toil/test/cwl/stream.json +6 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
- toil/test/cwl/whale.txt +16 -0
- toil/test/docs/scripts/example_alwaysfail.py +38 -0
- toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
- toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
- toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
- toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
- toil/test/docs/scripts/tutorial_arguments.py +23 -0
- toil/test/docs/scripts/tutorial_debugging.patch +12 -0
- toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
- toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
- toil/test/docs/scripts/tutorial_docker.py +20 -0
- toil/test/docs/scripts/tutorial_dynamic.py +24 -0
- toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
- toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
- toil/test/docs/scripts/tutorial_helloworld.py +15 -0
- toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
- toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
- toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
- toil/test/docs/scripts/tutorial_managing.py +29 -0
- toil/test/docs/scripts/tutorial_managing2.py +56 -0
- toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
- toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
- toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
- toil/test/docs/scripts/tutorial_promises.py +25 -0
- toil/test/docs/scripts/tutorial_promises2.py +30 -0
- toil/test/docs/scripts/tutorial_quickstart.py +22 -0
- toil/test/docs/scripts/tutorial_requirements.py +44 -0
- toil/test/docs/scripts/tutorial_services.py +45 -0
- toil/test/docs/scripts/tutorial_staging.py +45 -0
- toil/test/docs/scripts/tutorial_stats.py +64 -0
- toil/test/lib/aws/test_iam.py +3 -1
- toil/test/lib/dockerTest.py +205 -122
- toil/test/lib/test_history.py +101 -77
- toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
- toil/test/provisioners/clusterTest.py +4 -4
- toil/test/provisioners/gceProvisionerTest.py +16 -14
- toil/test/sort/sort.py +4 -1
- toil/test/src/busTest.py +17 -17
- toil/test/src/deferredFunctionTest.py +145 -132
- toil/test/src/importExportFileTest.py +71 -63
- toil/test/src/jobEncapsulationTest.py +27 -28
- toil/test/src/jobServiceTest.py +149 -133
- toil/test/src/jobTest.py +219 -211
- toil/test/src/miscTests.py +66 -60
- toil/test/src/promisedRequirementTest.py +163 -169
- toil/test/src/regularLogTest.py +24 -24
- toil/test/src/resourceTest.py +82 -76
- toil/test/src/restartDAGTest.py +51 -47
- toil/test/src/resumabilityTest.py +24 -19
- toil/test/src/retainTempDirTest.py +60 -57
- toil/test/src/systemTest.py +17 -13
- toil/test/src/threadingTest.py +29 -32
- toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
- toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
- toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
- toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
- toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
- toil/test/utils/toilDebugTest.py +117 -102
- toil/test/utils/toilKillTest.py +54 -53
- toil/test/utils/utilsTest.py +303 -229
- toil/test/wdl/lint_error.wdl +9 -0
- toil/test/wdl/md5sum/empty_file.json +1 -0
- toil/test/wdl/md5sum/md5sum-gs.json +1 -0
- toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
- toil/test/wdl/md5sum/md5sum.input +1 -0
- toil/test/wdl/md5sum/md5sum.json +1 -0
- toil/test/wdl/md5sum/md5sum.wdl +25 -0
- toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
- toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
- toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
- toil/test/wdl/standard_library/as_map.json +16 -0
- toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
- toil/test/wdl/standard_library/as_pairs.json +7 -0
- toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
- toil/test/wdl/standard_library/ceil.json +3 -0
- toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
- toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
- toil/test/wdl/standard_library/collect_by_key.json +1 -0
- toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
- toil/test/wdl/standard_library/cross.json +11 -0
- toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
- toil/test/wdl/standard_library/flatten.json +7 -0
- toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
- toil/test/wdl/standard_library/floor.json +3 -0
- toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
- toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
- toil/test/wdl/standard_library/keys.json +8 -0
- toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
- toil/test/wdl/standard_library/length.json +7 -0
- toil/test/wdl/standard_library/length_as_input.wdl +16 -0
- toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
- toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
- toil/test/wdl/standard_library/length_invalid.json +3 -0
- toil/test/wdl/standard_library/range.json +3 -0
- toil/test/wdl/standard_library/range_0.json +3 -0
- toil/test/wdl/standard_library/range_as_input.wdl +17 -0
- toil/test/wdl/standard_library/range_invalid.json +3 -0
- toil/test/wdl/standard_library/read_boolean.json +3 -0
- toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_float.json +3 -0
- toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_int.json +3 -0
- toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_json.json +3 -0
- toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_lines.json +3 -0
- toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_map.json +3 -0
- toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_string.json +3 -0
- toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_tsv.json +3 -0
- toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
- toil/test/wdl/standard_library/round.json +3 -0
- toil/test/wdl/standard_library/round_as_command.wdl +16 -0
- toil/test/wdl/standard_library/round_as_input.wdl +16 -0
- toil/test/wdl/standard_library/size.json +3 -0
- toil/test/wdl/standard_library/size_as_command.wdl +17 -0
- toil/test/wdl/standard_library/size_as_output.wdl +36 -0
- toil/test/wdl/standard_library/stderr.json +3 -0
- toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
- toil/test/wdl/standard_library/stdout.json +3 -0
- toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
- toil/test/wdl/standard_library/sub.json +3 -0
- toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
- toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
- toil/test/wdl/standard_library/transpose.json +6 -0
- toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
- toil/test/wdl/standard_library/write_json.json +6 -0
- toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_lines.json +7 -0
- toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_map.json +6 -0
- toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_tsv.json +6 -0
- toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
- toil/test/wdl/standard_library/zip.json +12 -0
- toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
- toil/test/wdl/test.csv +3 -0
- toil/test/wdl/test.tsv +3 -0
- toil/test/wdl/testfiles/croo.wdl +38 -0
- toil/test/wdl/testfiles/drop_files.wdl +62 -0
- toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
- toil/test/wdl/testfiles/empty.txt +0 -0
- toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
- toil/test/wdl/testfiles/random.wdl +66 -0
- toil/test/wdl/testfiles/string_file_coercion.json +1 -0
- toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
- toil/test/wdl/testfiles/test.json +4 -0
- toil/test/wdl/testfiles/test_boolean.txt +1 -0
- toil/test/wdl/testfiles/test_float.txt +1 -0
- toil/test/wdl/testfiles/test_int.txt +1 -0
- toil/test/wdl/testfiles/test_lines.txt +5 -0
- toil/test/wdl/testfiles/test_map.txt +2 -0
- toil/test/wdl/testfiles/test_string.txt +1 -0
- toil/test/wdl/testfiles/url_to_file.wdl +13 -0
- toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
- toil/test/wdl/testfiles/vocab.json +1 -0
- toil/test/wdl/testfiles/vocab.wdl +66 -0
- toil/test/wdl/testfiles/wait.wdl +34 -0
- toil/test/wdl/wdl_specification/type_pair.json +23 -0
- toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
- toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
- toil/test/wdl/wdl_specification/v1_spec.json +1 -0
- toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
- toil/test/wdl/wdltoil_test.py +680 -407
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/version.py +9 -9
- toil/wdl/wdltoil.py +336 -123
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/METADATA +5 -4
- toil-8.2.0.dist-info/RECORD +439 -0
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
- toil-8.1.0b1.dist-info/RECORD +0 -259
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
- {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/top_level.txt +0 -0
toil/test/src/regularLogTest.py
CHANGED
|
@@ -14,34 +14,34 @@
|
|
|
14
14
|
import logging
|
|
15
15
|
import mimetypes
|
|
16
16
|
import os
|
|
17
|
+
from pathlib import Path
|
|
17
18
|
import subprocess
|
|
18
19
|
import sys
|
|
20
|
+
from typing import Optional
|
|
19
21
|
|
|
20
|
-
from toil.test import
|
|
22
|
+
from toil.test import pslow as slow
|
|
21
23
|
from toil.test.mesos import helloWorld
|
|
22
24
|
|
|
23
25
|
logging.basicConfig(level=logging.DEBUG)
|
|
24
26
|
logger = logging.getLogger(__name__)
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
class RegularLogTest
|
|
29
|
+
class RegularLogTest:
|
|
28
30
|
|
|
29
|
-
def
|
|
30
|
-
super().setUp()
|
|
31
|
-
self.tempDir = self._createTempDir(purpose="tempDir")
|
|
32
|
-
|
|
33
|
-
def _getFiles(self, dir):
|
|
31
|
+
def _getFiles(self, dirpath: Path) -> list[str]:
|
|
34
32
|
return [
|
|
35
|
-
os.path.join(
|
|
36
|
-
for f in os.listdir(
|
|
37
|
-
if os.path.isfile(os.path.join(
|
|
33
|
+
os.path.join(dirpath, f)
|
|
34
|
+
for f in os.listdir(dirpath)
|
|
35
|
+
if os.path.isfile(os.path.join(dirpath, f))
|
|
38
36
|
]
|
|
39
37
|
|
|
40
|
-
def _assertFileTypeExists(
|
|
38
|
+
def _assertFileTypeExists(
|
|
39
|
+
self, dirpath: Path, extension: str, encoding: Optional[str] = None
|
|
40
|
+
) -> None:
|
|
41
41
|
# an encoding of None implies no compression
|
|
42
|
-
logger.info("Checking for %s file in %s", extension,
|
|
43
|
-
onlyFiles = self._getFiles(
|
|
44
|
-
logger.info("Found: %s", str(os.listdir(
|
|
42
|
+
logger.info("Checking for %s file in %s", extension, dirpath)
|
|
43
|
+
onlyFiles = self._getFiles(dirpath)
|
|
44
|
+
logger.info("Found: %s", str(os.listdir(dirpath)))
|
|
45
45
|
onlyLogs = [f for f in onlyFiles if f.endswith(extension)]
|
|
46
46
|
logger.info("Found matching: %s", str(onlyLogs))
|
|
47
47
|
assert onlyLogs
|
|
@@ -57,10 +57,10 @@ class RegularLogTest(ToilTest):
|
|
|
57
57
|
assert f.read().startswith(b"\x1f\x8b")
|
|
58
58
|
else:
|
|
59
59
|
mime = mimetypes.guess_type(log)
|
|
60
|
-
|
|
60
|
+
assert mime[1] == encoding
|
|
61
61
|
|
|
62
62
|
@slow
|
|
63
|
-
def testLogToMaster(self):
|
|
63
|
+
def testLogToMaster(self) -> None:
|
|
64
64
|
toilOutput = subprocess.check_output(
|
|
65
65
|
[
|
|
66
66
|
sys.executable,
|
|
@@ -74,7 +74,7 @@ class RegularLogTest(ToilTest):
|
|
|
74
74
|
)
|
|
75
75
|
assert helloWorld.childMessage in toilOutput.decode("utf-8")
|
|
76
76
|
|
|
77
|
-
def testWriteLogs(self):
|
|
77
|
+
def testWriteLogs(self, tmp_path: Path) -> None:
|
|
78
78
|
subprocess.check_call(
|
|
79
79
|
[
|
|
80
80
|
sys.executable,
|
|
@@ -83,13 +83,13 @@ class RegularLogTest(ToilTest):
|
|
|
83
83
|
"./toilTest",
|
|
84
84
|
"--clean=always",
|
|
85
85
|
"--logLevel=debug",
|
|
86
|
-
"--writeLogs
|
|
86
|
+
f"--writeLogs={tmp_path}",
|
|
87
87
|
]
|
|
88
88
|
)
|
|
89
|
-
self._assertFileTypeExists(
|
|
89
|
+
self._assertFileTypeExists(tmp_path, ".log")
|
|
90
90
|
|
|
91
91
|
@slow
|
|
92
|
-
def testWriteGzipLogs(self):
|
|
92
|
+
def testWriteGzipLogs(self, tmp_path: Path) -> None:
|
|
93
93
|
subprocess.check_call(
|
|
94
94
|
[
|
|
95
95
|
sys.executable,
|
|
@@ -98,13 +98,13 @@ class RegularLogTest(ToilTest):
|
|
|
98
98
|
"./toilTest",
|
|
99
99
|
"--clean=always",
|
|
100
100
|
"--logLevel=debug",
|
|
101
|
-
"--writeLogsGzip
|
|
101
|
+
f"--writeLogsGzip={tmp_path}",
|
|
102
102
|
]
|
|
103
103
|
)
|
|
104
|
-
self._assertFileTypeExists(
|
|
104
|
+
self._assertFileTypeExists(tmp_path, ".log.gz", "gzip")
|
|
105
105
|
|
|
106
106
|
@slow
|
|
107
|
-
def testMultipleLogToMaster(self):
|
|
107
|
+
def testMultipleLogToMaster(self) -> None:
|
|
108
108
|
toilOutput = subprocess.check_output(
|
|
109
109
|
[
|
|
110
110
|
sys.executable,
|
|
@@ -118,7 +118,7 @@ class RegularLogTest(ToilTest):
|
|
|
118
118
|
)
|
|
119
119
|
assert helloWorld.parentMessage in toilOutput.decode("utf-8")
|
|
120
120
|
|
|
121
|
-
def testRegularLog(self):
|
|
121
|
+
def testRegularLog(self) -> None:
|
|
122
122
|
toilOutput = subprocess.check_output(
|
|
123
123
|
[
|
|
124
124
|
sys.executable,
|
toil/test/src/resourceTest.py
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
from collections.abc import Iterable
|
|
14
15
|
import importlib
|
|
15
16
|
import os
|
|
17
|
+
from pathlib import Path
|
|
16
18
|
import subprocess
|
|
17
19
|
import sys
|
|
18
20
|
import tempfile
|
|
@@ -22,44 +24,38 @@ from io import BytesIO
|
|
|
22
24
|
from textwrap import dedent
|
|
23
25
|
from unittest.mock import MagicMock, patch
|
|
24
26
|
from zipfile import ZipFile
|
|
27
|
+
from typing import Optional
|
|
25
28
|
|
|
26
29
|
from toil import inVirtualEnv
|
|
27
30
|
from toil.resource import ModuleDescriptor, Resource, ResourceException
|
|
28
|
-
from toil.test import ToilTest
|
|
29
31
|
from toil.version import exactPython
|
|
30
32
|
|
|
33
|
+
import pytest
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
def tempFileContaining(content, suffix=""):
|
|
35
|
+
|
|
36
|
+
def tempFileContaining(directory: Path, content: str, suffix: str = "") -> str:
|
|
34
37
|
"""
|
|
35
38
|
Write a file with the given contents, and keep it on disk as long as the context is active.
|
|
36
39
|
:param str content: The contents of the file.
|
|
37
40
|
:param str suffix: The extension to use for the temporary file.
|
|
38
41
|
"""
|
|
39
|
-
|
|
40
|
-
try:
|
|
42
|
+
with (directory / "temp").open("wb") as fd:
|
|
41
43
|
encoded = content.encode("utf-8")
|
|
42
|
-
assert
|
|
43
|
-
|
|
44
|
-
os.close(fd)
|
|
45
|
-
raise
|
|
46
|
-
else:
|
|
47
|
-
os.close(fd)
|
|
48
|
-
yield path
|
|
49
|
-
finally:
|
|
50
|
-
os.unlink(path)
|
|
44
|
+
assert fd.write(encoded) == len(encoded)
|
|
45
|
+
return str(directory / "temp")
|
|
51
46
|
|
|
52
47
|
|
|
53
|
-
class
|
|
48
|
+
class TestResource:
|
|
54
49
|
"""Test module descriptors and resources derived from them."""
|
|
55
50
|
|
|
56
|
-
def testStandAlone(self):
|
|
51
|
+
def testStandAlone(self, tmp_path: Path) -> None:
|
|
57
52
|
self._testExternal(
|
|
58
|
-
moduleName="userScript", pyFiles=("userScript.py", "helper.py")
|
|
53
|
+
tmp_path, moduleName="userScript", pyFiles=("userScript.py", "helper.py")
|
|
59
54
|
)
|
|
60
55
|
|
|
61
|
-
def testPackage(self):
|
|
56
|
+
def testPackage(self, tmp_path: Path) -> None:
|
|
62
57
|
self._testExternal(
|
|
58
|
+
tmp_path,
|
|
63
59
|
moduleName="foo.userScript",
|
|
64
60
|
pyFiles=(
|
|
65
61
|
"foo/__init__.py",
|
|
@@ -69,8 +65,9 @@ class ResourceTest(ToilTest):
|
|
|
69
65
|
),
|
|
70
66
|
)
|
|
71
67
|
|
|
72
|
-
def testVirtualEnv(self):
|
|
68
|
+
def testVirtualEnv(self, tmp_path: Path) -> None:
|
|
73
69
|
self._testExternal(
|
|
70
|
+
tmp_path,
|
|
74
71
|
moduleName="foo.userScript",
|
|
75
72
|
virtualenv=True,
|
|
76
73
|
pyFiles=(
|
|
@@ -84,34 +81,45 @@ class ResourceTest(ToilTest):
|
|
|
84
81
|
),
|
|
85
82
|
)
|
|
86
83
|
|
|
87
|
-
def testStandAloneInPackage(self):
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
def testStandAloneInPackage(self, tmp_path: Path) -> None:
|
|
85
|
+
with pytest.raises(ResourceException):
|
|
86
|
+
self._testExternal(
|
|
87
|
+
tmp_path,
|
|
88
|
+
moduleName="userScript",
|
|
89
|
+
pyFiles=("__init__.py", "userScript.py", "helper.py"),
|
|
90
|
+
)
|
|
94
91
|
|
|
95
|
-
def _testExternal(
|
|
96
|
-
|
|
92
|
+
def _testExternal(
|
|
93
|
+
self,
|
|
94
|
+
dirPath: Path,
|
|
95
|
+
moduleName: str,
|
|
96
|
+
pyFiles: Iterable[str],
|
|
97
|
+
virtualenv: bool = False,
|
|
98
|
+
) -> None:
|
|
97
99
|
if virtualenv:
|
|
98
|
-
|
|
100
|
+
assert inVirtualEnv()
|
|
99
101
|
# --never-download prevents silent upgrades to pip, wheel and setuptools
|
|
100
102
|
subprocess.check_call(
|
|
101
|
-
[
|
|
103
|
+
[
|
|
104
|
+
"virtualenv",
|
|
105
|
+
"--never-download",
|
|
106
|
+
"--python",
|
|
107
|
+
exactPython,
|
|
108
|
+
str(dirPath),
|
|
109
|
+
]
|
|
102
110
|
)
|
|
103
|
-
sitePackages =
|
|
111
|
+
sitePackages = dirPath / "lib" / exactPython / "site-packages"
|
|
104
112
|
# tuple assignment is necessary to make this line immediately precede the try:
|
|
105
|
-
oldPrefix, sys.prefix, dirPath = sys.prefix, dirPath, sitePackages
|
|
113
|
+
oldPrefix, sys.prefix, dirPath = sys.prefix, str(dirPath), sitePackages
|
|
106
114
|
else:
|
|
107
115
|
oldPrefix = None
|
|
108
116
|
try:
|
|
109
117
|
for relPath in pyFiles:
|
|
110
|
-
path =
|
|
111
|
-
|
|
112
|
-
with open(
|
|
118
|
+
path = dirPath / relPath
|
|
119
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
120
|
+
with path.open("w") as f:
|
|
113
121
|
f.write("pass\n")
|
|
114
|
-
sys.path.append(dirPath)
|
|
122
|
+
sys.path.append(str(dirPath))
|
|
115
123
|
try:
|
|
116
124
|
userScript = importlib.import_module(moduleName)
|
|
117
125
|
try:
|
|
@@ -124,39 +132,39 @@ class ResourceTest(ToilTest):
|
|
|
124
132
|
del userScript
|
|
125
133
|
while moduleName:
|
|
126
134
|
del sys.modules[moduleName]
|
|
127
|
-
|
|
135
|
+
assert moduleName not in sys.modules
|
|
128
136
|
moduleName = ".".join(moduleName.split(".")[:-1])
|
|
129
137
|
|
|
130
138
|
finally:
|
|
131
|
-
sys.path.remove(dirPath)
|
|
139
|
+
sys.path.remove(str(dirPath))
|
|
132
140
|
finally:
|
|
133
141
|
if oldPrefix:
|
|
134
142
|
sys.prefix = oldPrefix
|
|
135
143
|
|
|
136
|
-
def testBuiltIn(self):
|
|
144
|
+
def testBuiltIn(self) -> None:
|
|
137
145
|
# Create a ModuleDescriptor for the module containing ModuleDescriptor, i.e. toil.resource
|
|
138
146
|
module_name = ModuleDescriptor.__module__
|
|
139
|
-
|
|
147
|
+
assert module_name == "toil.resource"
|
|
140
148
|
self._test(module_name, shouldBelongToToil=True)
|
|
141
149
|
|
|
142
150
|
def _test(
|
|
143
151
|
self,
|
|
144
|
-
module_name,
|
|
145
|
-
shouldBelongToToil=False,
|
|
146
|
-
expectedContents=None,
|
|
147
|
-
allowExtraContents=True,
|
|
148
|
-
):
|
|
152
|
+
module_name: str,
|
|
153
|
+
shouldBelongToToil: bool = False,
|
|
154
|
+
expectedContents: Optional[Iterable[str]] = None,
|
|
155
|
+
allowExtraContents: bool = True,
|
|
156
|
+
) -> None:
|
|
149
157
|
module = ModuleDescriptor.forModule(module_name)
|
|
150
158
|
# Assert basic attributes and properties
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
assert module.belongsToToil == shouldBelongToToil
|
|
160
|
+
assert module.name == module_name
|
|
153
161
|
if shouldBelongToToil:
|
|
154
|
-
|
|
162
|
+
assert module.dirPath.endswith("/src")
|
|
155
163
|
|
|
156
164
|
# Before the module is saved as a resource, localize() and globalize() are identity
|
|
157
165
|
# methods. This should log.warnings.
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
assert module.localize() is module
|
|
167
|
+
assert module.globalize() is module
|
|
160
168
|
# Create a mock job store ...
|
|
161
169
|
jobStore = MagicMock()
|
|
162
170
|
# ... to generate a fake URL for the resource ...
|
|
@@ -185,18 +193,18 @@ class ResourceTest(ToilTest):
|
|
|
185
193
|
# contents. This is a bit brittle since it assumes that all the data is written in a
|
|
186
194
|
# single call to write(). If more calls are made we can easily concatenate them.
|
|
187
195
|
zipFile = file_handle.write.call_args_list[0][0][0]
|
|
188
|
-
|
|
196
|
+
assert zipFile.startswith(b"PK") # the magic header for ZIP files
|
|
189
197
|
|
|
190
198
|
# Check contents if requested
|
|
191
199
|
if expectedContents is not None:
|
|
192
200
|
with ZipFile(BytesIO(zipFile)) as _zipFile:
|
|
193
201
|
actualContents = set(_zipFile.namelist())
|
|
194
202
|
if allowExtraContents:
|
|
195
|
-
|
|
203
|
+
assert actualContents.issuperset(expectedContents)
|
|
196
204
|
else:
|
|
197
|
-
|
|
205
|
+
assert actualContents == expectedContents
|
|
198
206
|
|
|
199
|
-
|
|
207
|
+
assert resource.url == url
|
|
200
208
|
# Now we're on the worker. Prepare the storage for localized resources
|
|
201
209
|
Resource.prepareSystem()
|
|
202
210
|
try:
|
|
@@ -206,8 +214,8 @@ class ResourceTest(ToilTest):
|
|
|
206
214
|
# original resource. Lookup will also be used when we localize the module that was
|
|
207
215
|
# originally used to create the resource.
|
|
208
216
|
localResource = Resource.lookup(module._resourcePath)
|
|
209
|
-
|
|
210
|
-
|
|
217
|
+
assert resource == localResource
|
|
218
|
+
assert resource is not localResource
|
|
211
219
|
# Now show that we can localize the module using the registered resource. Set up a mock
|
|
212
220
|
# urlopen() that yields the zipped tree ...
|
|
213
221
|
mock_urlopen = MagicMock()
|
|
@@ -216,16 +224,16 @@ class ResourceTest(ToilTest):
|
|
|
216
224
|
# ... and use it to download and unpack the resource
|
|
217
225
|
localModule = module.localize()
|
|
218
226
|
# The name should be equal between original and localized resource ...
|
|
219
|
-
|
|
227
|
+
assert module.name == localModule.name
|
|
220
228
|
# ... but the directory should be different.
|
|
221
|
-
|
|
229
|
+
assert module.dirPath != localModule.dirPath
|
|
222
230
|
# Show that we can 'undo' localization. This is necessary when the user script's jobs
|
|
223
231
|
# are invoked on the worker where they generate more child jobs.
|
|
224
|
-
|
|
232
|
+
assert localModule.globalize() == module
|
|
225
233
|
finally:
|
|
226
234
|
Resource.cleanSystem()
|
|
227
235
|
|
|
228
|
-
def testNonPyStandAlone(self):
|
|
236
|
+
def testNonPyStandAlone(self, tmp_path: Path) -> None:
|
|
229
237
|
"""
|
|
230
238
|
Asserts that Toil enforces the user script to have a .py or .pyc extension because that's
|
|
231
239
|
the only way auto-deployment can re-import the module on a worker. See
|
|
@@ -234,13 +242,13 @@ class ResourceTest(ToilTest):
|
|
|
234
242
|
https://github.com/BD2KGenomics/toil/issues/858
|
|
235
243
|
"""
|
|
236
244
|
|
|
237
|
-
def script():
|
|
245
|
+
def script() -> None:
|
|
238
246
|
from configargparse import ArgumentParser
|
|
239
247
|
|
|
240
248
|
from toil.common import Toil
|
|
241
249
|
from toil.job import Job
|
|
242
250
|
|
|
243
|
-
def fn():
|
|
251
|
+
def fn() -> None:
|
|
244
252
|
pass
|
|
245
253
|
|
|
246
254
|
if __name__ == "__main__":
|
|
@@ -253,17 +261,15 @@ class ResourceTest(ToilTest):
|
|
|
253
261
|
|
|
254
262
|
scriptBody = dedent("\n".join(getsource(script).split("\n")[1:]))
|
|
255
263
|
shebang = "#! %s\n" % sys.executable
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
self.assertNotEqual(0, process.returncode)
|
|
269
|
-
self.assertFalse(os.path.exists(jobStorePath))
|
|
264
|
+
scriptPath = tempFileContaining(tmp_path, shebang + scriptBody)
|
|
265
|
+
assert not scriptPath.endswith((".py", ".pyc"))
|
|
266
|
+
os.chmod(scriptPath, 0o755)
|
|
267
|
+
jobStorePath = scriptPath + ".jobStore"
|
|
268
|
+
process = subprocess.Popen([scriptPath, jobStorePath], stderr=subprocess.PIPE)
|
|
269
|
+
stdout, stderr = process.communicate()
|
|
270
|
+
assert (
|
|
271
|
+
"The name of a user script/module must end in .py or .pyc."
|
|
272
|
+
in stderr.decode("utf-8")
|
|
273
|
+
)
|
|
274
|
+
assert 0 != process.returncode
|
|
275
|
+
assert not os.path.exists(jobStorePath)
|
toil/test/src/restartDAGTest.py
CHANGED
|
@@ -15,41 +15,43 @@
|
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
17
|
import os
|
|
18
|
-
import
|
|
18
|
+
from pathlib import Path
|
|
19
19
|
import signal
|
|
20
|
+
from typing import Optional
|
|
20
21
|
|
|
21
22
|
from toil.common import Toil
|
|
22
23
|
from toil.exceptions import FailedJobsException
|
|
23
24
|
from toil.job import Job
|
|
24
|
-
from toil.test import
|
|
25
|
+
from toil.test import pslow as slow
|
|
26
|
+
|
|
27
|
+
import pytest
|
|
25
28
|
|
|
26
29
|
logger = logging.getLogger(__name__)
|
|
27
30
|
|
|
28
31
|
|
|
29
|
-
class
|
|
32
|
+
class TestRestartDAG:
|
|
30
33
|
"""
|
|
31
34
|
Tests that restarted job DAGs don't run children of jobs that failed in the first run till the
|
|
32
35
|
parent completes successfully in the restart.
|
|
33
36
|
"""
|
|
34
37
|
|
|
35
|
-
def setUp(self):
|
|
36
|
-
super().setUp()
|
|
37
|
-
self.tempDir = self._createTempDir(purpose="tempDir")
|
|
38
|
-
self.testJobStore = self._getTestJobStorePath()
|
|
39
|
-
|
|
40
|
-
def tearDown(self):
|
|
41
|
-
super().tearDown()
|
|
42
|
-
shutil.rmtree(self.testJobStore)
|
|
43
|
-
|
|
44
38
|
@slow
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
@pytest.mark.slow
|
|
40
|
+
def testRestartedWorkflowSchedulesCorrectJobsOnFailedParent(
|
|
41
|
+
self, tmp_path: Path
|
|
42
|
+
) -> None:
|
|
43
|
+
self._testRestartedWorkflowSchedulesCorrectJobs(tmp_path, "raise")
|
|
47
44
|
|
|
48
45
|
@slow
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
@pytest.mark.slow
|
|
47
|
+
def testRestartedWorkflowSchedulesCorrectJobsOnKilledParent(
|
|
48
|
+
self, tmp_path: Path
|
|
49
|
+
) -> None:
|
|
50
|
+
self._testRestartedWorkflowSchedulesCorrectJobs(tmp_path, "kill")
|
|
51
|
+
|
|
52
|
+
def _testRestartedWorkflowSchedulesCorrectJobs(
|
|
53
|
+
self, tmp_path: Path, failType: str
|
|
54
|
+
) -> None:
|
|
53
55
|
"""
|
|
54
56
|
Creates a diamond DAG
|
|
55
57
|
/->passingParent-\
|
|
@@ -63,19 +65,19 @@ class RestartDAGTest(ToilTest):
|
|
|
63
65
|
:param str failType: Does failingParent fail on an assertionError, or is it killed.
|
|
64
66
|
"""
|
|
65
67
|
# Specify options
|
|
66
|
-
options = Job.Runner.getDefaultOptions(
|
|
68
|
+
options = Job.Runner.getDefaultOptions(tmp_path / "jobstore")
|
|
67
69
|
options.logLevel = "DEBUG"
|
|
68
70
|
options.retryCount = 0
|
|
69
71
|
options.clean = "never"
|
|
70
72
|
|
|
71
|
-
parentFile =
|
|
72
|
-
childFile =
|
|
73
|
+
parentFile = tmp_path / "parent"
|
|
74
|
+
childFile = tmp_path / "child"
|
|
73
75
|
|
|
74
76
|
# Make the first job
|
|
75
77
|
root = Job.wrapJobFn(passingFn)
|
|
76
78
|
passingParent = Job.wrapJobFn(passingFn)
|
|
77
|
-
failingParent = Job.wrapJobFn(failingFn, failType=failType,
|
|
78
|
-
child = Job.wrapJobFn(passingFn,
|
|
79
|
+
failingParent = Job.wrapJobFn(failingFn, failType=failType, file=parentFile)
|
|
80
|
+
child = Job.wrapJobFn(passingFn, file=childFile)
|
|
79
81
|
|
|
80
82
|
# define the DAG
|
|
81
83
|
root.addChild(passingParent)
|
|
@@ -85,11 +87,12 @@ class RestartDAGTest(ToilTest):
|
|
|
85
87
|
|
|
86
88
|
failReasons = []
|
|
87
89
|
|
|
88
|
-
assert not
|
|
90
|
+
assert not childFile.exists()
|
|
89
91
|
|
|
92
|
+
errorRaised: Optional[BaseException] = None
|
|
90
93
|
# Run the test
|
|
91
94
|
for runMode in "start", "restart":
|
|
92
|
-
|
|
95
|
+
errorRaised = None
|
|
93
96
|
try:
|
|
94
97
|
with Toil(options) as toil:
|
|
95
98
|
if runMode == "start":
|
|
@@ -98,70 +101,71 @@ class RestartDAGTest(ToilTest):
|
|
|
98
101
|
toil.restart()
|
|
99
102
|
except Exception as e:
|
|
100
103
|
logger.exception(e)
|
|
101
|
-
|
|
104
|
+
errorRaised = e
|
|
102
105
|
finally:
|
|
103
106
|
# The processing of an AssertionError and FailedJobsException is the same so we do
|
|
104
107
|
# it together in this finally clause.
|
|
105
|
-
if
|
|
106
|
-
if not
|
|
108
|
+
if errorRaised is not None:
|
|
109
|
+
if not parentFile.exists():
|
|
107
110
|
failReasons.append(
|
|
108
111
|
'The failing parent file did not exist on toil "%s".'
|
|
109
112
|
% runMode
|
|
110
113
|
)
|
|
111
|
-
if
|
|
114
|
+
if childFile.exists():
|
|
112
115
|
failReasons.append(
|
|
113
116
|
"The child file existed. i.e. the child was run on "
|
|
114
117
|
'toil "%s".' % runMode
|
|
115
118
|
)
|
|
116
|
-
if isinstance(
|
|
117
|
-
if
|
|
119
|
+
if isinstance(errorRaised, FailedJobsException):
|
|
120
|
+
if errorRaised.numberOfFailedJobs != 3:
|
|
118
121
|
failReasons.append(
|
|
119
122
|
'FailedJobsException was raised on toil "%s" but '
|
|
120
123
|
"the number of failed jobs (%s) was not 3."
|
|
121
|
-
% (runMode,
|
|
124
|
+
% (runMode, errorRaised.numberOfFailedJobs)
|
|
122
125
|
)
|
|
123
|
-
elif isinstance(
|
|
126
|
+
elif isinstance(errorRaised, AssertionError):
|
|
124
127
|
failReasons.append(
|
|
125
128
|
"Toil raised an AssertionError instead of a "
|
|
126
129
|
'FailedJobsException on toil "%s".' % runMode
|
|
127
130
|
)
|
|
128
131
|
else:
|
|
129
|
-
failReasons.append("Toil raised error: %s" %
|
|
130
|
-
|
|
132
|
+
failReasons.append("Toil raised error: %s" % errorRaised)
|
|
133
|
+
errorRaised = None
|
|
131
134
|
options.restart = True
|
|
132
135
|
else:
|
|
133
|
-
|
|
136
|
+
pytest.fail('No errors were raised on toil "%s".' % runMode)
|
|
134
137
|
if failReasons:
|
|
135
|
-
|
|
138
|
+
pytest.fail(
|
|
136
139
|
"Test failed for ({}) reasons:\n\t{}".format(
|
|
137
140
|
len(failReasons), "\n\t".join(failReasons)
|
|
138
141
|
)
|
|
139
142
|
)
|
|
140
143
|
|
|
141
144
|
|
|
142
|
-
def passingFn(job,
|
|
145
|
+
def passingFn(job: Job, file: Optional[Path] = None) -> None:
|
|
143
146
|
"""
|
|
144
|
-
This function is guaranteed to pass as it does nothing out of the ordinary.
|
|
145
|
-
|
|
147
|
+
This function is guaranteed to pass as it does nothing out of the ordinary.
|
|
148
|
+
|
|
149
|
+
If "file" is provided, it will be created.
|
|
146
150
|
|
|
147
|
-
:param
|
|
151
|
+
:param file: The path of a file that must be created if provided.
|
|
148
152
|
"""
|
|
149
|
-
if
|
|
153
|
+
if file is not None:
|
|
150
154
|
# Emulates system touch.
|
|
151
|
-
open(
|
|
155
|
+
file.open("w").close()
|
|
152
156
|
|
|
153
157
|
|
|
154
|
-
def failingFn(job, failType,
|
|
158
|
+
def failingFn(job: Job, failType: str, file: Path) -> None:
|
|
155
159
|
"""
|
|
156
160
|
This function is guaranteed to fail via a raised assertion, or an os.kill
|
|
157
161
|
|
|
158
162
|
:param job: Job
|
|
159
|
-
:param
|
|
160
|
-
:param
|
|
163
|
+
:param failType: 'raise' or 'kill
|
|
164
|
+
:param file: The path of a file that must be created.
|
|
161
165
|
"""
|
|
162
166
|
assert failType in ("raise", "kill")
|
|
163
167
|
# Use that function to avoid code redundancy
|
|
164
|
-
passingFn(job,
|
|
168
|
+
passingFn(job, file)
|
|
165
169
|
|
|
166
170
|
if failType == "raise":
|
|
167
171
|
assert False
|