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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Copyright (C) 2015-2025 Regents of the University of California
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from collections.abc import Generator
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import logging
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from toil.lib.history import HistoryManager
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestHistory:
|
|
29
|
+
"""
|
|
30
|
+
Tests for Toil history tracking.
|
|
31
|
+
|
|
32
|
+
Each test gets its own history database.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@pytest.fixture(autouse=True, scope="function")
|
|
36
|
+
def private_history_manager(
|
|
37
|
+
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
38
|
+
) -> Generator[None]:
|
|
39
|
+
try:
|
|
40
|
+
with monkeypatch.context() as m:
|
|
41
|
+
m.setattr(
|
|
42
|
+
HistoryManager,
|
|
43
|
+
"database_path_override",
|
|
44
|
+
str(tmp_path / "test-db.sqlite"),
|
|
45
|
+
)
|
|
46
|
+
m.setattr(HistoryManager, "enabled", lambda: True)
|
|
47
|
+
m.setattr(HistoryManager, "enabled_job", lambda: True)
|
|
48
|
+
yield
|
|
49
|
+
finally:
|
|
50
|
+
pass # no cleanup needed
|
|
51
|
+
|
|
52
|
+
def make_fake_workflow(self, workflow_id: str) -> None:
|
|
53
|
+
# Make a fake workflow
|
|
54
|
+
workflow_jobstore_spec = "file:/tmp/tree"
|
|
55
|
+
HistoryManager.record_workflow_creation(workflow_id, workflow_jobstore_spec)
|
|
56
|
+
workflow_name = "SuperCoolWF"
|
|
57
|
+
workflow_trs_spec = "#wf:v1"
|
|
58
|
+
HistoryManager.record_workflow_metadata(
|
|
59
|
+
workflow_id, workflow_name, workflow_trs_spec
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Give it a job
|
|
63
|
+
workflow_attempt_number = 1
|
|
64
|
+
job_name = "DoThing"
|
|
65
|
+
succeeded = True
|
|
66
|
+
start_time = time.time()
|
|
67
|
+
runtime = 0.1
|
|
68
|
+
HistoryManager.record_job_attempt(
|
|
69
|
+
workflow_id,
|
|
70
|
+
workflow_attempt_number,
|
|
71
|
+
job_name,
|
|
72
|
+
succeeded,
|
|
73
|
+
start_time,
|
|
74
|
+
runtime,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Give it a workflow attempt with the same details.
|
|
78
|
+
HistoryManager.record_workflow_attempt(
|
|
79
|
+
workflow_id,
|
|
80
|
+
workflow_attempt_number,
|
|
81
|
+
succeeded,
|
|
82
|
+
start_time,
|
|
83
|
+
runtime,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def test_history_submittable_detection(self) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Make sure that a submittable workflow shows up as such before
|
|
89
|
+
submission and doesn't afterward.
|
|
90
|
+
"""
|
|
91
|
+
workflow_id = "123"
|
|
92
|
+
self.make_fake_workflow(workflow_id)
|
|
93
|
+
workflow_attempt_number = 1
|
|
94
|
+
|
|
95
|
+
# Make sure we have data
|
|
96
|
+
assert HistoryManager.count_workflows() == 1
|
|
97
|
+
assert HistoryManager.count_workflow_attempts() == 1
|
|
98
|
+
assert HistoryManager.count_job_attempts() == 1
|
|
99
|
+
|
|
100
|
+
# Make sure we see it as submittable
|
|
101
|
+
submittable_workflow_attempts = (
|
|
102
|
+
HistoryManager.get_submittable_workflow_attempts()
|
|
103
|
+
)
|
|
104
|
+
assert len(submittable_workflow_attempts) == 1
|
|
105
|
+
|
|
106
|
+
# Make sure we see its jobs as submittable
|
|
107
|
+
with_submittable_job_attempts = (
|
|
108
|
+
HistoryManager.get_workflow_attempts_with_submittable_job_attempts()
|
|
109
|
+
)
|
|
110
|
+
assert len(with_submittable_job_attempts) == 1
|
|
111
|
+
|
|
112
|
+
# Make sure we actually see the job
|
|
113
|
+
submittable_job_attempts = HistoryManager.get_unsubmitted_job_attempts(
|
|
114
|
+
workflow_id, workflow_attempt_number
|
|
115
|
+
)
|
|
116
|
+
assert len(submittable_job_attempts) == 1
|
|
117
|
+
|
|
118
|
+
# Pretend we submitted them.
|
|
119
|
+
HistoryManager.mark_job_attempts_submitted(
|
|
120
|
+
[j.id for j in submittable_job_attempts]
|
|
121
|
+
)
|
|
122
|
+
HistoryManager.mark_workflow_attempt_submitted(
|
|
123
|
+
workflow_id, workflow_attempt_number
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Make sure they are no longer matching
|
|
127
|
+
assert len(HistoryManager.get_submittable_workflow_attempts()) == 0
|
|
128
|
+
assert (
|
|
129
|
+
len(HistoryManager.get_workflow_attempts_with_submittable_job_attempts())
|
|
130
|
+
== 0
|
|
131
|
+
)
|
|
132
|
+
assert (
|
|
133
|
+
len(
|
|
134
|
+
HistoryManager.get_unsubmitted_job_attempts(
|
|
135
|
+
workflow_id, workflow_attempt_number
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
== 0
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Make sure we still have data
|
|
142
|
+
assert HistoryManager.count_workflows() == 1
|
|
143
|
+
assert HistoryManager.count_workflow_attempts() == 1
|
|
144
|
+
assert HistoryManager.count_job_attempts() == 1
|
|
145
|
+
|
|
146
|
+
def test_history_deletion(self) -> None:
|
|
147
|
+
workflow_id = "123"
|
|
148
|
+
self.make_fake_workflow(workflow_id)
|
|
149
|
+
workflow_attempt_number = 1
|
|
150
|
+
|
|
151
|
+
# Make sure we can see the workflow for deletion by age but not by done-ness
|
|
152
|
+
assert len(HistoryManager.get_oldest_workflow_ids()) == 1
|
|
153
|
+
assert len(HistoryManager.get_fully_submitted_workflow_ids()) == 0
|
|
154
|
+
|
|
155
|
+
# Pretend we submitted the workflow.
|
|
156
|
+
HistoryManager.mark_job_attempts_submitted(
|
|
157
|
+
[
|
|
158
|
+
j.id
|
|
159
|
+
for j in HistoryManager.get_unsubmitted_job_attempts(
|
|
160
|
+
workflow_id, workflow_attempt_number
|
|
161
|
+
)
|
|
162
|
+
]
|
|
163
|
+
)
|
|
164
|
+
HistoryManager.mark_workflow_attempt_submitted(
|
|
165
|
+
workflow_id, workflow_attempt_number
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Make sure we can see the workflow for deletion by done-ness
|
|
169
|
+
assert len(HistoryManager.get_fully_submitted_workflow_ids()) == 1
|
|
170
|
+
|
|
171
|
+
# Add a new workflow
|
|
172
|
+
other_workflow_id = "456"
|
|
173
|
+
self.make_fake_workflow(other_workflow_id)
|
|
174
|
+
|
|
175
|
+
# Make sure we can see the both for deletion by age but only one by done-ness
|
|
176
|
+
assert len(HistoryManager.get_oldest_workflow_ids()) == 2
|
|
177
|
+
assert len(HistoryManager.get_fully_submitted_workflow_ids()) == 1
|
|
178
|
+
|
|
179
|
+
# Make sure the older workflow is first.
|
|
180
|
+
assert HistoryManager.get_oldest_workflow_ids() == [
|
|
181
|
+
workflow_id,
|
|
182
|
+
other_workflow_id,
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
# Delete the new workflow
|
|
186
|
+
HistoryManager.delete_workflow(other_workflow_id)
|
|
187
|
+
|
|
188
|
+
# Make sure we can see the old one
|
|
189
|
+
assert HistoryManager.get_oldest_workflow_ids() == [workflow_id]
|
|
190
|
+
assert HistoryManager.get_fully_submitted_workflow_ids() == [workflow_id]
|
|
191
|
+
|
|
192
|
+
# Delete the old workflow
|
|
193
|
+
HistoryManager.delete_workflow(workflow_id)
|
|
194
|
+
|
|
195
|
+
# Make sure we have no data
|
|
196
|
+
assert HistoryManager.count_workflows() == 0
|
|
197
|
+
assert HistoryManager.count_workflow_attempts() == 0
|
|
198
|
+
assert HistoryManager.count_job_attempts() == 0
|
|
199
|
+
|
|
200
|
+
def test_history_size_limit(self) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Make sure the database size can be controlled.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
for workflow_id in (
|
|
206
|
+
"WorkflowThatTakesUpSomeSpace,ActuallyMoreThanTheLaterOnesTake" + str(i)
|
|
207
|
+
for i in range(10)
|
|
208
|
+
):
|
|
209
|
+
self.make_fake_workflow(workflow_id)
|
|
210
|
+
|
|
211
|
+
# We should see the workflows.
|
|
212
|
+
assert HistoryManager.count_workflows() == 10
|
|
213
|
+
# And they take up space.
|
|
214
|
+
small_size = HistoryManager.get_database_byte_size()
|
|
215
|
+
assert small_size > 0
|
|
216
|
+
|
|
217
|
+
# Add a bunch more
|
|
218
|
+
for workflow_id in ("WorkflowThatTakesUpSpace" + str(i) for i in range(50)):
|
|
219
|
+
self.make_fake_workflow(workflow_id)
|
|
220
|
+
|
|
221
|
+
# We should see that this is now a much larger database
|
|
222
|
+
large_size = HistoryManager.get_database_byte_size()
|
|
223
|
+
logger.info("Increased database size from %s to %s", small_size, large_size)
|
|
224
|
+
large_size > small_size
|
|
225
|
+
|
|
226
|
+
# We should be able to shrink it back down
|
|
227
|
+
HistoryManager.enforce_byte_size_limit(small_size)
|
|
228
|
+
|
|
229
|
+
reduced_size = HistoryManager.get_database_byte_size()
|
|
230
|
+
logger.info("Decreased database size from %s to %s", large_size, reduced_size)
|
|
231
|
+
# The database should be small enough
|
|
232
|
+
reduced_size <= small_size
|
|
233
|
+
# There should still be some workflow attempts left in the smaller database (though probably not the first ones)
|
|
234
|
+
remaining_workflows = HistoryManager.count_workflows()
|
|
235
|
+
logger.info("Still have %s workflows", remaining_workflows)
|
|
236
|
+
assert remaining_workflows > 0
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Copyright (C) 2015-2024 Regents of the University of California
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import io
|
|
16
|
+
import logging
|
|
17
|
+
import pytest
|
|
18
|
+
from typing import IO
|
|
19
|
+
import urllib.request
|
|
20
|
+
from urllib.error import URLError
|
|
21
|
+
|
|
22
|
+
from toil.lib.retry import retry
|
|
23
|
+
from toil.lib.trs import find_workflow, fetch_workflow
|
|
24
|
+
from toil.test import ToilTest, needs_online
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
28
|
+
|
|
29
|
+
@pytest.mark.integrative
|
|
30
|
+
@needs_online
|
|
31
|
+
class DockstoreLookupTest(ToilTest):
|
|
32
|
+
"""
|
|
33
|
+
Make sure we can look up workflows on Dockstore.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@retry(errors=[URLError, RuntimeError])
|
|
37
|
+
def read_result(self, url_or_path: str) -> IO[bytes]:
|
|
38
|
+
"""
|
|
39
|
+
Read a file or URL.
|
|
40
|
+
|
|
41
|
+
Binary mode to allow testing for binary file support.
|
|
42
|
+
|
|
43
|
+
This lets us test that we have the right workflow contents and not care
|
|
44
|
+
how we are being shown them.
|
|
45
|
+
"""
|
|
46
|
+
if url_or_path.startswith("http://") or url_or_path.startswith("https://"):
|
|
47
|
+
response = urllib.request.urlopen(url_or_path)
|
|
48
|
+
if response.status != 200:
|
|
49
|
+
raise RuntimeError(f"HTTP error response: {response}")
|
|
50
|
+
return response
|
|
51
|
+
else:
|
|
52
|
+
return open(url_or_path, "rb")
|
|
53
|
+
|
|
54
|
+
# TODO: Tests that definitely test a clear cache
|
|
55
|
+
|
|
56
|
+
def test_lookup_from_page_url(self) -> None:
|
|
57
|
+
PAGE_URL = "https://dockstore.org/workflows/github.com/dockstore/bcc2020-training/HelloWorld:master?tab=info"
|
|
58
|
+
trs_id, trs_version, language = find_workflow(PAGE_URL)
|
|
59
|
+
|
|
60
|
+
self.assertEqual(trs_id, "#workflow/github.com/dockstore/bcc2020-training/HelloWorld")
|
|
61
|
+
self.assertEqual(trs_version, "master")
|
|
62
|
+
self.assertEqual(language, "WDL")
|
|
63
|
+
|
|
64
|
+
def test_lookup_from_trs_with_version(self) -> None:
|
|
65
|
+
TRS_ID = "#workflow/github.com/dockstore-testing/md5sum-checker"
|
|
66
|
+
TRS_VERSION = "master"
|
|
67
|
+
trs_id, trs_version, language = find_workflow(f"{TRS_ID}:{TRS_VERSION}")
|
|
68
|
+
|
|
69
|
+
self.assertEqual(trs_id, TRS_ID)
|
|
70
|
+
self.assertEqual(trs_version, TRS_VERSION)
|
|
71
|
+
self.assertEqual(language, "CWL")
|
|
72
|
+
|
|
73
|
+
def test_lookup_from_trs_no_version(self) -> None:
|
|
74
|
+
TRS_ID = "#workflow/github.com/dockstore-testing/md5sum-checker"
|
|
75
|
+
with pytest.raises(ValueError):
|
|
76
|
+
# We don't yet have a way to read Dockstore's default version info,
|
|
77
|
+
# so it's not safe to apply any default version when multiple
|
|
78
|
+
# versions exist.
|
|
79
|
+
trs_id, trs_version, language = find_workflow(TRS_ID)
|
|
80
|
+
|
|
81
|
+
# TODO: Add a test with a workflow that we know has and will only ever
|
|
82
|
+
# have one version, to test version auto-detection in that case.
|
|
83
|
+
|
|
84
|
+
def test_get(self) -> None:
|
|
85
|
+
TRS_ID = "#workflow/github.com/dockstore-testing/md5sum-checker"
|
|
86
|
+
TRS_VERSION = "master"
|
|
87
|
+
LANGUAGE = "CWL"
|
|
88
|
+
# Despite "-checker" in the ID, this actually refers to the base md5sum
|
|
89
|
+
# workflow that just happens to have a checker *available*, not to the
|
|
90
|
+
# checker workflow itself.
|
|
91
|
+
WORKFLOW_URL = "https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/master/md5sum/md5sum-workflow.cwl"
|
|
92
|
+
looked_up = fetch_workflow(TRS_ID, TRS_VERSION, LANGUAGE)
|
|
93
|
+
|
|
94
|
+
data_from_lookup = self.read_result(looked_up).read()
|
|
95
|
+
data_from_source = self.read_result(WORKFLOW_URL).read()
|
|
96
|
+
self.assertEqual(data_from_lookup, data_from_source)
|
|
97
|
+
|
|
98
|
+
def test_get_from_trs_cached(self) -> None:
|
|
99
|
+
TRS_ID = "#workflow/github.com/dockstore-testing/md5sum-checker"
|
|
100
|
+
TRS_VERSION = "master"
|
|
101
|
+
LANGUAGE = "CWL"
|
|
102
|
+
WORKFLOW_URL = "https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/master/md5sum/md5sum-workflow.cwl"
|
|
103
|
+
# This lookup may or may not be cached
|
|
104
|
+
fetch_workflow(TRS_ID, TRS_VERSION, LANGUAGE)
|
|
105
|
+
# This lookup is definitely cached
|
|
106
|
+
looked_up = fetch_workflow(TRS_ID, TRS_VERSION, LANGUAGE)
|
|
107
|
+
|
|
108
|
+
data_from_lookup = self.read_result(looked_up).read()
|
|
109
|
+
data_from_source = self.read_result(WORKFLOW_URL).read()
|
|
110
|
+
self.assertEqual(data_from_lookup, data_from_source)
|
|
111
|
+
|
|
112
|
+
def test_lookup_from_trs_with_version(self) -> None:
|
|
113
|
+
TRS_VERSIONED_ID = "#workflow/github.com/dockstore-testing/md5sum-checker:workflowWithHTTPImport"
|
|
114
|
+
trs_id, trs_version, language = find_workflow(TRS_VERSIONED_ID)
|
|
115
|
+
|
|
116
|
+
parts = TRS_VERSIONED_ID.split(":")
|
|
117
|
+
|
|
118
|
+
self.assertEqual(trs_id, parts[0])
|
|
119
|
+
self.assertEqual(trs_version, parts[1])
|
|
120
|
+
self.assertEqual(language, "CWL")
|
|
121
|
+
|
|
122
|
+
def test_lookup_from_trs_nonexistent_workflow(self) -> None:
|
|
123
|
+
TRS_VERSIONED_ID = "#workflow/github.com/adamnovak/veryfakerepo:notARealVersion"
|
|
124
|
+
with self.assertRaises(FileNotFoundError):
|
|
125
|
+
looked_up = find_workflow(TRS_VERSIONED_ID)
|
|
126
|
+
|
|
127
|
+
def test_lookup_from_trs_nonexistent_workflow_bad_format(self) -> None:
|
|
128
|
+
TRS_VERSIONED_ID = "#workflow/AbsoluteGarbage:notARealVersion"
|
|
129
|
+
with self.assertRaises(FileNotFoundError):
|
|
130
|
+
looked_up = find_workflow(TRS_VERSIONED_ID)
|
|
131
|
+
|
|
132
|
+
def test_lookup_from_trs_nonexistent_version(self) -> None:
|
|
133
|
+
TRS_VERSIONED_ID = "#workflow/github.com/dockstore-testing/md5sum-checker:notARealVersion"
|
|
134
|
+
with self.assertRaises(FileNotFoundError):
|
|
135
|
+
looked_up = find_workflow(TRS_VERSIONED_ID)
|
|
136
|
+
|
|
137
|
+
def test_get_nonexistent_workflow(self) -> None:
|
|
138
|
+
TRS_ID = "#workflow/github.com/adamnovak/veryfakerepo"
|
|
139
|
+
TRS_VERSION = "notARealVersion"
|
|
140
|
+
LANGUAGE = "CWL"
|
|
141
|
+
with self.assertRaises(FileNotFoundError):
|
|
142
|
+
looked_up = fetch_workflow(TRS_ID, TRS_VERSION, LANGUAGE)
|
|
143
|
+
|
|
144
|
+
def test_get_nonexistent_version(self) -> None:
|
|
145
|
+
TRS_ID = "#workflow/github.com/dockstore-testing/md5sum-checker"
|
|
146
|
+
TRS_VERSION = "notARealVersion"
|
|
147
|
+
LANGUAGE = "CWL"
|
|
148
|
+
with self.assertRaises(FileNotFoundError):
|
|
149
|
+
looked_up = fetch_workflow(TRS_ID, TRS_VERSION, LANGUAGE)
|
|
150
|
+
|
|
151
|
+
def test_get_nonexistent_workflow_bad_format(self) -> None:
|
|
152
|
+
# Dockstore enforces an ID pattern and blames your request if you ask
|
|
153
|
+
# about something that doesn't follow it. So don't follow it.
|
|
154
|
+
TRS_ID = "#workflow/AbsoluteGarbage"
|
|
155
|
+
TRS_VERSION = "notARealVersion"
|
|
156
|
+
LANGUAGE = "CWL"
|
|
157
|
+
with self.assertRaises(FileNotFoundError):
|
|
158
|
+
looked_up = fetch_workflow(TRS_ID, TRS_VERSION, LANGUAGE)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
@@ -28,6 +28,7 @@ from toil.provisioners import cluster_factory
|
|
|
28
28
|
from toil.provisioners.aws.awsProvisioner import AWSProvisioner
|
|
29
29
|
from toil.test import (
|
|
30
30
|
ToilTest,
|
|
31
|
+
get_data,
|
|
31
32
|
integrative,
|
|
32
33
|
needs_aws_ec2,
|
|
33
34
|
needs_fetchable_appliance,
|
|
@@ -135,7 +136,7 @@ class AbstractAWSAutoscaleTest(AbstractClusterTest):
|
|
|
135
136
|
"""
|
|
136
137
|
return os.path.join(self.dataDir, filename)
|
|
137
138
|
|
|
138
|
-
def rsyncUtil(self, src, dest):
|
|
139
|
+
def rsyncUtil(self, src: str, dest: str) -> None:
|
|
139
140
|
subprocess.check_call(
|
|
140
141
|
[
|
|
141
142
|
"toil",
|
|
@@ -285,10 +286,11 @@ class AWSAutoscaleTest(AbstractAWSAutoscaleTest):
|
|
|
285
286
|
with open(fileToSort, "w") as f:
|
|
286
287
|
# Fixme: making this file larger causes the test to hang
|
|
287
288
|
f.write("01234567890123456789012345678901")
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
with get_data("test/sort/sort.py") as sort_py:
|
|
290
|
+
self.rsyncUtil(
|
|
291
|
+
sort_py,
|
|
292
|
+
":" + self.script(),
|
|
293
|
+
)
|
|
292
294
|
self.rsyncUtil(fileToSort, ":" + self.data("sortFile"))
|
|
293
295
|
os.unlink(fileToSort)
|
|
294
296
|
|
|
@@ -501,10 +503,11 @@ class AWSAutoscaleTestMultipleNodeTypes(AbstractAWSAutoscaleTest):
|
|
|
501
503
|
sseKeyFile = os.path.join(os.getcwd(), "keyFile")
|
|
502
504
|
with open(sseKeyFile, "w") as f:
|
|
503
505
|
f.write("01234567890123456789012345678901")
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
with get_data("test/sort/sort.py") as sort_py:
|
|
507
|
+
self.rsyncUtil(
|
|
508
|
+
sort_py,
|
|
509
|
+
":" + self.script(),
|
|
510
|
+
)
|
|
508
511
|
self.rsyncUtil(sseKeyFile, ":" + self.data("keyFile"))
|
|
509
512
|
os.unlink(sseKeyFile)
|
|
510
513
|
|
|
@@ -39,7 +39,7 @@ log = logging.getLogger(__name__)
|
|
|
39
39
|
class AbstractClusterTest(ToilTest):
|
|
40
40
|
def __init__(self, methodName: str) -> None:
|
|
41
41
|
super().__init__(methodName=methodName)
|
|
42
|
-
self.keyName = os.getenv("TOIL_AWS_KEYNAME").strip()
|
|
42
|
+
self.keyName = os.getenv("TOIL_AWS_KEYNAME", "id_rsa").strip()
|
|
43
43
|
self.clusterName = f"aws-provisioner-test-{uuid4()}"
|
|
44
44
|
self.leaderNodeType = "t2.medium"
|
|
45
45
|
self.clusterType = "mesos"
|
|
@@ -276,12 +276,12 @@ class CWLOnARMTest(AbstractClusterTest):
|
|
|
276
276
|
]
|
|
277
277
|
)
|
|
278
278
|
|
|
279
|
-
# Runs the
|
|
279
|
+
# Runs the TestCWLv12 on an ARM instance
|
|
280
280
|
self.sshUtil(
|
|
281
281
|
[
|
|
282
282
|
"bash",
|
|
283
283
|
"-c",
|
|
284
|
-
f". .{self.venvDir}/bin/activate && cd {self.cwl_test_dir}/toil && pytest --log-cli-level DEBUG -r s src/toil/test/cwl/cwlTest.py::
|
|
284
|
+
f". .{self.venvDir}/bin/activate && cd {self.cwl_test_dir}/toil && pytest --log-cli-level DEBUG -r s src/toil/test/cwl/cwlTest.py::TestCWLv12::test_run_conformance",
|
|
285
285
|
]
|
|
286
286
|
)
|
|
287
287
|
|
|
@@ -289,5 +289,5 @@ class CWLOnARMTest(AbstractClusterTest):
|
|
|
289
289
|
# Bring it back to be an artifact.
|
|
290
290
|
self.rsync_util(
|
|
291
291
|
f":{self.cwl_test_dir}/toil/conformance-1.2.junit.xml",
|
|
292
|
-
|
|
292
|
+
str(self._rootpath / "arm-conformance-1.2.junit.xml"),
|
|
293
293
|
)
|
|
@@ -21,6 +21,7 @@ import pytest
|
|
|
21
21
|
|
|
22
22
|
from toil.test import (
|
|
23
23
|
ToilTest,
|
|
24
|
+
get_data,
|
|
24
25
|
integrative,
|
|
25
26
|
needs_fetchable_appliance,
|
|
26
27
|
needs_google_project,
|
|
@@ -214,10 +215,11 @@ class GCEAutoscaleTest(AbstractGCEAutoscaleTest):
|
|
|
214
215
|
with open(fileToSort, "w") as f:
|
|
215
216
|
# Fixme: making this file larger causes the test to hang
|
|
216
217
|
f.write("01234567890123456789012345678901")
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
with get_data("test/sort/sort.py") as sort_py:
|
|
219
|
+
self.rsyncUtil(
|
|
220
|
+
sort_py,
|
|
221
|
+
":/home/sort.py",
|
|
222
|
+
)
|
|
221
223
|
self.rsyncUtil(fileToSort, ":/home/sortFile")
|
|
222
224
|
os.unlink(fileToSort)
|
|
223
225
|
|
|
@@ -324,10 +326,11 @@ class GCEAutoscaleTestMultipleNodeTypes(AbstractGCEAutoscaleTest):
|
|
|
324
326
|
sseKeyFile = os.path.join(os.getcwd(), "keyFile")
|
|
325
327
|
with open(sseKeyFile, "w") as f:
|
|
326
328
|
f.write("01234567890123456789012345678901")
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
with get_data("test/sort/sort.py") as sort_py:
|
|
330
|
+
self.rsyncUtil(
|
|
331
|
+
sort_py,
|
|
332
|
+
":/home/sort.py",
|
|
333
|
+
)
|
|
331
334
|
self.rsyncUtil(sseKeyFile, ":/home/keyFile")
|
|
332
335
|
os.unlink(sseKeyFile)
|
|
333
336
|
|
|
@@ -376,12 +379,11 @@ class GCERestartTest(AbstractGCEAutoscaleTest):
|
|
|
376
379
|
self.jobStore = f"google:{self.projectID}:restart-{uuid4()}"
|
|
377
380
|
|
|
378
381
|
def _getScript(self):
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
)
|
|
382
|
+
with get_data("test/provisioners/restartScript.py") as restartScript:
|
|
383
|
+
self.rsyncUtil(
|
|
384
|
+
restartScript,
|
|
385
|
+
":" + self.scriptName,
|
|
386
|
+
)
|
|
385
387
|
|
|
386
388
|
def _runScript(self, toilOptions):
|
|
387
389
|
# clean = onSuccess
|
toil/test/sort/sort.py
CHANGED
|
@@ -23,6 +23,7 @@ from configargparse import ArgumentParser
|
|
|
23
23
|
|
|
24
24
|
from toil.common import Toil
|
|
25
25
|
from toil.job import Job
|
|
26
|
+
from toil.lib.misc import StrPath
|
|
26
27
|
from toil.realtimeLogger import RealtimeLogger
|
|
27
28
|
|
|
28
29
|
defaultLines = 1000
|
|
@@ -207,7 +208,9 @@ def getMidPoint(file, fileStart, fileEnd):
|
|
|
207
208
|
return len(line) + fileStart - 1
|
|
208
209
|
|
|
209
210
|
|
|
210
|
-
def makeFileToSort(
|
|
211
|
+
def makeFileToSort(
|
|
212
|
+
fileName: StrPath, lines: int = defaultLines, lineLen: int = defaultLineLen
|
|
213
|
+
) -> None:
|
|
211
214
|
with open(fileName, "w") as f:
|
|
212
215
|
for _ in range(lines):
|
|
213
216
|
line = (
|
toil/test/src/busTest.py
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
|
+
from pathlib import Path
|
|
17
18
|
from threading import Thread, current_thread
|
|
19
|
+
from typing import NoReturn
|
|
18
20
|
|
|
19
21
|
from toil.batchSystems.abstractBatchSystem import BatchJobExitReason
|
|
20
22
|
from toil.bus import (
|
|
@@ -26,18 +28,17 @@ from toil.bus import (
|
|
|
26
28
|
from toil.common import Toil
|
|
27
29
|
from toil.exceptions import FailedJobsException
|
|
28
30
|
from toil.job import Job
|
|
29
|
-
from toil.test import ToilTest, get_temp_file
|
|
30
31
|
|
|
31
32
|
logger = logging.getLogger(__name__)
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
class
|
|
35
|
+
class TestMessageBus:
|
|
35
36
|
|
|
36
|
-
def test_enum_ints_in_file(self) -> None:
|
|
37
|
+
def test_enum_ints_in_file(self, tmp_path: Path) -> None:
|
|
37
38
|
"""
|
|
38
39
|
Make sure writing bus messages to files works with enums.
|
|
39
40
|
"""
|
|
40
|
-
bus_file =
|
|
41
|
+
bus_file = tmp_path / "bus"
|
|
41
42
|
|
|
42
43
|
bus = MessageBus()
|
|
43
44
|
# Connect the handler and hold the result to protect it from GC
|
|
@@ -73,7 +74,7 @@ class MessageBusTest(ToilTest):
|
|
|
73
74
|
# Message should always arrive in the main thread.
|
|
74
75
|
nonlocal message_count
|
|
75
76
|
logger.debug("Got message: %s", received)
|
|
76
|
-
|
|
77
|
+
assert current_thread() == main_thread
|
|
77
78
|
message_count += 1
|
|
78
79
|
|
|
79
80
|
bus.subscribe(JobIssuedMessage, handler)
|
|
@@ -101,28 +102,27 @@ class MessageBusTest(ToilTest):
|
|
|
101
102
|
t.join()
|
|
102
103
|
|
|
103
104
|
# We should ge tone message per thread, plus our own
|
|
104
|
-
|
|
105
|
+
assert box.count(JobIssuedMessage) == 11
|
|
105
106
|
# And having polled for those, our handler should have run
|
|
106
|
-
|
|
107
|
+
assert message_count == 11
|
|
107
108
|
|
|
108
|
-
def test_restart_without_bus_path(self) -> None:
|
|
109
|
+
def test_restart_without_bus_path(self, tmp_path: Path) -> None:
|
|
109
110
|
"""
|
|
110
111
|
Test the ability to restart a workflow when the message bus path used
|
|
111
112
|
by the previous attempt is gone.
|
|
112
113
|
"""
|
|
113
|
-
temp_dir =
|
|
114
|
-
|
|
114
|
+
temp_dir = tmp_path / "tempDir"
|
|
115
|
+
temp_dir.mkdir()
|
|
116
|
+
job_store = tmp_path / "jobstore"
|
|
115
117
|
|
|
116
|
-
bus_holder_dir =
|
|
117
|
-
|
|
118
|
+
bus_holder_dir = temp_dir / "bus_holder"
|
|
119
|
+
bus_holder_dir.mkdir()
|
|
118
120
|
|
|
119
121
|
start_options = Job.Runner.getDefaultOptions(job_store)
|
|
120
122
|
start_options.logLevel = "DEBUG"
|
|
121
123
|
start_options.retryCount = 0
|
|
122
124
|
start_options.clean = "never"
|
|
123
|
-
start_options.write_messages =
|
|
124
|
-
os.path.join(bus_holder_dir, "messagebus.txt")
|
|
125
|
-
)
|
|
125
|
+
start_options.write_messages = str(bus_holder_dir / "messagebus.txt")
|
|
126
126
|
|
|
127
127
|
root = Job.wrapJobFn(failing_job_fn)
|
|
128
128
|
|
|
@@ -137,7 +137,7 @@ class MessageBusTest(ToilTest):
|
|
|
137
137
|
|
|
138
138
|
# Get rid of the bus
|
|
139
139
|
os.unlink(start_options.write_messages)
|
|
140
|
-
|
|
140
|
+
bus_holder_dir.rmdir()
|
|
141
141
|
|
|
142
142
|
logger.info("Making second attempt")
|
|
143
143
|
|
|
@@ -158,7 +158,7 @@ class MessageBusTest(ToilTest):
|
|
|
158
158
|
logger.info("Second attempt successfully failed")
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
def failing_job_fn(job: Job) ->
|
|
161
|
+
def failing_job_fn(job: Job) -> NoReturn:
|
|
162
162
|
"""
|
|
163
163
|
This function is guaranteed to fail.
|
|
164
164
|
"""
|