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/jobServiceTest.py
CHANGED
|
@@ -13,33 +13,34 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import codecs
|
|
15
15
|
import logging
|
|
16
|
-
import
|
|
16
|
+
from pathlib import Path
|
|
17
17
|
import random
|
|
18
18
|
import sys
|
|
19
19
|
import time
|
|
20
20
|
import traceback
|
|
21
|
+
from typing import cast, Any, Literal
|
|
21
22
|
from threading import Event, Thread
|
|
22
|
-
from unittest import skipIf
|
|
23
23
|
|
|
24
24
|
import pytest
|
|
25
25
|
|
|
26
26
|
from toil.batchSystems.singleMachine import SingleMachineBatchSystem
|
|
27
27
|
from toil.exceptions import FailedJobsException
|
|
28
|
-
from toil.job import Job
|
|
29
|
-
from toil.
|
|
30
|
-
from toil.
|
|
31
|
-
from toil.test import
|
|
28
|
+
from toil.job import Job, ServiceHostJob
|
|
29
|
+
from toil.jobStores.abstractJobStore import AbstractJobStore
|
|
30
|
+
from toil.batchSystems import DeadlockException
|
|
31
|
+
from toil.test import pslow as slow
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger(__name__)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class
|
|
36
|
+
class TestJobService:
|
|
37
37
|
"""
|
|
38
38
|
Tests testing the Job.Service class
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
41
|
@slow
|
|
42
|
-
|
|
42
|
+
@pytest.mark.slow
|
|
43
|
+
def testServiceSerialization(self, tmp_path: Path) -> None:
|
|
43
44
|
"""
|
|
44
45
|
Tests that a service can receive a promise without producing a serialization
|
|
45
46
|
error.
|
|
@@ -47,114 +48,119 @@ class JobServiceTest(ToilTest):
|
|
|
47
48
|
job = Job()
|
|
48
49
|
service = ToySerializableService("woot")
|
|
49
50
|
startValue = job.addService(service) # Add a first service to job
|
|
50
|
-
subService = ToySerializableService(
|
|
51
|
+
subService = ToySerializableService(
|
|
52
|
+
cast(str, startValue)
|
|
53
|
+
) # Now create a child of
|
|
51
54
|
# that service that takes the start value promise from the parent service
|
|
52
55
|
job.addService(subService, parentService=service) # This should work if
|
|
53
56
|
# serialization on services is working correctly.
|
|
54
57
|
|
|
55
|
-
self.runToil(job)
|
|
58
|
+
self.runToil(tmp_path, job)
|
|
56
59
|
|
|
57
60
|
@slow
|
|
58
|
-
|
|
61
|
+
@pytest.mark.slow
|
|
62
|
+
def testService(self, tmp_path: Path, checkpoint: bool = False) -> None:
|
|
59
63
|
"""
|
|
60
64
|
Tests the creation of a Job.Service with random failures of the worker.
|
|
61
65
|
"""
|
|
62
66
|
for test in range(2):
|
|
63
|
-
outFile =
|
|
67
|
+
outFile = tmp_path / f"test{test}"
|
|
64
68
|
messageInt = random.randint(1, sys.maxsize)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
t = Job.wrapJobFn(
|
|
68
|
-
serviceTest, outFile, messageInt, checkpoint=checkpoint
|
|
69
|
-
)
|
|
69
|
+
# Wire up the services/jobs
|
|
70
|
+
t = Job.wrapJobFn(serviceTest, outFile, messageInt, checkpoint=checkpoint)
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
# Run the workflow repeatedly until success
|
|
73
|
+
sub_tmp_path = tmp_path / str(test)
|
|
74
|
+
sub_tmp_path.mkdir()
|
|
75
|
+
self.runToil(sub_tmp_path, t)
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
finally:
|
|
77
|
-
os.remove(outFile)
|
|
77
|
+
# Check output
|
|
78
|
+
assert int(outFile.read_text()) == messageInt
|
|
78
79
|
|
|
79
80
|
@slow
|
|
80
|
-
@
|
|
81
|
+
@pytest.mark.slow
|
|
82
|
+
@pytest.mark.skipif(
|
|
81
83
|
SingleMachineBatchSystem.numCores < 4,
|
|
82
|
-
"Need at least four cores to run this test",
|
|
84
|
+
reason="Need at least four cores to run this test",
|
|
83
85
|
)
|
|
84
|
-
def testServiceDeadlock(self):
|
|
86
|
+
def testServiceDeadlock(self, tmp_path: Path) -> None:
|
|
85
87
|
"""
|
|
86
88
|
Creates a job with more services than maxServices, checks that deadlock is detected.
|
|
87
89
|
"""
|
|
88
|
-
outFile =
|
|
89
|
-
|
|
90
|
+
outFile = tmp_path / "out"
|
|
91
|
+
|
|
92
|
+
def makeWorkflow() -> Job:
|
|
93
|
+
job = Job()
|
|
94
|
+
r1 = job.addService(ToySerializableService("woot1"))
|
|
95
|
+
r2 = job.addService(ToySerializableService("woot2"))
|
|
96
|
+
r3 = job.addService(ToySerializableService("woot3"))
|
|
97
|
+
job.addChildFn(fnTest, [r1, r2, r3], outFile)
|
|
98
|
+
return job
|
|
99
|
+
|
|
100
|
+
# This should fail as too few services available
|
|
101
|
+
sub_tmp_path1 = tmp_path / "1"
|
|
102
|
+
sub_tmp_path1.mkdir()
|
|
103
|
+
with pytest.raises(DeadlockException):
|
|
104
|
+
self.runToil(
|
|
105
|
+
sub_tmp_path1,
|
|
106
|
+
makeWorkflow(),
|
|
107
|
+
badWorker=0.0,
|
|
108
|
+
maxServiceJobs=2,
|
|
109
|
+
deadlockWait=5,
|
|
110
|
+
)
|
|
90
111
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return job
|
|
112
|
+
# This should pass, as adequate services available
|
|
113
|
+
sub_tmp_path2 = tmp_path / "2"
|
|
114
|
+
sub_tmp_path2.mkdir()
|
|
115
|
+
self.runToil(sub_tmp_path2, makeWorkflow(), maxServiceJobs=3)
|
|
116
|
+
# Check we get expected output
|
|
117
|
+
assert outFile.read_text() == "woot1 woot2 woot3"
|
|
98
118
|
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
self.runToil(
|
|
102
|
-
makeWorkflow(), badWorker=0.0, maxServiceJobs=2, deadlockWait=5
|
|
103
|
-
)
|
|
104
|
-
except DeadlockException:
|
|
105
|
-
print("Got expected deadlock exception")
|
|
106
|
-
else:
|
|
107
|
-
assert 0
|
|
108
|
-
|
|
109
|
-
# This should pass, as adequate services available
|
|
110
|
-
self.runToil(makeWorkflow(), maxServiceJobs=3)
|
|
111
|
-
# Check we get expected output
|
|
112
|
-
assert open(outFile).read() == "woot1 woot2 woot3"
|
|
113
|
-
finally:
|
|
114
|
-
os.remove(outFile)
|
|
115
|
-
|
|
116
|
-
def testServiceWithCheckpoints(self):
|
|
119
|
+
def testServiceWithCheckpoints(self, tmp_path: Path) -> None:
|
|
117
120
|
"""
|
|
118
121
|
Tests the creation of a Job.Service with random failures of the worker, making the root job use checkpointing to
|
|
119
122
|
restart the subtree.
|
|
120
123
|
"""
|
|
121
|
-
self.testService(checkpoint=True)
|
|
124
|
+
self.testService(tmp_path, checkpoint=True)
|
|
122
125
|
|
|
123
126
|
@slow
|
|
124
|
-
@
|
|
127
|
+
@pytest.mark.slow
|
|
128
|
+
@pytest.mark.skipif(
|
|
125
129
|
SingleMachineBatchSystem.numCores < 4,
|
|
126
|
-
"Need at least four cores to run this test",
|
|
130
|
+
reason="Need at least four cores to run this test",
|
|
127
131
|
)
|
|
128
|
-
def testServiceRecursive(self, checkpoint=True):
|
|
132
|
+
def testServiceRecursive(self, tmp_path: Path, checkpoint: bool = True) -> None:
|
|
129
133
|
"""
|
|
130
134
|
Tests the creation of a Job.Service, creating a chain of services and accessing jobs.
|
|
131
135
|
Randomly fails the worker.
|
|
132
136
|
"""
|
|
133
137
|
for test in range(1):
|
|
134
138
|
# Temporary file
|
|
135
|
-
outFile =
|
|
139
|
+
outFile = tmp_path / f"test{test}"
|
|
136
140
|
messages = [random.randint(1, sys.maxsize) for i in range(3)]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
141
|
+
# Wire up the services/jobs
|
|
142
|
+
t = Job.wrapJobFn(
|
|
143
|
+
serviceTestRecursive, outFile, messages, checkpoint=checkpoint
|
|
144
|
+
)
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
# Run the workflow repeatedly until success
|
|
147
|
+
sub_tmp_path = tmp_path / str(test)
|
|
148
|
+
sub_tmp_path.mkdir()
|
|
149
|
+
self.runToil(sub_tmp_path, t)
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
finally:
|
|
149
|
-
os.remove(outFile)
|
|
151
|
+
# Check output
|
|
152
|
+
assert list(map(int, outFile.open().readlines())) == messages
|
|
150
153
|
|
|
151
154
|
@slow
|
|
152
|
-
@
|
|
155
|
+
@pytest.mark.slow
|
|
156
|
+
@pytest.mark.skipif(
|
|
153
157
|
SingleMachineBatchSystem.numCores < 4,
|
|
154
|
-
"Need at least four cores to run this test",
|
|
158
|
+
reason="Need at least four cores to run this test",
|
|
155
159
|
)
|
|
156
160
|
@pytest.mark.timeout(1200)
|
|
157
|
-
def testServiceParallelRecursive(
|
|
161
|
+
def testServiceParallelRecursive(
|
|
162
|
+
self, tmp_path: Path, checkpoint: bool = True
|
|
163
|
+
) -> None:
|
|
158
164
|
"""
|
|
159
165
|
Tests the creation of a Job.Service, creating parallel chains of services and accessing jobs.
|
|
160
166
|
Randomly fails the worker.
|
|
@@ -176,51 +182,54 @@ class JobServiceTest(ToilTest):
|
|
|
176
182
|
logger.info("This test will fail spuriously with probability %s", p_test_failure)
|
|
177
183
|
|
|
178
184
|
# We want to run the workflow through several times to test restarting, so we need it to often fail but reliably sometimes succeed, and almost always succeed when repeated.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
|
|
186
|
+
assert 0.8 > p_workflow_success
|
|
187
|
+
assert p_workflow_success > 0.2
|
|
188
|
+
assert 0.001 > p_test_failure
|
|
184
189
|
|
|
185
190
|
for test in range(1):
|
|
186
191
|
# Temporary file
|
|
187
|
-
outFiles = [
|
|
192
|
+
outFiles = [tmp_path / f"test{test}_{j}" for j in range(BUNDLE_COUNT)]
|
|
188
193
|
# We send 3 messages each in 2 sets, each of which needs a service and a client
|
|
189
194
|
messageBundles = [
|
|
190
195
|
[random.randint(1, sys.maxsize) for i in range(BUNDLE_SIZE)] for j in range(BUNDLE_COUNT)
|
|
191
196
|
]
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
197
|
+
# Wire up the services/jobs
|
|
198
|
+
t = Job.wrapJobFn(
|
|
199
|
+
serviceTestParallelRecursive,
|
|
200
|
+
outFiles,
|
|
201
|
+
messageBundles,
|
|
202
|
+
checkpoint=True,
|
|
203
|
+
)
|
|
200
204
|
|
|
201
|
-
|
|
202
|
-
|
|
205
|
+
# Run the workflow repeatedly until success
|
|
206
|
+
sub_tmp_path = tmp_path / str(test)
|
|
207
|
+
sub_tmp_path.mkdir()
|
|
208
|
+
self.runToil(
|
|
209
|
+
sub_tmp_path,
|
|
210
|
+
t,
|
|
211
|
+
retryCount=RETRY_COUNT,
|
|
212
|
+
badWorker=FAIL_FRACTION,
|
|
213
|
+
max_attempts=MAX_ATTEMPTS,
|
|
214
|
+
)
|
|
203
215
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
list(map(int, open(outFile).readlines())), messages
|
|
208
|
-
)
|
|
209
|
-
finally:
|
|
210
|
-
list(map(os.remove, outFiles))
|
|
216
|
+
# Check output
|
|
217
|
+
for messages, outFile in zip(messageBundles, outFiles):
|
|
218
|
+
assert list(map(int, outFile.open().readlines())) == messages
|
|
211
219
|
|
|
212
220
|
def runToil(
|
|
213
221
|
self,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
tmp_path: Path,
|
|
223
|
+
rootJob: Job,
|
|
224
|
+
retryCount: int = 1,
|
|
225
|
+
badWorker: float = 0.5,
|
|
226
|
+
badWorkedFailInterval: float = 0.1,
|
|
227
|
+
maxServiceJobs: int = sys.maxsize,
|
|
228
|
+
deadlockWait: int = 60,
|
|
229
|
+
max_attempts: int = 50,
|
|
230
|
+
) -> None:
|
|
222
231
|
# Create the runner for the workflow.
|
|
223
|
-
options = Job.Runner.getDefaultOptions(
|
|
232
|
+
options = Job.Runner.getDefaultOptions(tmp_path / "jobstore")
|
|
224
233
|
options.logLevel = "DEBUG"
|
|
225
234
|
|
|
226
235
|
options.retryCount = retryCount
|
|
@@ -240,18 +249,14 @@ class JobServiceTest(ToilTest):
|
|
|
240
249
|
i = e.numberOfFailedJobs
|
|
241
250
|
logger.info("Workflow attempt %s/%s failed with %s failed jobs", total_tries, max_attempts, i)
|
|
242
251
|
if total_tries == max_attempts:
|
|
243
|
-
|
|
252
|
+
pytest.fail(reason="Exceeded a reasonable number of restarts")
|
|
244
253
|
total_tries += 1
|
|
245
254
|
options.restart = True
|
|
246
255
|
logger.info("Succeeded after %s/%s attempts", total_tries, max_attempts)
|
|
247
256
|
|
|
248
257
|
|
|
249
|
-
class
|
|
250
|
-
def runToil(
|
|
251
|
-
self,
|
|
252
|
-
*args,
|
|
253
|
-
**kwargs
|
|
254
|
-
):
|
|
258
|
+
class TestPerfectServicet(TestJobService):
|
|
259
|
+
def runToil(self, *args: Any, **kwargs: Any) -> None:
|
|
255
260
|
"""
|
|
256
261
|
Let us run all the tests in the other service test class, but without worker failures.
|
|
257
262
|
"""
|
|
@@ -262,13 +267,13 @@ class PerfectServiceTest(JobServiceTest):
|
|
|
262
267
|
)
|
|
263
268
|
|
|
264
269
|
|
|
265
|
-
def serviceTest(job, outFile, messageInt):
|
|
270
|
+
def serviceTest(job: Job, outFile: Path, messageInt: int) -> None:
|
|
266
271
|
"""
|
|
267
272
|
Creates one service and one accessing job, which communicate with two files to establish
|
|
268
273
|
that both run concurrently.
|
|
269
274
|
"""
|
|
270
275
|
# Clean out out-file
|
|
271
|
-
open(
|
|
276
|
+
outFile.open("w").close()
|
|
272
277
|
# We create a random number that is added to messageInt and subtracted by
|
|
273
278
|
# the serviceAccessor, to prove that when service test is checkpointed and
|
|
274
279
|
# restarted there is never a connection made between an earlier service and
|
|
@@ -284,13 +289,13 @@ def serviceTest(job, outFile, messageInt):
|
|
|
284
289
|
)
|
|
285
290
|
|
|
286
291
|
|
|
287
|
-
def serviceTestRecursive(job, outFile, messages):
|
|
292
|
+
def serviceTestRecursive(job: Job, outFile: Path, messages: list[int]) -> None:
|
|
288
293
|
"""
|
|
289
294
|
Creates a chain of services and accessing jobs, each paired together.
|
|
290
295
|
"""
|
|
291
296
|
if len(messages) > 0:
|
|
292
297
|
# Clean out out-file
|
|
293
|
-
open(
|
|
298
|
+
outFile.open("w").close()
|
|
294
299
|
to_add = random.randint(1, sys.maxsize)
|
|
295
300
|
service = ToyService(messages[0] + to_add)
|
|
296
301
|
child = job.addChildJobFn(
|
|
@@ -310,13 +315,15 @@ def serviceTestRecursive(job, outFile, messages):
|
|
|
310
315
|
service = service2
|
|
311
316
|
|
|
312
317
|
|
|
313
|
-
def serviceTestParallelRecursive(
|
|
318
|
+
def serviceTestParallelRecursive(
|
|
319
|
+
job: Job, outFiles: list[Path], messageBundles: list[list[int]]
|
|
320
|
+
) -> None:
|
|
314
321
|
"""
|
|
315
322
|
Creates multiple chains of services and accessing jobs.
|
|
316
323
|
"""
|
|
317
324
|
for messages, outFile in zip(messageBundles, outFiles):
|
|
318
325
|
# Clean out out-file
|
|
319
|
-
open(
|
|
326
|
+
outFile.open("w").close()
|
|
320
327
|
if len(messages) > 0:
|
|
321
328
|
to_add = random.randint(1, sys.maxsize)
|
|
322
329
|
service = ToyService(messages[0] + to_add)
|
|
@@ -338,7 +345,7 @@ def serviceTestParallelRecursive(job, outFiles, messageBundles):
|
|
|
338
345
|
|
|
339
346
|
|
|
340
347
|
class ToyService(Job.Service):
|
|
341
|
-
def __init__(self, messageInt, *args, **kwargs):
|
|
348
|
+
def __init__(self, messageInt: int, *args: Any, **kwargs: Any) -> None:
|
|
342
349
|
"""
|
|
343
350
|
While established the service repeatedly:
|
|
344
351
|
- reads an integer i from the inJobStoreFileID file
|
|
@@ -347,7 +354,7 @@ class ToyService(Job.Service):
|
|
|
347
354
|
Job.Service.__init__(self, *args, **kwargs)
|
|
348
355
|
self.messageInt = messageInt
|
|
349
356
|
|
|
350
|
-
def start(self, job):
|
|
357
|
+
def start(self, job: ServiceHostJob) -> tuple[str, str]:
|
|
351
358
|
assert self.disk is not None
|
|
352
359
|
assert self.memory is not None
|
|
353
360
|
assert self.cores is not None
|
|
@@ -371,19 +378,24 @@ class ToyService(Job.Service):
|
|
|
371
378
|
self.serviceThread.start()
|
|
372
379
|
return (inJobStoreID, outJobStoreID)
|
|
373
380
|
|
|
374
|
-
def stop(self, job):
|
|
381
|
+
def stop(self, job: Job) -> None:
|
|
375
382
|
self.terminate.set()
|
|
376
383
|
self.serviceThread.join()
|
|
377
384
|
|
|
378
|
-
def check(self):
|
|
385
|
+
def check(self) -> Literal[True]:
|
|
379
386
|
if self.error.isSet():
|
|
380
387
|
raise RuntimeError("Service worker failed")
|
|
381
388
|
return True
|
|
382
389
|
|
|
383
390
|
@staticmethod
|
|
384
391
|
def serviceWorker(
|
|
385
|
-
jobStore
|
|
386
|
-
|
|
392
|
+
jobStore: AbstractJobStore,
|
|
393
|
+
terminate: Event,
|
|
394
|
+
error: Event,
|
|
395
|
+
inJobStoreID: str,
|
|
396
|
+
outJobStoreID: str,
|
|
397
|
+
messageInt: int,
|
|
398
|
+
) -> None:
|
|
387
399
|
try:
|
|
388
400
|
while True:
|
|
389
401
|
if terminate.isSet(): # Quit if we've got the terminate signal
|
|
@@ -395,8 +407,7 @@ class ToyService(Job.Service):
|
|
|
395
407
|
# Try reading a line from the input file
|
|
396
408
|
try:
|
|
397
409
|
with jobStore.read_file_stream(inJobStoreID) as f:
|
|
398
|
-
|
|
399
|
-
line = f.readline()
|
|
410
|
+
line = codecs.getreader("utf-8")(f).readline()
|
|
400
411
|
except:
|
|
401
412
|
logger.debug(
|
|
402
413
|
"Something went wrong reading a line: %s",
|
|
@@ -428,7 +439,12 @@ class ToyService(Job.Service):
|
|
|
428
439
|
raise
|
|
429
440
|
|
|
430
441
|
|
|
431
|
-
def serviceAccessor(
|
|
442
|
+
def serviceAccessor(
|
|
443
|
+
job: ServiceHostJob,
|
|
444
|
+
communicationFiles: tuple[str, str],
|
|
445
|
+
outFile: Path,
|
|
446
|
+
to_subtract: int,
|
|
447
|
+
) -> None:
|
|
432
448
|
"""
|
|
433
449
|
Writes a random integer iinto the inJobStoreFileID file, then tries 10 times reading
|
|
434
450
|
from outJobStoreFileID to get a pair of integers, the first equal to i the second written into the outputFile.
|
|
@@ -462,7 +478,7 @@ def serviceAccessor(job, communicationFiles, outFile, to_subtract):
|
|
|
462
478
|
logger.debug(
|
|
463
479
|
f"Matched key's: {key}, writing message: {int(message) - to_subtract} with to_subtract: {to_subtract}"
|
|
464
480
|
)
|
|
465
|
-
with open(
|
|
481
|
+
with outFile.open("a") as fH:
|
|
466
482
|
fH.write("%s\n" % (int(message) - to_subtract))
|
|
467
483
|
return
|
|
468
484
|
|
|
@@ -470,26 +486,26 @@ def serviceAccessor(job, communicationFiles, outFile, to_subtract):
|
|
|
470
486
|
|
|
471
487
|
|
|
472
488
|
class ToySerializableService(Job.Service):
|
|
473
|
-
def __init__(self, messageInt, *args, **kwargs):
|
|
489
|
+
def __init__(self, messageInt: str, *args: Any, **kwargs: Any) -> None:
|
|
474
490
|
"""
|
|
475
491
|
Trivial service for testing serialization.
|
|
476
492
|
"""
|
|
477
493
|
Job.Service.__init__(self, *args, **kwargs)
|
|
478
494
|
self.messageInt = messageInt
|
|
479
495
|
|
|
480
|
-
def start(self, job):
|
|
496
|
+
def start(self, job: Job) -> str:
|
|
481
497
|
return self.messageInt
|
|
482
498
|
|
|
483
|
-
def stop(self, job):
|
|
499
|
+
def stop(self, job: Job) -> None:
|
|
484
500
|
pass
|
|
485
501
|
|
|
486
|
-
def check(self):
|
|
502
|
+
def check(self) -> Literal[True]:
|
|
487
503
|
return True
|
|
488
504
|
|
|
489
505
|
|
|
490
|
-
def fnTest(strings, outputFile):
|
|
506
|
+
def fnTest(strings: list[str], outputFile: Path) -> None:
|
|
491
507
|
"""
|
|
492
508
|
Function concatenates the strings together and writes them to the output file
|
|
493
509
|
"""
|
|
494
|
-
with open(
|
|
510
|
+
with outputFile.open("w") as fH:
|
|
495
511
|
fH.write(" ".join(strings))
|