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.
Files changed (254) hide show
  1. toil/__init__.py +0 -35
  2. toil/batchSystems/abstractBatchSystem.py +1 -1
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
  4. toil/batchSystems/awsBatch.py +1 -1
  5. toil/batchSystems/cleanup_support.py +1 -1
  6. toil/batchSystems/kubernetes.py +53 -7
  7. toil/batchSystems/local_support.py +1 -1
  8. toil/batchSystems/mesos/batchSystem.py +13 -8
  9. toil/batchSystems/mesos/test/__init__.py +3 -2
  10. toil/batchSystems/singleMachine.py +1 -1
  11. toil/batchSystems/slurm.py +27 -26
  12. toil/bus.py +5 -3
  13. toil/common.py +39 -11
  14. toil/cwl/cwltoil.py +1 -1
  15. toil/job.py +64 -49
  16. toil/jobStores/abstractJobStore.py +24 -3
  17. toil/jobStores/fileJobStore.py +25 -1
  18. toil/jobStores/googleJobStore.py +104 -30
  19. toil/leader.py +9 -0
  20. toil/lib/accelerators.py +3 -1
  21. toil/lib/aws/utils.py.orig +504 -0
  22. toil/lib/bioio.py +1 -1
  23. toil/lib/docker.py +252 -91
  24. toil/lib/dockstore.py +11 -3
  25. toil/lib/exceptions.py +5 -3
  26. toil/lib/history.py +87 -13
  27. toil/lib/history_submission.py +23 -9
  28. toil/lib/io.py +34 -22
  29. toil/lib/misc.py +7 -1
  30. toil/lib/resources.py +2 -1
  31. toil/lib/threading.py +11 -10
  32. toil/options/common.py +8 -0
  33. toil/options/wdl.py +11 -0
  34. toil/server/api_spec/LICENSE +201 -0
  35. toil/server/api_spec/README.rst +5 -0
  36. toil/server/cli/wes_cwl_runner.py +2 -1
  37. toil/test/__init__.py +275 -115
  38. toil/test/batchSystems/batchSystemTest.py +227 -205
  39. toil/test/batchSystems/test_slurm.py +27 -0
  40. toil/test/cactus/pestis.tar.gz +0 -0
  41. toil/test/conftest.py +7 -0
  42. toil/test/cwl/2.fasta +11 -0
  43. toil/test/cwl/2.fastq +12 -0
  44. toil/test/cwl/conftest.py +1 -1
  45. toil/test/cwl/cwlTest.py +999 -867
  46. toil/test/cwl/directory/directory/file.txt +15 -0
  47. toil/test/cwl/download_directory_file.json +4 -0
  48. toil/test/cwl/download_directory_s3.json +4 -0
  49. toil/test/cwl/download_file.json +6 -0
  50. toil/test/cwl/download_http.json +6 -0
  51. toil/test/cwl/download_https.json +6 -0
  52. toil/test/cwl/download_s3.json +6 -0
  53. toil/test/cwl/download_subdirectory_file.json +5 -0
  54. toil/test/cwl/download_subdirectory_s3.json +5 -0
  55. toil/test/cwl/empty.json +1 -0
  56. toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
  57. toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
  58. toil/test/cwl/optional-file-exists.json +6 -0
  59. toil/test/cwl/optional-file-missing.json +6 -0
  60. toil/test/cwl/preemptible_expression.json +1 -0
  61. toil/test/cwl/revsort-job-missing.json +6 -0
  62. toil/test/cwl/revsort-job.json +6 -0
  63. toil/test/cwl/s3_secondary_file.json +16 -0
  64. toil/test/cwl/seqtk_seq_job.json +6 -0
  65. toil/test/cwl/stream.json +6 -0
  66. toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
  67. toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
  68. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
  69. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
  70. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
  71. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
  72. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
  73. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
  74. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
  75. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
  76. toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
  77. toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
  78. toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
  79. toil/test/cwl/whale.txt +16 -0
  80. toil/test/docs/scripts/example_alwaysfail.py +38 -0
  81. toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
  82. toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
  83. toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
  84. toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
  85. toil/test/docs/scripts/tutorial_arguments.py +23 -0
  86. toil/test/docs/scripts/tutorial_debugging.patch +12 -0
  87. toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
  88. toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
  89. toil/test/docs/scripts/tutorial_docker.py +20 -0
  90. toil/test/docs/scripts/tutorial_dynamic.py +24 -0
  91. toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
  92. toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
  93. toil/test/docs/scripts/tutorial_helloworld.py +15 -0
  94. toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
  95. toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
  96. toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
  97. toil/test/docs/scripts/tutorial_managing.py +29 -0
  98. toil/test/docs/scripts/tutorial_managing2.py +56 -0
  99. toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
  100. toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
  101. toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
  102. toil/test/docs/scripts/tutorial_promises.py +25 -0
  103. toil/test/docs/scripts/tutorial_promises2.py +30 -0
  104. toil/test/docs/scripts/tutorial_quickstart.py +22 -0
  105. toil/test/docs/scripts/tutorial_requirements.py +44 -0
  106. toil/test/docs/scripts/tutorial_services.py +45 -0
  107. toil/test/docs/scripts/tutorial_staging.py +45 -0
  108. toil/test/docs/scripts/tutorial_stats.py +64 -0
  109. toil/test/lib/aws/test_iam.py +3 -1
  110. toil/test/lib/dockerTest.py +205 -122
  111. toil/test/lib/test_history.py +101 -77
  112. toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
  113. toil/test/provisioners/clusterTest.py +4 -4
  114. toil/test/provisioners/gceProvisionerTest.py +16 -14
  115. toil/test/sort/sort.py +4 -1
  116. toil/test/src/busTest.py +17 -17
  117. toil/test/src/deferredFunctionTest.py +145 -132
  118. toil/test/src/importExportFileTest.py +71 -63
  119. toil/test/src/jobEncapsulationTest.py +27 -28
  120. toil/test/src/jobServiceTest.py +149 -133
  121. toil/test/src/jobTest.py +219 -211
  122. toil/test/src/miscTests.py +66 -60
  123. toil/test/src/promisedRequirementTest.py +163 -169
  124. toil/test/src/regularLogTest.py +24 -24
  125. toil/test/src/resourceTest.py +82 -76
  126. toil/test/src/restartDAGTest.py +51 -47
  127. toil/test/src/resumabilityTest.py +24 -19
  128. toil/test/src/retainTempDirTest.py +60 -57
  129. toil/test/src/systemTest.py +17 -13
  130. toil/test/src/threadingTest.py +29 -32
  131. toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
  132. toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
  133. toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
  134. toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
  135. toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
  136. toil/test/utils/toilDebugTest.py +117 -102
  137. toil/test/utils/toilKillTest.py +54 -53
  138. toil/test/utils/utilsTest.py +303 -229
  139. toil/test/wdl/lint_error.wdl +9 -0
  140. toil/test/wdl/md5sum/empty_file.json +1 -0
  141. toil/test/wdl/md5sum/md5sum-gs.json +1 -0
  142. toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
  143. toil/test/wdl/md5sum/md5sum.input +1 -0
  144. toil/test/wdl/md5sum/md5sum.json +1 -0
  145. toil/test/wdl/md5sum/md5sum.wdl +25 -0
  146. toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
  147. toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
  148. toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
  149. toil/test/wdl/standard_library/as_map.json +16 -0
  150. toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
  151. toil/test/wdl/standard_library/as_pairs.json +7 -0
  152. toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
  153. toil/test/wdl/standard_library/ceil.json +3 -0
  154. toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
  155. toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
  156. toil/test/wdl/standard_library/collect_by_key.json +1 -0
  157. toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
  158. toil/test/wdl/standard_library/cross.json +11 -0
  159. toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
  160. toil/test/wdl/standard_library/flatten.json +7 -0
  161. toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
  162. toil/test/wdl/standard_library/floor.json +3 -0
  163. toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
  164. toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
  165. toil/test/wdl/standard_library/keys.json +8 -0
  166. toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
  167. toil/test/wdl/standard_library/length.json +7 -0
  168. toil/test/wdl/standard_library/length_as_input.wdl +16 -0
  169. toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
  170. toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
  171. toil/test/wdl/standard_library/length_invalid.json +3 -0
  172. toil/test/wdl/standard_library/range.json +3 -0
  173. toil/test/wdl/standard_library/range_0.json +3 -0
  174. toil/test/wdl/standard_library/range_as_input.wdl +17 -0
  175. toil/test/wdl/standard_library/range_invalid.json +3 -0
  176. toil/test/wdl/standard_library/read_boolean.json +3 -0
  177. toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
  178. toil/test/wdl/standard_library/read_float.json +3 -0
  179. toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
  180. toil/test/wdl/standard_library/read_int.json +3 -0
  181. toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
  182. toil/test/wdl/standard_library/read_json.json +3 -0
  183. toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
  184. toil/test/wdl/standard_library/read_lines.json +3 -0
  185. toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
  186. toil/test/wdl/standard_library/read_map.json +3 -0
  187. toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
  188. toil/test/wdl/standard_library/read_string.json +3 -0
  189. toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
  190. toil/test/wdl/standard_library/read_tsv.json +3 -0
  191. toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
  192. toil/test/wdl/standard_library/round.json +3 -0
  193. toil/test/wdl/standard_library/round_as_command.wdl +16 -0
  194. toil/test/wdl/standard_library/round_as_input.wdl +16 -0
  195. toil/test/wdl/standard_library/size.json +3 -0
  196. toil/test/wdl/standard_library/size_as_command.wdl +17 -0
  197. toil/test/wdl/standard_library/size_as_output.wdl +36 -0
  198. toil/test/wdl/standard_library/stderr.json +3 -0
  199. toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
  200. toil/test/wdl/standard_library/stdout.json +3 -0
  201. toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
  202. toil/test/wdl/standard_library/sub.json +3 -0
  203. toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
  204. toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
  205. toil/test/wdl/standard_library/transpose.json +6 -0
  206. toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
  207. toil/test/wdl/standard_library/write_json.json +6 -0
  208. toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
  209. toil/test/wdl/standard_library/write_lines.json +7 -0
  210. toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
  211. toil/test/wdl/standard_library/write_map.json +6 -0
  212. toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
  213. toil/test/wdl/standard_library/write_tsv.json +6 -0
  214. toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
  215. toil/test/wdl/standard_library/zip.json +12 -0
  216. toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
  217. toil/test/wdl/test.csv +3 -0
  218. toil/test/wdl/test.tsv +3 -0
  219. toil/test/wdl/testfiles/croo.wdl +38 -0
  220. toil/test/wdl/testfiles/drop_files.wdl +62 -0
  221. toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
  222. toil/test/wdl/testfiles/empty.txt +0 -0
  223. toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
  224. toil/test/wdl/testfiles/random.wdl +66 -0
  225. toil/test/wdl/testfiles/string_file_coercion.json +1 -0
  226. toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
  227. toil/test/wdl/testfiles/test.json +4 -0
  228. toil/test/wdl/testfiles/test_boolean.txt +1 -0
  229. toil/test/wdl/testfiles/test_float.txt +1 -0
  230. toil/test/wdl/testfiles/test_int.txt +1 -0
  231. toil/test/wdl/testfiles/test_lines.txt +5 -0
  232. toil/test/wdl/testfiles/test_map.txt +2 -0
  233. toil/test/wdl/testfiles/test_string.txt +1 -0
  234. toil/test/wdl/testfiles/url_to_file.wdl +13 -0
  235. toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
  236. toil/test/wdl/testfiles/vocab.json +1 -0
  237. toil/test/wdl/testfiles/vocab.wdl +66 -0
  238. toil/test/wdl/testfiles/wait.wdl +34 -0
  239. toil/test/wdl/wdl_specification/type_pair.json +23 -0
  240. toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
  241. toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
  242. toil/test/wdl/wdl_specification/v1_spec.json +1 -0
  243. toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
  244. toil/test/wdl/wdltoil_test.py +680 -407
  245. toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
  246. toil/version.py +9 -9
  247. toil/wdl/wdltoil.py +336 -123
  248. {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/METADATA +5 -4
  249. toil-8.2.0.dist-info/RECORD +439 -0
  250. {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
  251. toil-8.1.0b1.dist-info/RECORD +0 -259
  252. {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
  253. {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
  254. {toil-8.1.0b1.dist-info → toil-8.2.0.dist-info}/top_level.txt +0 -0
toil/test/src/jobTest.py CHANGED
@@ -11,32 +11,38 @@
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 Callable
14
15
  import collections
15
16
  import logging
16
17
  import os
18
+ from pathlib import Path
17
19
  import random
18
- import unittest
20
+ from typing import cast, Optional, Union, Any, NoReturn
19
21
 
20
22
  import pytest
21
23
 
22
24
  from toil.common import Toil
23
25
  from toil.exceptions import FailedJobsException
24
- from toil.job import Job, JobFunctionWrappingJob, JobGraphDeadlockException
25
- from toil.test import ToilTest, get_temp_file, slow
26
+ from toil.job import (
27
+ FunctionWrappingJob,
28
+ Job,
29
+ JobFunctionWrappingJob,
30
+ JobGraphDeadlockException,
31
+ ServiceHostJob,
32
+ Promise,
33
+ )
34
+ from toil.lib.misc import FileDescriptorOrPath
35
+ from toil.test import pslow as slow
26
36
 
27
- logger = logging.getLogger(__name__)
37
+ from pytest_subtests import SubTests
28
38
 
29
39
 
30
- class JobTest(ToilTest):
40
+ class TestJob:
31
41
  """Tests the job class."""
32
42
 
33
- @classmethod
34
- def setUpClass(cls):
35
- super().setUpClass()
36
- logging.basicConfig(level=logging.DEBUG)
37
-
38
43
  @slow
39
- def testStatic(self):
44
+ @pytest.mark.slow
45
+ def testStatic(self, tmp_path: Path) -> None:
40
46
  r"""
41
47
  Create a DAG of jobs non-dynamically and run it. DAG is::
42
48
 
@@ -48,39 +54,35 @@ class JobTest(ToilTest):
48
54
 
49
55
  Follow on is marked by ``->``
50
56
  """
51
- outFile = get_temp_file(rootDir=self._createTempDir())
52
- try:
53
-
54
- # Create the jobs
55
- A = Job.wrapFn(fn1Test, "A", outFile)
56
- B = Job.wrapFn(fn1Test, A.rv(), outFile)
57
- C = Job.wrapFn(fn1Test, B.rv(), outFile)
58
- D = Job.wrapFn(fn1Test, C.rv(), outFile)
59
- E = Job.wrapFn(fn1Test, D.rv(), outFile)
60
- F = Job.wrapFn(fn1Test, E.rv(), outFile)
61
- # Connect them into a workflow
62
- A.addChild(B)
63
- A.addChild(C)
64
- B.addChild(C)
65
- B.addFollowOn(E)
66
- C.addFollowOn(D)
67
- A.addFollowOn(F)
68
-
69
- # Create the runner for the workflow.
70
- options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
71
- options.logLevel = "INFO"
72
- options.retryCount = 100
73
- options.badWorker = 0.5
74
- options.badWorkerFailInterval = 0.01
75
- # Run the workflow, the return value being the number of failed jobs
76
- Job.Runner.startToil(A, options)
57
+ outFile = tmp_path / "out"
58
+ # Create the jobs
59
+ A = Job.wrapFn(fn1Test, "A", outFile)
60
+ B = Job.wrapFn(fn1Test, A.rv(), outFile)
61
+ C = Job.wrapFn(fn1Test, B.rv(), outFile)
62
+ D = Job.wrapFn(fn1Test, C.rv(), outFile)
63
+ E = Job.wrapFn(fn1Test, D.rv(), outFile)
64
+ F = Job.wrapFn(fn1Test, E.rv(), outFile)
65
+ # Connect them into a workflow
66
+ A.addChild(B)
67
+ A.addChild(C)
68
+ B.addChild(C)
69
+ B.addFollowOn(E)
70
+ C.addFollowOn(D)
71
+ A.addFollowOn(F)
72
+
73
+ # Create the runner for the workflow.
74
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobstore")
75
+ options.logLevel = "INFO"
76
+ options.retryCount = 100
77
+ options.badWorker = 0.5
78
+ options.badWorkerFailInterval = 0.01
79
+ # Run the workflow, the return value being the number of failed jobs
80
+ Job.Runner.startToil(A, options)
77
81
 
78
- # Check output
79
- self.assertEqual(open(outFile).readline(), "ABCDEFG")
80
- finally:
81
- os.remove(outFile)
82
+ # Check output
83
+ assert open(outFile).readline() == "ABCDEFG"
82
84
 
83
- def testStatic2(self):
85
+ def testStatic2(self, tmp_path: Path) -> None:
84
86
  r"""
85
87
  Create a DAG of jobs non-dynamically and run it. DAG is::
86
88
 
@@ -92,84 +94,68 @@ class JobTest(ToilTest):
92
94
 
93
95
  Follow on is marked by ``->``
94
96
  """
95
- outFile = get_temp_file(rootDir=self._createTempDir())
96
- try:
97
-
98
- # Create the jobs
99
- A = Job.wrapFn(fn1Test, "A", outFile)
100
- B = Job.wrapFn(fn1Test, A.rv(), outFile)
101
- C = Job.wrapFn(fn1Test, B.rv(), outFile)
102
- D = Job.wrapFn(fn1Test, C.rv(), outFile)
103
-
104
- # Connect them into a workflow
105
- A.addChild(B)
106
- A.addFollowOn(C)
107
- C.addChild(D)
108
-
109
- # Create the runner for the workflow.
110
- options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
111
- options.logLevel = "INFO"
112
- options.retryCount = 100
113
- options.badWorker = 0.5
114
- options.badWorkerFailInterval = 0.01
115
- # Run the workflow, the return value being the number of failed jobs
116
- Job.Runner.startToil(A, options)
97
+ outFile = tmp_path / "out"
98
+
99
+ # Create the jobs
100
+ A = Job.wrapFn(fn1Test, "A", outFile)
101
+ B = Job.wrapFn(fn1Test, A.rv(), outFile)
102
+ C = Job.wrapFn(fn1Test, B.rv(), outFile)
103
+ D = Job.wrapFn(fn1Test, C.rv(), outFile)
117
104
 
118
- # Check output
119
- self.assertEqual(open(outFile).readline(), "ABCDE")
120
- finally:
121
- os.remove(outFile)
105
+ # Connect them into a workflow
106
+ A.addChild(B)
107
+ A.addFollowOn(C)
108
+ C.addChild(D)
109
+
110
+ # Create the runner for the workflow.
111
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobstore")
112
+ options.logLevel = "INFO"
113
+ options.retryCount = 100
114
+ options.badWorker = 0.5
115
+ options.badWorkerFailInterval = 0.01
116
+ # Run the workflow, the return value being the number of failed jobs
117
+ Job.Runner.startToil(A, options)
118
+
119
+ # Check output
120
+ assert open(outFile).readline() == "ABCDE"
122
121
 
123
122
  @slow
124
- def testTrivialDAGConsistency(self):
125
- options = Job.Runner.getDefaultOptions(self._createTempDir() + "/jobStore")
123
+ @pytest.mark.slow
124
+ def testTrivialDAGConsistency(self, tmp_path: Path) -> None:
125
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobStore")
126
126
  options.clean = "always"
127
127
  options.logLevel = "debug"
128
128
  i = Job.wrapJobFn(trivialParent)
129
129
  with Toil(options) as toil:
130
- try:
130
+ with pytest.raises(FailedJobsException):
131
131
  toil.start(i)
132
- except FailedJobsException:
133
- # we expect this exception to be raised
134
- pass
135
- else:
136
- self.fail()
137
132
 
138
133
  @pytest.mark.timeout(300)
139
- def testDAGConsistency(self):
140
- options = Job.Runner.getDefaultOptions(self._createTempDir() + "/jobStore")
134
+ def testDAGConsistency(self, tmp_path: Path) -> None:
135
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobStore")
141
136
  options.clean = "always"
142
137
  options.logLevel = "debug"
143
138
  i = Job.wrapJobFn(parent)
144
139
  with Toil(options) as toil:
145
- try:
140
+ with pytest.raises(FailedJobsException):
146
141
  toil.start(i)
147
- except FailedJobsException:
148
- # we expect this exception to be raised
149
- pass
150
- else:
151
- self.fail()
152
142
 
153
143
  @slow
154
- def testSiblingDAGConsistency(self):
144
+ @pytest.mark.slow
145
+ def testSiblingDAGConsistency(self, tmp_path: Path) -> None:
155
146
  """
156
147
  Slightly more complex case. The stranded job's predecessors are siblings instead of
157
148
  parent/child.
158
149
  """
159
- options = Job.Runner.getDefaultOptions(self._createTempDir() + "/jobStore")
150
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobStore")
160
151
  options.clean = "always"
161
152
  options.logLevel = "debug"
162
153
  i = Job.wrapJobFn(diamond)
163
154
  with Toil(options) as toil:
164
- try:
155
+ with pytest.raises(FailedJobsException):
165
156
  toil.start(i)
166
- except FailedJobsException:
167
- # we expect this exception to be raised
168
- pass
169
- else:
170
- self.fail()
171
157
 
172
- def testDeadlockDetection(self):
158
+ def testDeadlockDetection(self) -> None:
173
159
  """
174
160
  Randomly generate job graphs with various types of cycle in them and
175
161
  check they cause an exception properly. Also check that multiple roots
@@ -181,19 +167,19 @@ class JobTest(ToilTest):
181
167
  childEdges = self.makeRandomDAG(nodeNumber)
182
168
  # Get an adjacency list representation and check is acyclic
183
169
  adjacencyList = self.getAdjacencyList(nodeNumber, childEdges)
184
- self.assertTrue(self.isAcyclic(adjacencyList))
170
+ assert self.isAcyclic(adjacencyList)
185
171
 
186
172
  # Add in follow-on edges - these are returned as a list, and as a set of augmented
187
173
  # edges in the adjacency list
188
174
  # edges in the adjacency list
189
175
  followOnEdges = self.addRandomFollowOnEdges(adjacencyList)
190
- self.assertTrue(self.isAcyclic(adjacencyList))
176
+ assert self.isAcyclic(adjacencyList)
191
177
  # Make the job graph
192
178
  rootJob = self.makeJobGraph(nodeNumber, childEdges, followOnEdges, None)
193
179
  rootJob.checkJobGraphAcylic() # This should not throw an exception
194
180
  rootJob.checkJobGraphConnected() # Nor this
195
181
  # Check root detection explicitly
196
- self.assertEqual(rootJob.getRootJobs(), {rootJob})
182
+ assert rootJob.getRootJobs() == {rootJob}
197
183
 
198
184
  # Test making multiple roots
199
185
  childEdges2 = childEdges.copy()
@@ -203,23 +189,17 @@ class JobTest(ToilTest):
203
189
  rootJob2 = self.makeJobGraph(
204
190
  nodeNumber + 1, childEdges2, followOnEdges, None, False
205
191
  )
206
- try:
192
+ with pytest.raises(JobGraphDeadlockException):
207
193
  rootJob2.checkJobGraphConnected()
208
- self.assertTrue(False) # Multiple roots were not detected
209
- except JobGraphDeadlockException:
210
- pass # This is the expected behaviour
211
194
 
212
- def checkChildEdgeCycleDetection(fNode, tNode):
195
+ def checkChildEdgeCycleDetection(fNode: int, tNode: int) -> None:
213
196
  childEdges.add((fNode, tNode)) # Create a cycle
214
197
  adjacencyList[fNode].add(tNode)
215
- self.assertTrue(not self.isAcyclic(adjacencyList))
216
- try:
198
+ assert not self.isAcyclic(adjacencyList)
199
+ with pytest.raises(JobGraphDeadlockException):
217
200
  self.makeJobGraph(
218
201
  nodeNumber, childEdges, followOnEdges, None
219
202
  ).checkJobGraphAcylic()
220
- self.assertTrue(False) # A cycle was not detected
221
- except JobGraphDeadlockException:
222
- pass # This is the expected behaviour
223
203
  # Remove the edges
224
204
  childEdges.remove((fNode, tNode))
225
205
  adjacencyList[fNode].remove(tNode)
@@ -228,15 +208,12 @@ class JobTest(ToilTest):
228
208
  nodeNumber, childEdges, followOnEdges, None, False
229
209
  ).checkJobGraphAcylic()
230
210
 
231
- def checkFollowOnEdgeCycleDetection(fNode, tNode):
211
+ def checkFollowOnEdgeCycleDetection(fNode: int, tNode: int) -> None:
232
212
  followOnEdges.add((fNode, tNode)) # Create a cycle
233
- try:
213
+ with pytest.raises(JobGraphDeadlockException):
234
214
  self.makeJobGraph(
235
215
  nodeNumber, childEdges, followOnEdges, None, False
236
216
  ).checkJobGraphAcylic()
237
- # self.assertTrue(False) #The cycle was not detected
238
- except JobGraphDeadlockException:
239
- pass # This is the expected behaviour
240
217
  # Remove the edges
241
218
  followOnEdges.remove((fNode, tNode))
242
219
  # Check is now acyclic again
@@ -279,7 +256,10 @@ class JobTest(ToilTest):
279
256
  checkFollowOnEdgeCycleDetection(fNode, tNode)
280
257
 
281
258
  @slow
282
- def testNewCheckpointIsLeafVertexNonRootCase(self):
259
+ @pytest.mark.slow
260
+ def testNewCheckpointIsLeafVertexNonRootCase(
261
+ self, tmp_path: Path, subtests: SubTests
262
+ ) -> None:
283
263
  """
284
264
  Test for issue #1465: Detection of checkpoint jobs that are not leaf vertices
285
265
  identifies leaf vertices incorrectly
@@ -293,17 +273,20 @@ class JobTest(ToilTest):
293
273
 
294
274
  """
295
275
 
296
- def createWorkflow():
276
+ def createWorkflow() -> tuple[Job, FunctionWrappingJob]:
297
277
  rootJob = Job.wrapJobFn(simpleJobFn, "Parent")
298
278
  childCheckpointJob = rootJob.addChildJobFn(
299
279
  simpleJobFn, "Child", checkpoint=True
300
280
  )
301
281
  return rootJob, childCheckpointJob
302
282
 
303
- self.runNewCheckpointIsLeafVertexTest(createWorkflow)
283
+ self.runNewCheckpointIsLeafVertexTest(tmp_path, subtests, createWorkflow)
304
284
 
305
285
  @slow
306
- def testNewCheckpointIsLeafVertexRootCase(self):
286
+ @pytest.mark.slow
287
+ def testNewCheckpointIsLeafVertexRootCase(
288
+ self, tmp_path: Path, subtests: SubTests
289
+ ) -> None:
307
290
  """
308
291
  Test for issue #1466: Detection of checkpoint jobs that are not leaf vertices
309
292
  omits the workflow root job
@@ -315,13 +298,18 @@ class JobTest(ToilTest):
315
298
 
316
299
  """
317
300
 
318
- def createWorkflow():
301
+ def createWorkflow() -> tuple[Job, Job]:
319
302
  rootJob = Job.wrapJobFn(simpleJobFn, "Root", checkpoint=True)
320
303
  return rootJob, rootJob
321
304
 
322
- self.runNewCheckpointIsLeafVertexTest(createWorkflow)
305
+ self.runNewCheckpointIsLeafVertexTest(tmp_path, subtests, createWorkflow)
323
306
 
324
- def runNewCheckpointIsLeafVertexTest(self, createWorkflowFn):
307
+ def runNewCheckpointIsLeafVertexTest(
308
+ self,
309
+ tmp_path: Path,
310
+ subtests: SubTests,
311
+ createWorkflowFn: Callable[[], tuple[Job, Job]],
312
+ ) -> None:
325
313
  """
326
314
  Test verification that a checkpoint job is a leaf vertex using both
327
315
  valid and invalid cases.
@@ -333,51 +321,65 @@ class JobTest(ToilTest):
333
321
 
334
322
  """
335
323
 
336
- logger.debug("Test checkpoint job that is a leaf vertex")
337
- self.runCheckpointVertexTest(*createWorkflowFn(), expectedException=None)
324
+ with subtests.test(msg="Test checkpoint job that is a leaf vertex"):
325
+ sub_tmp_path1 = tmp_path / "1"
326
+ sub_tmp_path1.mkdir()
327
+ self.runCheckpointVertexTest(
328
+ *createWorkflowFn(), tmp_path=sub_tmp_path1, expectedException=None
329
+ )
338
330
 
339
- logger.debug(
340
- "Test checkpoint job that is not a leaf vertex due to the presence of a service"
341
- )
342
- self.runCheckpointVertexTest(
343
- *createWorkflowFn(),
344
- checkpointJobService=TrivialService("LeafTestService"),
345
- expectedException=JobGraphDeadlockException
346
- )
331
+ with subtests.test(
332
+ msg="Test checkpoint job that is not a leaf vertex due to the presence of a service"
333
+ ):
334
+ sub_tmp_path2 = tmp_path / "2"
335
+ sub_tmp_path2.mkdir()
336
+ self.runCheckpointVertexTest(
337
+ *createWorkflowFn(),
338
+ tmp_path=sub_tmp_path2,
339
+ checkpointJobService=TrivialService("LeafTestService"),
340
+ expectedException=JobGraphDeadlockException,
341
+ )
347
342
 
348
- logger.debug(
349
- "Test checkpoint job that is not a leaf vertex due to the presence of a child job"
350
- )
351
- self.runCheckpointVertexTest(
352
- *createWorkflowFn(),
353
- checkpointJobChild=Job.wrapJobFn(simpleJobFn, "LeafTestChild"),
354
- expectedException=JobGraphDeadlockException
355
- )
343
+ with subtests.test(
344
+ msg="Test checkpoint job that is not a leaf vertex due to the presence of a child job"
345
+ ):
346
+ sub_tmp_path3 = tmp_path / "3"
347
+ sub_tmp_path3.mkdir()
348
+ self.runCheckpointVertexTest(
349
+ *createWorkflowFn(),
350
+ tmp_path=sub_tmp_path3,
351
+ checkpointJobChild=Job.wrapJobFn(simpleJobFn, "LeafTestChild"),
352
+ expectedException=JobGraphDeadlockException,
353
+ )
356
354
 
357
- logger.debug(
358
- "Test checkpoint job that is not a leaf vertex due to the presence of a follow-on job"
359
- )
360
- self.runCheckpointVertexTest(
361
- *createWorkflowFn(),
362
- checkpointJobFollowOn=Job.wrapJobFn(simpleJobFn, "LeafTestFollowOn"),
363
- expectedException=JobGraphDeadlockException
364
- )
355
+ with subtests.test(
356
+ msg="Test checkpoint job that is not a leaf vertex due to the presence of a follow-on job"
357
+ ):
358
+ sub_tmp_path4 = tmp_path / "4"
359
+ sub_tmp_path4.mkdir()
360
+ self.runCheckpointVertexTest(
361
+ *createWorkflowFn(),
362
+ tmp_path=sub_tmp_path4,
363
+ checkpointJobFollowOn=Job.wrapJobFn(simpleJobFn, "LeafTestFollowOn"),
364
+ expectedException=JobGraphDeadlockException,
365
+ )
365
366
 
366
367
  def runCheckpointVertexTest(
367
368
  self,
368
- workflowRootJob,
369
- checkpointJob,
370
- checkpointJobService=None,
371
- checkpointJobChild=None,
372
- checkpointJobFollowOn=None,
373
- expectedException=None,
374
- ):
369
+ workflowRootJob: Job,
370
+ checkpointJob: Job,
371
+ tmp_path: Path,
372
+ checkpointJobService: Optional[Job.Service] = None,
373
+ checkpointJobChild: Optional[Job] = None,
374
+ checkpointJobFollowOn: Optional[Job] = None,
375
+ expectedException: Optional[type[Exception]] = None,
376
+ ) -> None:
375
377
  """
376
378
  Modifies the checkpoint job according to the given parameters
377
379
  then runs the workflow, checking for the expected exception, if any.
378
380
  """
379
381
 
380
- self.assertTrue(checkpointJob.checkpoint)
382
+ assert checkpointJob.checkpoint
381
383
 
382
384
  if checkpointJobService is not None:
383
385
  checkpointJob.addService(checkpointJobService)
@@ -387,38 +389,37 @@ class JobTest(ToilTest):
387
389
  checkpointJob.addFollowOn(checkpointJobFollowOn)
388
390
 
389
391
  # Run the workflow and check for the expected behavior
390
- options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
392
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobstore")
391
393
  options.logLevel = "INFO"
392
394
  if expectedException is None:
393
395
  Job.Runner.startToil(workflowRootJob, options)
394
396
  else:
395
- try:
397
+ with pytest.raises(expectedException):
396
398
  Job.Runner.startToil(workflowRootJob, options)
397
- self.fail("The expected exception was not thrown")
398
- except expectedException as ex:
399
- logger.debug("The expected exception was thrown: %s", repr(ex))
400
399
 
401
400
  @slow
402
- def testEvaluatingRandomDAG(self):
401
+ @pytest.mark.slow
402
+ def testEvaluatingRandomDAG(self, tmp_path: Path) -> None:
403
403
  """
404
404
  Randomly generate test input then check that the job graph can be
405
405
  run successfully, using the existence of promises
406
406
  to validate the run.
407
407
  """
408
- jobStore = self._getTestJobStorePath()
408
+ jobStore = tmp_path / "jobstore"
409
409
  for test in range(5):
410
410
  # Temporary file
411
- tempDir = self._createTempDir(purpose="tempDir")
411
+ tempDir = tmp_path / f"tempDir{test}"
412
+ tempDir.mkdir()
412
413
  # Make a random DAG for the set of child edges
413
414
  nodeNumber = random.choice(range(2, 8))
414
415
  childEdges = self.makeRandomDAG(nodeNumber)
415
416
  # Get an adjacency list representation and check is acyclic
416
417
  adjacencyList = self.getAdjacencyList(nodeNumber, childEdges)
417
- self.assertTrue(self.isAcyclic(adjacencyList))
418
+ assert self.isAcyclic(adjacencyList)
418
419
  # Add in follow on edges - these are returned as a list, and as a set of augmented
419
420
  # edges in the adjacency list
420
421
  followOnEdges = self.addRandomFollowOnEdges(adjacencyList)
421
- self.assertTrue(self.isAcyclic(adjacencyList))
422
+ assert self.isAcyclic(adjacencyList)
422
423
  # Make the job graph
423
424
  rootJob = self.makeJobGraph(nodeNumber, childEdges, followOnEdges, tempDir)
424
425
  # Run the job graph
@@ -450,7 +451,7 @@ class JobTest(ToilTest):
450
451
  except FailedJobsException as e:
451
452
  numberOfFailedJobs = e.numberOfFailedJobs
452
453
  if totalTrys > 32: # p(fail after this many restarts) ~= 0.5**32
453
- self.fail() # Exceeded a reasonable number of restarts
454
+ pytest.fail("Exceeded a reasonable number of restarts")
454
455
  totalTrys += 1
455
456
 
456
457
  # For each job check it created a valid output file and add the ordering
@@ -458,9 +459,9 @@ class JobTest(ToilTest):
458
459
  # so we can check they are compatible with the relationships defined by the job DAG.
459
460
  ordering = None
460
461
  for i in range(nodeNumber):
461
- with open(os.path.join(tempDir, str(i))) as fH:
462
+ with (tempDir / str(i)).open() as fH:
462
463
  ordering = list(map(int, fH.readline().split()))
463
- self.assertEqual(int(ordering[-1]), i)
464
+ assert int(ordering[-1]) == i
464
465
  for j in ordering[:-1]:
465
466
  adjacencyList[int(j)].add(i)
466
467
  # Check the ordering retains an acyclic graph
@@ -469,16 +470,16 @@ class JobTest(ToilTest):
469
470
  print("CHILD EDGES", childEdges)
470
471
  print("FOLLOW ON EDGES", followOnEdges)
471
472
  print("ADJACENCY LIST", adjacencyList)
472
- self.assertTrue(self.isAcyclic(adjacencyList))
473
+ assert self.isAcyclic(adjacencyList)
473
474
 
474
475
  @staticmethod
475
- def getRandomEdge(nodeNumber):
476
+ def getRandomEdge(nodeNumber: int) -> tuple[int, int]:
476
477
  assert nodeNumber > 1
477
478
  fNode = random.choice(range(nodeNumber - 1))
478
479
  return fNode, random.choice(range(fNode + 1, nodeNumber))
479
480
 
480
481
  @staticmethod
481
- def makeRandomDAG(nodeNumber):
482
+ def makeRandomDAG(nodeNumber: int) -> set[tuple[int, int]]:
482
483
  """
483
484
  Makes a random dag with "nodeNumber" nodes in which all nodes are connected. Return value
484
485
  is list of edges, each of form (a, b), where a and b are integers >= 0 < nodeNumber
@@ -492,56 +493,58 @@ class JobTest(ToilTest):
492
493
  edges = {(random.choice(range(i)), i) for i in range(1, nodeNumber)}
493
494
  # Add extra random edges until there are edgeNumber edges
494
495
  while len(edges) < edgeNumber:
495
- edges.add(JobTest.getRandomEdge(nodeNumber))
496
+ edges.add(TestJob.getRandomEdge(nodeNumber))
496
497
  return edges
497
498
 
498
499
  @staticmethod
499
- def getAdjacencyList(nodeNumber, edges):
500
+ def getAdjacencyList(
501
+ nodeNumber: int, edges: set[tuple[int, int]]
502
+ ) -> list[set[int]]:
500
503
  """
501
504
  Make adjacency list representation of edges
502
505
  """
503
- adjacencyList = [set() for _ in range(nodeNumber)]
506
+ adjacencyList: list[set[int]] = [set() for _ in range(nodeNumber)]
504
507
  for fNode, tNode in edges:
505
508
  adjacencyList[fNode].add(tNode)
506
509
  return adjacencyList
507
510
 
508
- def reachable(self, node, adjacencyList, followOnAdjacencyList=None):
511
+ def reachable(self, node: int, adjacencyList: list[set[int]]) -> set[int]:
509
512
  """
510
513
  Find the set of nodes reachable from this node (including the node). Return is a set of
511
514
  integers.
512
515
  """
513
- visited = set()
516
+ visited: set[int] = set()
514
517
 
515
- def dfs(fNode):
518
+ def dfs(fNode: int) -> None:
516
519
  if fNode not in visited:
517
520
  visited.add(fNode)
518
521
  list(map(dfs, adjacencyList[fNode]))
519
- if followOnAdjacencyList is not None:
520
- list(map(dfs, followOnAdjacencyList[fNode]))
521
522
 
522
523
  dfs(node)
523
524
  return visited
524
525
 
525
- def addRandomFollowOnEdges(self, childAdjacencyList):
526
+ def addRandomFollowOnEdges(
527
+ self, childAdjacencyList: list[set[int]]
528
+ ) -> set[tuple[int, int]]:
526
529
  """
527
530
  Adds random follow on edges to the graph, represented as an adjacency list. The follow on
528
531
  edges are returned as a set and their augmented edges are added to the adjacency list.
529
532
  """
530
533
 
531
- def makeAugmentedAdjacencyList():
534
+ def makeAugmentedAdjacencyList() -> list[set[int]]:
532
535
  augmentedAdjacencyList = [
533
536
  childAdjacencyList[i].union(followOnAdjacencyList[i])
534
537
  for i in range(len(childAdjacencyList))
535
538
  ]
536
539
 
537
- def addImpliedEdges(node, followOnEdges):
540
+ def addImpliedEdges(node: int, followOnEdges: set[int]) -> None:
538
541
  # Let node2 be a child of node or a successor of a child of node.
539
542
  # For all node2 the following adds an edge to the augmented
540
543
  # adjacency list from node2 to each followOn of node
541
544
 
542
- visited = set()
545
+ visited: set[int] = set()
543
546
 
544
- def f(node2):
547
+ def f(node2: int) -> None:
545
548
  if node2 not in visited:
546
549
  visited.add(node2)
547
550
  for i in followOnEdges:
@@ -556,10 +559,10 @@ class JobTest(ToilTest):
556
559
  return augmentedAdjacencyList
557
560
 
558
561
  followOnEdges = set()
559
- followOnAdjacencyList = [set() for i in childAdjacencyList]
562
+ followOnAdjacencyList: list[set[int]] = [set() for i in childAdjacencyList]
560
563
  # Loop to create the follow on edges (try 1000 times)
561
564
  while random.random() > 0.001:
562
- fNode, tNode = JobTest.getRandomEdge(len(childAdjacencyList))
565
+ fNode, tNode = TestJob.getRandomEdge(len(childAdjacencyList))
563
566
 
564
567
  # Make an adjacency list including augmented edges and proposed
565
568
  # follow on edge
@@ -583,17 +586,22 @@ class JobTest(ToilTest):
583
586
  return followOnEdges
584
587
 
585
588
  def makeJobGraph(
586
- self, nodeNumber, childEdges, followOnEdges, outPath, addServices=True
587
- ):
589
+ self,
590
+ nodeNumber: int,
591
+ childEdges: set[tuple[int, int]],
592
+ followOnEdges: set[tuple[int, int]],
593
+ outPath: Optional[Path],
594
+ addServices: bool = True,
595
+ ) -> Job:
588
596
  """
589
597
  Converts a DAG into a job graph. childEdges and followOnEdges are the lists of child and
590
598
  followOn edges.
591
599
  """
592
600
  # Map of jobs to the list of promises they have
593
- jobsToPromisesMap = {}
601
+ jobsToPromisesMap: dict[FunctionWrappingJob, list[Promise]] = {}
594
602
 
595
- def makeJob(string):
596
- promises = []
603
+ def makeJob(string: str) -> FunctionWrappingJob:
604
+ promises: list[Promise] = []
597
605
  job = Job.wrapFn(
598
606
  fn2Test,
599
607
  promises,
@@ -610,7 +618,9 @@ class JobTest(ToilTest):
610
618
  jobs = [makeJob(str(i)) for i in range(nodeNumber)]
611
619
 
612
620
  # Record predecessors for sampling
613
- predecessors = collections.defaultdict(list)
621
+ predecessors: dict[FunctionWrappingJob, list[FunctionWrappingJob]] = (
622
+ collections.defaultdict(list)
623
+ )
614
624
 
615
625
  # Make the edges
616
626
  for fNode, tNode in childEdges:
@@ -624,7 +634,9 @@ class JobTest(ToilTest):
624
634
  jobsToRvs = {
625
635
  job: (
626
636
  job.addService(
627
- TrivialService(job.rv(), cores=0.1, memory="0.5G", disk="0.1G")
637
+ TrivialService(
638
+ cast(str, job.rv()), cores=0.1, memory="0.5G", disk="0.1G"
639
+ )
628
640
  )
629
641
  if addServices
630
642
  else job.rv()
@@ -632,7 +644,7 @@ class JobTest(ToilTest):
632
644
  for job in jobs
633
645
  }
634
646
 
635
- def getRandomPredecessor(job):
647
+ def getRandomPredecessor(job: FunctionWrappingJob) -> FunctionWrappingJob:
636
648
  predecessor = random.choice(list(predecessors[job]))
637
649
  while random.random() > 0.5 and len(predecessors[predecessor]) > 0:
638
650
  predecessor = random.choice(list(predecessors[predecessor]))
@@ -648,13 +660,13 @@ class JobTest(ToilTest):
648
660
 
649
661
  return jobs[0]
650
662
 
651
- def isAcyclic(self, adjacencyList):
663
+ def isAcyclic(self, adjacencyList: list[set[int]]) -> bool:
652
664
  """
653
665
  Returns true if there are any cycles in the graph, which is represented as an adjacency
654
666
  list.
655
667
  """
656
668
 
657
- def cyclic(fNode, visited, stack):
669
+ def cyclic(fNode: int, visited: set[int], stack: list[int]) -> Union[bool, int]:
658
670
  if fNode not in visited:
659
671
  visited.add(fNode)
660
672
  assert fNode not in stack
@@ -665,18 +677,18 @@ class JobTest(ToilTest):
665
677
  assert stack.pop() == fNode
666
678
  return fNode in stack
667
679
 
668
- visited = set()
680
+ visited: set[int] = set()
669
681
  for i in range(len(adjacencyList)):
670
682
  if cyclic(i, visited, []):
671
683
  return False
672
684
  return True
673
685
 
674
686
 
675
- def simpleJobFn(job, value):
687
+ def simpleJobFn(job: ServiceHostJob, value: str) -> None:
676
688
  job.fileStore.log_to_leader(value)
677
689
 
678
690
 
679
- def fn1Test(string, outputFile):
691
+ def fn1Test(string: str, outputFile: FileDescriptorOrPath) -> str:
680
692
  """
681
693
  Function appends the next character after the last character in the given
682
694
  string to the string, writes the string to a file, and returns it. For
@@ -689,7 +701,7 @@ def fn1Test(string, outputFile):
689
701
  return rV
690
702
 
691
703
 
692
- def fn2Test(pStrings, s, outputFile):
704
+ def fn2Test(pStrings: list[str], s: str, outputFile: Path) -> str:
693
705
  """
694
706
  Function concatenates the strings in pStrings and s, in that order, and writes the result to
695
707
  the output file. Returns s.
@@ -699,7 +711,7 @@ def fn2Test(pStrings, s, outputFile):
699
711
  return s
700
712
 
701
713
 
702
- def trivialParent(job):
714
+ def trivialParent(job: Job) -> None:
703
715
  strandedJob = JobFunctionWrappingJob(child)
704
716
  failingJob = JobFunctionWrappingJob(errorChild)
705
717
 
@@ -708,7 +720,7 @@ def trivialParent(job):
708
720
  failingJob.addChild(strandedJob)
709
721
 
710
722
 
711
- def parent(job):
723
+ def parent(job: Job) -> None:
712
724
  childJob = JobFunctionWrappingJob(child)
713
725
  strandedJob = JobFunctionWrappingJob(child)
714
726
  failingJob = JobFunctionWrappingJob(errorChild)
@@ -719,7 +731,7 @@ def parent(job):
719
731
  failingJob.addChild(strandedJob)
720
732
 
721
733
 
722
- def diamond(job):
734
+ def diamond(job: Job) -> None:
723
735
  childJob = JobFunctionWrappingJob(child)
724
736
  strandedJob = JobFunctionWrappingJob(child)
725
737
  failingJob = JobFunctionWrappingJob(errorChild)
@@ -730,32 +742,28 @@ def diamond(job):
730
742
  failingJob.addChild(strandedJob)
731
743
 
732
744
 
733
- def child(job):
745
+ def child(job: Job) -> None:
734
746
  assert job.cores is not None
735
747
  assert job.disk is not None
736
748
  assert job.memory is not None
737
749
  assert job.preemptible is not None
738
750
 
739
751
 
740
- def errorChild(job):
752
+ def errorChild(job: Job) -> NoReturn:
741
753
  raise RuntimeError("Child failure")
742
754
 
743
755
 
744
756
  class TrivialService(Job.Service):
745
- def __init__(self, message, *args, **kwargs):
757
+ def __init__(self, message: str, *args: Any, **kwargs: Any) -> None:
746
758
  """Service that does nothing, used to check for deadlocks"""
747
759
  Job.Service.__init__(self, *args, **kwargs)
748
760
  self.message = message
749
761
 
750
- def start(self, job):
762
+ def start(self, job: ServiceHostJob) -> str:
751
763
  return self.message
752
764
 
753
- def stop(self, job):
765
+ def stop(self, job: ServiceHostJob) -> None:
754
766
  pass
755
767
 
756
- def check(self):
757
- pass
758
-
759
-
760
- if __name__ == "__main__":
761
- unittest.main()
768
+ def check(self) -> bool:
769
+ return True