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
@@ -14,34 +14,34 @@
14
14
  import logging
15
15
  import mimetypes
16
16
  import os
17
+ from pathlib import Path
17
18
  import subprocess
18
19
  import sys
20
+ from typing import Optional
19
21
 
20
- from toil.test import ToilTest, slow
22
+ from toil.test import pslow as slow
21
23
  from toil.test.mesos import helloWorld
22
24
 
23
25
  logging.basicConfig(level=logging.DEBUG)
24
26
  logger = logging.getLogger(__name__)
25
27
 
26
28
 
27
- class RegularLogTest(ToilTest):
29
+ class RegularLogTest:
28
30
 
29
- def setUp(self) -> None:
30
- super().setUp()
31
- self.tempDir = self._createTempDir(purpose="tempDir")
32
-
33
- def _getFiles(self, dir):
31
+ def _getFiles(self, dirpath: Path) -> list[str]:
34
32
  return [
35
- os.path.join(dir, f)
36
- for f in os.listdir(dir)
37
- if os.path.isfile(os.path.join(dir, f))
33
+ os.path.join(dirpath, f)
34
+ for f in os.listdir(dirpath)
35
+ if os.path.isfile(os.path.join(dirpath, f))
38
36
  ]
39
37
 
40
- def _assertFileTypeExists(self, dir, extension, encoding=None):
38
+ def _assertFileTypeExists(
39
+ self, dirpath: Path, extension: str, encoding: Optional[str] = None
40
+ ) -> None:
41
41
  # an encoding of None implies no compression
42
- logger.info("Checking for %s file in %s", extension, dir)
43
- onlyFiles = self._getFiles(dir)
44
- logger.info("Found: %s", str(os.listdir(dir)))
42
+ logger.info("Checking for %s file in %s", extension, dirpath)
43
+ onlyFiles = self._getFiles(dirpath)
44
+ logger.info("Found: %s", str(os.listdir(dirpath)))
45
45
  onlyLogs = [f for f in onlyFiles if f.endswith(extension)]
46
46
  logger.info("Found matching: %s", str(onlyLogs))
47
47
  assert onlyLogs
@@ -57,10 +57,10 @@ class RegularLogTest(ToilTest):
57
57
  assert f.read().startswith(b"\x1f\x8b")
58
58
  else:
59
59
  mime = mimetypes.guess_type(log)
60
- self.assertEqual(mime[1], encoding)
60
+ assert mime[1] == encoding
61
61
 
62
62
  @slow
63
- def testLogToMaster(self):
63
+ def testLogToMaster(self) -> None:
64
64
  toilOutput = subprocess.check_output(
65
65
  [
66
66
  sys.executable,
@@ -74,7 +74,7 @@ class RegularLogTest(ToilTest):
74
74
  )
75
75
  assert helloWorld.childMessage in toilOutput.decode("utf-8")
76
76
 
77
- def testWriteLogs(self):
77
+ def testWriteLogs(self, tmp_path: Path) -> None:
78
78
  subprocess.check_call(
79
79
  [
80
80
  sys.executable,
@@ -83,13 +83,13 @@ class RegularLogTest(ToilTest):
83
83
  "./toilTest",
84
84
  "--clean=always",
85
85
  "--logLevel=debug",
86
- "--writeLogs=%s" % self.tempDir,
86
+ f"--writeLogs={tmp_path}",
87
87
  ]
88
88
  )
89
- self._assertFileTypeExists(self.tempDir, ".log")
89
+ self._assertFileTypeExists(tmp_path, ".log")
90
90
 
91
91
  @slow
92
- def testWriteGzipLogs(self):
92
+ def testWriteGzipLogs(self, tmp_path: Path) -> None:
93
93
  subprocess.check_call(
94
94
  [
95
95
  sys.executable,
@@ -98,13 +98,13 @@ class RegularLogTest(ToilTest):
98
98
  "./toilTest",
99
99
  "--clean=always",
100
100
  "--logLevel=debug",
101
- "--writeLogsGzip=%s" % self.tempDir,
101
+ f"--writeLogsGzip={tmp_path}",
102
102
  ]
103
103
  )
104
- self._assertFileTypeExists(self.tempDir, ".log.gz", "gzip")
104
+ self._assertFileTypeExists(tmp_path, ".log.gz", "gzip")
105
105
 
106
106
  @slow
107
- def testMultipleLogToMaster(self):
107
+ def testMultipleLogToMaster(self) -> None:
108
108
  toilOutput = subprocess.check_output(
109
109
  [
110
110
  sys.executable,
@@ -118,7 +118,7 @@ class RegularLogTest(ToilTest):
118
118
  )
119
119
  assert helloWorld.parentMessage in toilOutput.decode("utf-8")
120
120
 
121
- def testRegularLog(self):
121
+ def testRegularLog(self) -> None:
122
122
  toilOutput = subprocess.check_output(
123
123
  [
124
124
  sys.executable,
@@ -11,8 +11,10 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ from collections.abc import Iterable
14
15
  import importlib
15
16
  import os
17
+ from pathlib import Path
16
18
  import subprocess
17
19
  import sys
18
20
  import tempfile
@@ -22,44 +24,38 @@ from io import BytesIO
22
24
  from textwrap import dedent
23
25
  from unittest.mock import MagicMock, patch
24
26
  from zipfile import ZipFile
27
+ from typing import Optional
25
28
 
26
29
  from toil import inVirtualEnv
27
30
  from toil.resource import ModuleDescriptor, Resource, ResourceException
28
- from toil.test import ToilTest
29
31
  from toil.version import exactPython
30
32
 
33
+ import pytest
31
34
 
32
- @contextmanager
33
- def tempFileContaining(content, suffix=""):
35
+
36
+ def tempFileContaining(directory: Path, content: str, suffix: str = "") -> str:
34
37
  """
35
38
  Write a file with the given contents, and keep it on disk as long as the context is active.
36
39
  :param str content: The contents of the file.
37
40
  :param str suffix: The extension to use for the temporary file.
38
41
  """
39
- fd, path = tempfile.mkstemp(suffix=suffix)
40
- try:
42
+ with (directory / "temp").open("wb") as fd:
41
43
  encoded = content.encode("utf-8")
42
- assert os.write(fd, encoded) == len(encoded)
43
- except:
44
- os.close(fd)
45
- raise
46
- else:
47
- os.close(fd)
48
- yield path
49
- finally:
50
- os.unlink(path)
44
+ assert fd.write(encoded) == len(encoded)
45
+ return str(directory / "temp")
51
46
 
52
47
 
53
- class ResourceTest(ToilTest):
48
+ class TestResource:
54
49
  """Test module descriptors and resources derived from them."""
55
50
 
56
- def testStandAlone(self):
51
+ def testStandAlone(self, tmp_path: Path) -> None:
57
52
  self._testExternal(
58
- moduleName="userScript", pyFiles=("userScript.py", "helper.py")
53
+ tmp_path, moduleName="userScript", pyFiles=("userScript.py", "helper.py")
59
54
  )
60
55
 
61
- def testPackage(self):
56
+ def testPackage(self, tmp_path: Path) -> None:
62
57
  self._testExternal(
58
+ tmp_path,
63
59
  moduleName="foo.userScript",
64
60
  pyFiles=(
65
61
  "foo/__init__.py",
@@ -69,8 +65,9 @@ class ResourceTest(ToilTest):
69
65
  ),
70
66
  )
71
67
 
72
- def testVirtualEnv(self):
68
+ def testVirtualEnv(self, tmp_path: Path) -> None:
73
69
  self._testExternal(
70
+ tmp_path,
74
71
  moduleName="foo.userScript",
75
72
  virtualenv=True,
76
73
  pyFiles=(
@@ -84,34 +81,45 @@ class ResourceTest(ToilTest):
84
81
  ),
85
82
  )
86
83
 
87
- def testStandAloneInPackage(self):
88
- self.assertRaises(
89
- ResourceException,
90
- self._testExternal,
91
- moduleName="userScript",
92
- pyFiles=("__init__.py", "userScript.py", "helper.py"),
93
- )
84
+ def testStandAloneInPackage(self, tmp_path: Path) -> None:
85
+ with pytest.raises(ResourceException):
86
+ self._testExternal(
87
+ tmp_path,
88
+ moduleName="userScript",
89
+ pyFiles=("__init__.py", "userScript.py", "helper.py"),
90
+ )
94
91
 
95
- def _testExternal(self, moduleName, pyFiles, virtualenv=False):
96
- dirPath = self._createTempDir()
92
+ def _testExternal(
93
+ self,
94
+ dirPath: Path,
95
+ moduleName: str,
96
+ pyFiles: Iterable[str],
97
+ virtualenv: bool = False,
98
+ ) -> None:
97
99
  if virtualenv:
98
- self.assertTrue(inVirtualEnv())
100
+ assert inVirtualEnv()
99
101
  # --never-download prevents silent upgrades to pip, wheel and setuptools
100
102
  subprocess.check_call(
101
- ["virtualenv", "--never-download", "--python", exactPython, dirPath]
103
+ [
104
+ "virtualenv",
105
+ "--never-download",
106
+ "--python",
107
+ exactPython,
108
+ str(dirPath),
109
+ ]
102
110
  )
103
- sitePackages = os.path.join(dirPath, "lib", exactPython, "site-packages")
111
+ sitePackages = dirPath / "lib" / exactPython / "site-packages"
104
112
  # tuple assignment is necessary to make this line immediately precede the try:
105
- oldPrefix, sys.prefix, dirPath = sys.prefix, dirPath, sitePackages
113
+ oldPrefix, sys.prefix, dirPath = sys.prefix, str(dirPath), sitePackages
106
114
  else:
107
115
  oldPrefix = None
108
116
  try:
109
117
  for relPath in pyFiles:
110
- path = os.path.join(dirPath, relPath)
111
- os.makedirs(os.path.dirname(path), exist_ok=True)
112
- with open(path, "w") as f:
118
+ path = dirPath / relPath
119
+ path.parent.mkdir(parents=True, exist_ok=True)
120
+ with path.open("w") as f:
113
121
  f.write("pass\n")
114
- sys.path.append(dirPath)
122
+ sys.path.append(str(dirPath))
115
123
  try:
116
124
  userScript = importlib.import_module(moduleName)
117
125
  try:
@@ -124,39 +132,39 @@ class ResourceTest(ToilTest):
124
132
  del userScript
125
133
  while moduleName:
126
134
  del sys.modules[moduleName]
127
- self.assertFalse(moduleName in sys.modules)
135
+ assert moduleName not in sys.modules
128
136
  moduleName = ".".join(moduleName.split(".")[:-1])
129
137
 
130
138
  finally:
131
- sys.path.remove(dirPath)
139
+ sys.path.remove(str(dirPath))
132
140
  finally:
133
141
  if oldPrefix:
134
142
  sys.prefix = oldPrefix
135
143
 
136
- def testBuiltIn(self):
144
+ def testBuiltIn(self) -> None:
137
145
  # Create a ModuleDescriptor for the module containing ModuleDescriptor, i.e. toil.resource
138
146
  module_name = ModuleDescriptor.__module__
139
- self.assertEqual(module_name, "toil.resource")
147
+ assert module_name == "toil.resource"
140
148
  self._test(module_name, shouldBelongToToil=True)
141
149
 
142
150
  def _test(
143
151
  self,
144
- module_name,
145
- shouldBelongToToil=False,
146
- expectedContents=None,
147
- allowExtraContents=True,
148
- ):
152
+ module_name: str,
153
+ shouldBelongToToil: bool = False,
154
+ expectedContents: Optional[Iterable[str]] = None,
155
+ allowExtraContents: bool = True,
156
+ ) -> None:
149
157
  module = ModuleDescriptor.forModule(module_name)
150
158
  # Assert basic attributes and properties
151
- self.assertEqual(module.belongsToToil, shouldBelongToToil)
152
- self.assertEqual(module.name, module_name)
159
+ assert module.belongsToToil == shouldBelongToToil
160
+ assert module.name == module_name
153
161
  if shouldBelongToToil:
154
- self.assertTrue(module.dirPath.endswith("/src"))
162
+ assert module.dirPath.endswith("/src")
155
163
 
156
164
  # Before the module is saved as a resource, localize() and globalize() are identity
157
165
  # methods. This should log.warnings.
158
- self.assertIs(module.localize(), module)
159
- self.assertIs(module.globalize(), module)
166
+ assert module.localize() is module
167
+ assert module.globalize() is module
160
168
  # Create a mock job store ...
161
169
  jobStore = MagicMock()
162
170
  # ... to generate a fake URL for the resource ...
@@ -185,18 +193,18 @@ class ResourceTest(ToilTest):
185
193
  # contents. This is a bit brittle since it assumes that all the data is written in a
186
194
  # single call to write(). If more calls are made we can easily concatenate them.
187
195
  zipFile = file_handle.write.call_args_list[0][0][0]
188
- self.assertTrue(zipFile.startswith(b"PK")) # the magic header for ZIP files
196
+ assert zipFile.startswith(b"PK") # the magic header for ZIP files
189
197
 
190
198
  # Check contents if requested
191
199
  if expectedContents is not None:
192
200
  with ZipFile(BytesIO(zipFile)) as _zipFile:
193
201
  actualContents = set(_zipFile.namelist())
194
202
  if allowExtraContents:
195
- self.assertTrue(actualContents.issuperset(expectedContents))
203
+ assert actualContents.issuperset(expectedContents)
196
204
  else:
197
- self.assertEqual(actualContents, expectedContents)
205
+ assert actualContents == expectedContents
198
206
 
199
- self.assertEqual(resource.url, url)
207
+ assert resource.url == url
200
208
  # Now we're on the worker. Prepare the storage for localized resources
201
209
  Resource.prepareSystem()
202
210
  try:
@@ -206,8 +214,8 @@ class ResourceTest(ToilTest):
206
214
  # original resource. Lookup will also be used when we localize the module that was
207
215
  # originally used to create the resource.
208
216
  localResource = Resource.lookup(module._resourcePath)
209
- self.assertEqual(resource, localResource)
210
- self.assertIsNot(resource, localResource)
217
+ assert resource == localResource
218
+ assert resource is not localResource
211
219
  # Now show that we can localize the module using the registered resource. Set up a mock
212
220
  # urlopen() that yields the zipped tree ...
213
221
  mock_urlopen = MagicMock()
@@ -216,16 +224,16 @@ class ResourceTest(ToilTest):
216
224
  # ... and use it to download and unpack the resource
217
225
  localModule = module.localize()
218
226
  # The name should be equal between original and localized resource ...
219
- self.assertEqual(module.name, localModule.name)
227
+ assert module.name == localModule.name
220
228
  # ... but the directory should be different.
221
- self.assertNotEqual(module.dirPath, localModule.dirPath)
229
+ assert module.dirPath != localModule.dirPath
222
230
  # Show that we can 'undo' localization. This is necessary when the user script's jobs
223
231
  # are invoked on the worker where they generate more child jobs.
224
- self.assertEqual(localModule.globalize(), module)
232
+ assert localModule.globalize() == module
225
233
  finally:
226
234
  Resource.cleanSystem()
227
235
 
228
- def testNonPyStandAlone(self):
236
+ def testNonPyStandAlone(self, tmp_path: Path) -> None:
229
237
  """
230
238
  Asserts that Toil enforces the user script to have a .py or .pyc extension because that's
231
239
  the only way auto-deployment can re-import the module on a worker. See
@@ -234,13 +242,13 @@ class ResourceTest(ToilTest):
234
242
  https://github.com/BD2KGenomics/toil/issues/858
235
243
  """
236
244
 
237
- def script():
245
+ def script() -> None:
238
246
  from configargparse import ArgumentParser
239
247
 
240
248
  from toil.common import Toil
241
249
  from toil.job import Job
242
250
 
243
- def fn():
251
+ def fn() -> None:
244
252
  pass
245
253
 
246
254
  if __name__ == "__main__":
@@ -253,17 +261,15 @@ class ResourceTest(ToilTest):
253
261
 
254
262
  scriptBody = dedent("\n".join(getsource(script).split("\n")[1:]))
255
263
  shebang = "#! %s\n" % sys.executable
256
- with tempFileContaining(shebang + scriptBody) as scriptPath:
257
- self.assertFalse(scriptPath.endswith((".py", ".pyc")))
258
- os.chmod(scriptPath, 0o755)
259
- jobStorePath = scriptPath + ".jobStore"
260
- process = subprocess.Popen(
261
- [scriptPath, jobStorePath], stderr=subprocess.PIPE
262
- )
263
- stdout, stderr = process.communicate()
264
- self.assertTrue(
265
- "The name of a user script/module must end in .py or .pyc."
266
- in stderr.decode("utf-8")
267
- )
268
- self.assertNotEqual(0, process.returncode)
269
- self.assertFalse(os.path.exists(jobStorePath))
264
+ scriptPath = tempFileContaining(tmp_path, shebang + scriptBody)
265
+ assert not scriptPath.endswith((".py", ".pyc"))
266
+ os.chmod(scriptPath, 0o755)
267
+ jobStorePath = scriptPath + ".jobStore"
268
+ process = subprocess.Popen([scriptPath, jobStorePath], stderr=subprocess.PIPE)
269
+ stdout, stderr = process.communicate()
270
+ assert (
271
+ "The name of a user script/module must end in .py or .pyc."
272
+ in stderr.decode("utf-8")
273
+ )
274
+ assert 0 != process.returncode
275
+ assert not os.path.exists(jobStorePath)
@@ -15,41 +15,43 @@
15
15
 
16
16
  import logging
17
17
  import os
18
- import shutil
18
+ from pathlib import Path
19
19
  import signal
20
+ from typing import Optional
20
21
 
21
22
  from toil.common import Toil
22
23
  from toil.exceptions import FailedJobsException
23
24
  from toil.job import Job
24
- from toil.test import ToilTest, slow
25
+ from toil.test import pslow as slow
26
+
27
+ import pytest
25
28
 
26
29
  logger = logging.getLogger(__name__)
27
30
 
28
31
 
29
- class RestartDAGTest(ToilTest):
32
+ class TestRestartDAG:
30
33
  """
31
34
  Tests that restarted job DAGs don't run children of jobs that failed in the first run till the
32
35
  parent completes successfully in the restart.
33
36
  """
34
37
 
35
- def setUp(self):
36
- super().setUp()
37
- self.tempDir = self._createTempDir(purpose="tempDir")
38
- self.testJobStore = self._getTestJobStorePath()
39
-
40
- def tearDown(self):
41
- super().tearDown()
42
- shutil.rmtree(self.testJobStore)
43
-
44
38
  @slow
45
- def testRestartedWorkflowSchedulesCorrectJobsOnFailedParent(self):
46
- self._testRestartedWorkflowSchedulesCorrectJobs("raise")
39
+ @pytest.mark.slow
40
+ def testRestartedWorkflowSchedulesCorrectJobsOnFailedParent(
41
+ self, tmp_path: Path
42
+ ) -> None:
43
+ self._testRestartedWorkflowSchedulesCorrectJobs(tmp_path, "raise")
47
44
 
48
45
  @slow
49
- def testRestartedWorkflowSchedulesCorrectJobsOnKilledParent(self):
50
- self._testRestartedWorkflowSchedulesCorrectJobs("kill")
51
-
52
- def _testRestartedWorkflowSchedulesCorrectJobs(self, failType):
46
+ @pytest.mark.slow
47
+ def testRestartedWorkflowSchedulesCorrectJobsOnKilledParent(
48
+ self, tmp_path: Path
49
+ ) -> None:
50
+ self._testRestartedWorkflowSchedulesCorrectJobs(tmp_path, "kill")
51
+
52
+ def _testRestartedWorkflowSchedulesCorrectJobs(
53
+ self, tmp_path: Path, failType: str
54
+ ) -> None:
53
55
  """
54
56
  Creates a diamond DAG
55
57
  /->passingParent-\
@@ -63,19 +65,19 @@ class RestartDAGTest(ToilTest):
63
65
  :param str failType: Does failingParent fail on an assertionError, or is it killed.
64
66
  """
65
67
  # Specify options
66
- options = Job.Runner.getDefaultOptions(self.testJobStore)
68
+ options = Job.Runner.getDefaultOptions(tmp_path / "jobstore")
67
69
  options.logLevel = "DEBUG"
68
70
  options.retryCount = 0
69
71
  options.clean = "never"
70
72
 
71
- parentFile = os.path.join(self.tempDir, "parent")
72
- childFile = os.path.join(self.tempDir, "child")
73
+ parentFile = tmp_path / "parent"
74
+ childFile = tmp_path / "child"
73
75
 
74
76
  # Make the first job
75
77
  root = Job.wrapJobFn(passingFn)
76
78
  passingParent = Job.wrapJobFn(passingFn)
77
- failingParent = Job.wrapJobFn(failingFn, failType=failType, fileName=parentFile)
78
- child = Job.wrapJobFn(passingFn, fileName=childFile)
79
+ failingParent = Job.wrapJobFn(failingFn, failType=failType, file=parentFile)
80
+ child = Job.wrapJobFn(passingFn, file=childFile)
79
81
 
80
82
  # define the DAG
81
83
  root.addChild(passingParent)
@@ -85,11 +87,12 @@ class RestartDAGTest(ToilTest):
85
87
 
86
88
  failReasons = []
87
89
 
88
- assert not os.path.exists(childFile)
90
+ assert not childFile.exists()
89
91
 
92
+ errorRaised: Optional[BaseException] = None
90
93
  # Run the test
91
94
  for runMode in "start", "restart":
92
- self.errorRaised = None
95
+ errorRaised = None
93
96
  try:
94
97
  with Toil(options) as toil:
95
98
  if runMode == "start":
@@ -98,70 +101,71 @@ class RestartDAGTest(ToilTest):
98
101
  toil.restart()
99
102
  except Exception as e:
100
103
  logger.exception(e)
101
- self.errorRaised = e
104
+ errorRaised = e
102
105
  finally:
103
106
  # The processing of an AssertionError and FailedJobsException is the same so we do
104
107
  # it together in this finally clause.
105
- if self.errorRaised is not None:
106
- if not os.path.exists(parentFile):
108
+ if errorRaised is not None:
109
+ if not parentFile.exists():
107
110
  failReasons.append(
108
111
  'The failing parent file did not exist on toil "%s".'
109
112
  % runMode
110
113
  )
111
- if os.path.exists(childFile):
114
+ if childFile.exists():
112
115
  failReasons.append(
113
116
  "The child file existed. i.e. the child was run on "
114
117
  'toil "%s".' % runMode
115
118
  )
116
- if isinstance(self.errorRaised, FailedJobsException):
117
- if self.errorRaised.numberOfFailedJobs != 3:
119
+ if isinstance(errorRaised, FailedJobsException):
120
+ if errorRaised.numberOfFailedJobs != 3:
118
121
  failReasons.append(
119
122
  'FailedJobsException was raised on toil "%s" but '
120
123
  "the number of failed jobs (%s) was not 3."
121
- % (runMode, self.errorRaised.numberOfFailedJobs)
124
+ % (runMode, errorRaised.numberOfFailedJobs)
122
125
  )
123
- elif isinstance(self.errorRaised, AssertionError):
126
+ elif isinstance(errorRaised, AssertionError):
124
127
  failReasons.append(
125
128
  "Toil raised an AssertionError instead of a "
126
129
  'FailedJobsException on toil "%s".' % runMode
127
130
  )
128
131
  else:
129
- failReasons.append("Toil raised error: %s" % self.errorRaised)
130
- self.errorRaised = None
132
+ failReasons.append("Toil raised error: %s" % errorRaised)
133
+ errorRaised = None
131
134
  options.restart = True
132
135
  else:
133
- self.fail('No errors were raised on toil "%s".' % runMode)
136
+ pytest.fail('No errors were raised on toil "%s".' % runMode)
134
137
  if failReasons:
135
- self.fail(
138
+ pytest.fail(
136
139
  "Test failed for ({}) reasons:\n\t{}".format(
137
140
  len(failReasons), "\n\t".join(failReasons)
138
141
  )
139
142
  )
140
143
 
141
144
 
142
- def passingFn(job, fileName=None):
145
+ def passingFn(job: Job, file: Optional[Path] = None) -> None:
143
146
  """
144
- This function is guaranteed to pass as it does nothing out of the ordinary. If fileName is
145
- provided, it will be created.
147
+ This function is guaranteed to pass as it does nothing out of the ordinary.
148
+
149
+ If "file" is provided, it will be created.
146
150
 
147
- :param str fileName: The name of a file that must be created if provided.
151
+ :param file: The path of a file that must be created if provided.
148
152
  """
149
- if fileName is not None:
153
+ if file is not None:
150
154
  # Emulates system touch.
151
- open(fileName, "w").close()
155
+ file.open("w").close()
152
156
 
153
157
 
154
- def failingFn(job, failType, fileName):
158
+ def failingFn(job: Job, failType: str, file: Path) -> None:
155
159
  """
156
160
  This function is guaranteed to fail via a raised assertion, or an os.kill
157
161
 
158
162
  :param job: Job
159
- :param str failType: 'raise' or 'kill
160
- :param str fileName: The name of a file that must be created.
163
+ :param failType: 'raise' or 'kill
164
+ :param file: The path of a file that must be created.
161
165
  """
162
166
  assert failType in ("raise", "kill")
163
167
  # Use that function to avoid code redundancy
164
- passingFn(job, fileName)
168
+ passingFn(job, file)
165
169
 
166
170
  if failType == "raise":
167
171
  assert False