toil 8.1.0b1__py3-none-any.whl → 9.0.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 (275) 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/registry.py +15 -118
  11. toil/batchSystems/singleMachine.py +1 -1
  12. toil/batchSystems/slurm.py +27 -26
  13. toil/bus.py +5 -3
  14. toil/common.py +59 -12
  15. toil/cwl/cwltoil.py +81 -38
  16. toil/cwl/utils.py +103 -3
  17. toil/job.py +64 -49
  18. toil/jobStores/abstractJobStore.py +35 -239
  19. toil/jobStores/aws/jobStore.py +2 -1
  20. toil/jobStores/fileJobStore.py +27 -2
  21. toil/jobStores/googleJobStore.py +110 -33
  22. toil/leader.py +9 -0
  23. toil/lib/accelerators.py +4 -2
  24. toil/lib/aws/utils.py.orig +504 -0
  25. toil/lib/bioio.py +1 -1
  26. toil/lib/docker.py +252 -91
  27. toil/lib/dockstore.py +11 -3
  28. toil/lib/exceptions.py +5 -3
  29. toil/lib/generatedEC2Lists.py +81 -19
  30. toil/lib/history.py +87 -13
  31. toil/lib/history_submission.py +23 -9
  32. toil/lib/io.py +34 -22
  33. toil/lib/misc.py +8 -2
  34. toil/lib/plugins.py +106 -0
  35. toil/lib/resources.py +2 -1
  36. toil/lib/threading.py +11 -10
  37. toil/lib/url.py +320 -0
  38. toil/options/common.py +8 -0
  39. toil/options/cwl.py +13 -1
  40. toil/options/runner.py +17 -10
  41. toil/options/wdl.py +22 -0
  42. toil/provisioners/aws/awsProvisioner.py +25 -2
  43. toil/server/api_spec/LICENSE +201 -0
  44. toil/server/api_spec/README.rst +5 -0
  45. toil/server/app.py +12 -6
  46. toil/server/cli/wes_cwl_runner.py +3 -2
  47. toil/server/wes/abstract_backend.py +21 -43
  48. toil/server/wes/toil_backend.py +2 -2
  49. toil/test/__init__.py +275 -115
  50. toil/test/batchSystems/batchSystemTest.py +228 -213
  51. toil/test/batchSystems/batch_system_plugin_test.py +7 -0
  52. toil/test/batchSystems/test_slurm.py +27 -0
  53. toil/test/cactus/pestis.tar.gz +0 -0
  54. toil/test/conftest.py +7 -0
  55. toil/test/cwl/2.fasta +11 -0
  56. toil/test/cwl/2.fastq +12 -0
  57. toil/test/cwl/conftest.py +1 -1
  58. toil/test/cwl/cwlTest.py +1175 -870
  59. toil/test/cwl/directory/directory/file.txt +15 -0
  60. toil/test/cwl/download_directory_file.json +4 -0
  61. toil/test/cwl/download_directory_s3.json +4 -0
  62. toil/test/cwl/download_file.json +6 -0
  63. toil/test/cwl/download_http.json +6 -0
  64. toil/test/cwl/download_https.json +6 -0
  65. toil/test/cwl/download_s3.json +6 -0
  66. toil/test/cwl/download_subdirectory_file.json +5 -0
  67. toil/test/cwl/download_subdirectory_s3.json +5 -0
  68. toil/test/cwl/empty.json +1 -0
  69. toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
  70. toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
  71. toil/test/cwl/optional-file-exists.json +6 -0
  72. toil/test/cwl/optional-file-missing.json +6 -0
  73. toil/test/cwl/preemptible_expression.json +1 -0
  74. toil/test/cwl/revsort-job-missing.json +6 -0
  75. toil/test/cwl/revsort-job.json +6 -0
  76. toil/test/cwl/s3_secondary_file.json +16 -0
  77. toil/test/cwl/seqtk_seq_job.json +6 -0
  78. toil/test/cwl/stream.json +6 -0
  79. toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
  80. toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
  81. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
  82. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
  83. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
  84. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
  85. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
  86. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
  87. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
  88. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
  89. toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
  90. toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
  91. toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
  92. toil/test/cwl/whale.txt +16 -0
  93. toil/test/docs/scripts/example_alwaysfail.py +38 -0
  94. toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
  95. toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
  96. toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
  97. toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
  98. toil/test/docs/scripts/tutorial_arguments.py +23 -0
  99. toil/test/docs/scripts/tutorial_debugging.patch +12 -0
  100. toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
  101. toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
  102. toil/test/docs/scripts/tutorial_docker.py +20 -0
  103. toil/test/docs/scripts/tutorial_dynamic.py +24 -0
  104. toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
  105. toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
  106. toil/test/docs/scripts/tutorial_helloworld.py +15 -0
  107. toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
  108. toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
  109. toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
  110. toil/test/docs/scripts/tutorial_managing.py +29 -0
  111. toil/test/docs/scripts/tutorial_managing2.py +56 -0
  112. toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
  113. toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
  114. toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
  115. toil/test/docs/scripts/tutorial_promises.py +25 -0
  116. toil/test/docs/scripts/tutorial_promises2.py +30 -0
  117. toil/test/docs/scripts/tutorial_quickstart.py +22 -0
  118. toil/test/docs/scripts/tutorial_requirements.py +44 -0
  119. toil/test/docs/scripts/tutorial_services.py +45 -0
  120. toil/test/docs/scripts/tutorial_staging.py +45 -0
  121. toil/test/docs/scripts/tutorial_stats.py +64 -0
  122. toil/test/docs/scriptsTest.py +2 -1
  123. toil/test/lib/aws/test_iam.py +3 -1
  124. toil/test/lib/dockerTest.py +205 -122
  125. toil/test/lib/test_history.py +101 -77
  126. toil/test/lib/test_url.py +69 -0
  127. toil/test/lib/url_plugin_test.py +105 -0
  128. toil/test/provisioners/aws/awsProvisionerTest.py +13 -10
  129. toil/test/provisioners/clusterTest.py +17 -4
  130. toil/test/provisioners/gceProvisionerTest.py +17 -15
  131. toil/test/server/serverTest.py +78 -36
  132. toil/test/sort/sort.py +4 -1
  133. toil/test/src/busTest.py +17 -17
  134. toil/test/src/deferredFunctionTest.py +145 -132
  135. toil/test/src/importExportFileTest.py +71 -63
  136. toil/test/src/jobEncapsulationTest.py +27 -28
  137. toil/test/src/jobServiceTest.py +149 -133
  138. toil/test/src/jobTest.py +219 -211
  139. toil/test/src/miscTests.py +66 -60
  140. toil/test/src/promisedRequirementTest.py +163 -169
  141. toil/test/src/regularLogTest.py +24 -24
  142. toil/test/src/resourceTest.py +82 -76
  143. toil/test/src/restartDAGTest.py +51 -47
  144. toil/test/src/resumabilityTest.py +24 -19
  145. toil/test/src/retainTempDirTest.py +60 -57
  146. toil/test/src/systemTest.py +17 -13
  147. toil/test/src/threadingTest.py +29 -32
  148. toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
  149. toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
  150. toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
  151. toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
  152. toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
  153. toil/test/utils/toilDebugTest.py +117 -102
  154. toil/test/utils/toilKillTest.py +54 -53
  155. toil/test/utils/utilsTest.py +303 -229
  156. toil/test/wdl/lint_error.wdl +9 -0
  157. toil/test/wdl/md5sum/empty_file.json +1 -0
  158. toil/test/wdl/md5sum/md5sum-gs.json +1 -0
  159. toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
  160. toil/test/wdl/md5sum/md5sum.input +1 -0
  161. toil/test/wdl/md5sum/md5sum.json +1 -0
  162. toil/test/wdl/md5sum/md5sum.wdl +25 -0
  163. toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
  164. toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
  165. toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
  166. toil/test/wdl/standard_library/as_map.json +16 -0
  167. toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
  168. toil/test/wdl/standard_library/as_pairs.json +7 -0
  169. toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
  170. toil/test/wdl/standard_library/ceil.json +3 -0
  171. toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
  172. toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
  173. toil/test/wdl/standard_library/collect_by_key.json +1 -0
  174. toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
  175. toil/test/wdl/standard_library/cross.json +11 -0
  176. toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
  177. toil/test/wdl/standard_library/flatten.json +7 -0
  178. toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
  179. toil/test/wdl/standard_library/floor.json +3 -0
  180. toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
  181. toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
  182. toil/test/wdl/standard_library/keys.json +8 -0
  183. toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
  184. toil/test/wdl/standard_library/length.json +7 -0
  185. toil/test/wdl/standard_library/length_as_input.wdl +16 -0
  186. toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
  187. toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
  188. toil/test/wdl/standard_library/length_invalid.json +3 -0
  189. toil/test/wdl/standard_library/range.json +3 -0
  190. toil/test/wdl/standard_library/range_0.json +3 -0
  191. toil/test/wdl/standard_library/range_as_input.wdl +17 -0
  192. toil/test/wdl/standard_library/range_invalid.json +3 -0
  193. toil/test/wdl/standard_library/read_boolean.json +3 -0
  194. toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
  195. toil/test/wdl/standard_library/read_float.json +3 -0
  196. toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
  197. toil/test/wdl/standard_library/read_int.json +3 -0
  198. toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
  199. toil/test/wdl/standard_library/read_json.json +3 -0
  200. toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
  201. toil/test/wdl/standard_library/read_lines.json +3 -0
  202. toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
  203. toil/test/wdl/standard_library/read_map.json +3 -0
  204. toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
  205. toil/test/wdl/standard_library/read_string.json +3 -0
  206. toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
  207. toil/test/wdl/standard_library/read_tsv.json +3 -0
  208. toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
  209. toil/test/wdl/standard_library/round.json +3 -0
  210. toil/test/wdl/standard_library/round_as_command.wdl +16 -0
  211. toil/test/wdl/standard_library/round_as_input.wdl +16 -0
  212. toil/test/wdl/standard_library/size.json +3 -0
  213. toil/test/wdl/standard_library/size_as_command.wdl +17 -0
  214. toil/test/wdl/standard_library/size_as_output.wdl +36 -0
  215. toil/test/wdl/standard_library/stderr.json +3 -0
  216. toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
  217. toil/test/wdl/standard_library/stdout.json +3 -0
  218. toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
  219. toil/test/wdl/standard_library/sub.json +3 -0
  220. toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
  221. toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
  222. toil/test/wdl/standard_library/transpose.json +6 -0
  223. toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
  224. toil/test/wdl/standard_library/write_json.json +6 -0
  225. toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
  226. toil/test/wdl/standard_library/write_lines.json +7 -0
  227. toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
  228. toil/test/wdl/standard_library/write_map.json +6 -0
  229. toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
  230. toil/test/wdl/standard_library/write_tsv.json +6 -0
  231. toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
  232. toil/test/wdl/standard_library/zip.json +12 -0
  233. toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
  234. toil/test/wdl/test.csv +3 -0
  235. toil/test/wdl/test.tsv +3 -0
  236. toil/test/wdl/testfiles/croo.wdl +38 -0
  237. toil/test/wdl/testfiles/drop_files.wdl +62 -0
  238. toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
  239. toil/test/wdl/testfiles/empty.txt +0 -0
  240. toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
  241. toil/test/wdl/testfiles/random.wdl +66 -0
  242. toil/test/wdl/testfiles/read_file.wdl +18 -0
  243. toil/test/wdl/testfiles/string_file_coercion.json +1 -0
  244. toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
  245. toil/test/wdl/testfiles/test.json +4 -0
  246. toil/test/wdl/testfiles/test_boolean.txt +1 -0
  247. toil/test/wdl/testfiles/test_float.txt +1 -0
  248. toil/test/wdl/testfiles/test_int.txt +1 -0
  249. toil/test/wdl/testfiles/test_lines.txt +5 -0
  250. toil/test/wdl/testfiles/test_map.txt +2 -0
  251. toil/test/wdl/testfiles/test_string.txt +1 -0
  252. toil/test/wdl/testfiles/url_to_file.wdl +13 -0
  253. toil/test/wdl/testfiles/url_to_optional_file.wdl +14 -0
  254. toil/test/wdl/testfiles/vocab.json +1 -0
  255. toil/test/wdl/testfiles/vocab.wdl +66 -0
  256. toil/test/wdl/testfiles/wait.wdl +34 -0
  257. toil/test/wdl/wdl_specification/type_pair.json +23 -0
  258. toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
  259. toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
  260. toil/test/wdl/wdl_specification/v1_spec.json +1 -0
  261. toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
  262. toil/test/wdl/wdltoil_test.py +751 -529
  263. toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
  264. toil/utils/toilSshCluster.py +23 -0
  265. toil/utils/toilUpdateEC2Instances.py +1 -0
  266. toil/version.py +5 -5
  267. toil/wdl/wdltoil.py +518 -437
  268. toil/worker.py +11 -6
  269. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/METADATA +25 -24
  270. toil-9.0.0.dist-info/RECORD +444 -0
  271. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/WHEEL +1 -1
  272. toil-8.1.0b1.dist-info/RECORD +0 -259
  273. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/entry_points.txt +0 -0
  274. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info/licenses}/LICENSE +0 -0
  275. {toil-8.1.0b1.dist-info → toil-9.0.0.dist-info}/top_level.txt +0 -0
toil/test/__init__.py CHANGED
@@ -28,8 +28,10 @@ import uuid
28
28
  import zoneinfo
29
29
  from abc import ABCMeta, abstractmethod
30
30
  from collections.abc import Generator
31
- from contextlib import contextmanager
31
+ from contextlib import AbstractContextManager, contextmanager
32
+ from importlib.resources import as_file, files
32
33
  from inspect import getsource
34
+ from pathlib import Path
33
35
  from shutil import which
34
36
  from tempfile import mkstemp
35
37
  from textwrap import dedent
@@ -38,7 +40,10 @@ from unittest.util import strclass
38
40
  from urllib.error import HTTPError, URLError
39
41
  from urllib.request import urlopen
40
42
 
41
- from toil import ApplianceImageNotFound, applianceSelf, toilPackageDirPath
43
+ import pytest
44
+ from typing_extensions import Self
45
+
46
+ from toil import ApplianceImageNotFound, applianceSelf
42
47
  from toil.lib.accelerators import (
43
48
  have_working_nvidia_docker_runtime,
44
49
  have_working_nvidia_smi,
@@ -49,14 +54,41 @@ from toil.lib.memoize import memoize
49
54
  from toil.lib.threading import ExceptionalThread, cpu_count
50
55
  from toil.version import distVersion
51
56
 
57
+ try:
58
+ from botocore.exceptions import ProxyConnectionError
59
+ except ImportError:
60
+
61
+ class ProxyConnectionError(BaseException): # type: ignore[no-redef]
62
+ ...
63
+
52
64
  logger = logging.getLogger(__name__)
53
65
 
54
66
 
67
+ @contextmanager
68
+ def _fallback_get_data(filename: str) -> Generator[Path]:
69
+ try:
70
+ yield Path(os.path.dirname(__file__)) / ".." / ".." / ".." / filename
71
+ finally:
72
+ pass # no cleanup needed
73
+
74
+
75
+ def get_data(filename: str) -> AbstractContextManager[Path]:
76
+ """Returns an absolute path for a file from this package."""
77
+ # normalizing path depending on OS or else it will cause problem when joining path
78
+ filename = os.path.normpath(filename)
79
+ try:
80
+ return as_file(files("toil") / filename)
81
+ except ModuleNotFoundError:
82
+ pass
83
+ return _fallback_get_data(filename)
84
+
85
+
86
+ @pytest.mark.usefixtures("rootpath")
55
87
  class ToilTest(unittest.TestCase):
56
88
  """
57
- A common base class for Toil tests.
89
+ Legacy common base class for Toil tests.
58
90
 
59
- Please have every test case directly or indirectly inherit this one.
91
+ New tests should be made in the 'pytest' style and not use this class.
60
92
 
61
93
  When running tests you may optionally set the TOIL_TEST_TEMP environment variable
62
94
  to the path of a directory where you want temporary test files be placed. The
@@ -68,6 +100,7 @@ class ToilTest(unittest.TestCase):
68
100
  Otherwise, left-over files will not be removed.
69
101
  """
70
102
 
103
+ _rootpath: Path
71
104
  _tempBaseDir: Optional[str] = None
72
105
  _tempDirs: list[str] = []
73
106
 
@@ -84,9 +117,7 @@ class ToilTest(unittest.TestCase):
84
117
  super().setUpClass()
85
118
  tempBaseDir = os.environ.get("TOIL_TEST_TEMP", None)
86
119
  if tempBaseDir is not None and not os.path.isabs(tempBaseDir):
87
- tempBaseDir = os.path.abspath(
88
- os.path.join(cls._projectRootPath(), tempBaseDir)
89
- )
120
+ tempBaseDir = os.path.abspath(cls._rootpath / tempBaseDir)
90
121
  os.makedirs(tempBaseDir, exist_ok=True)
91
122
  cls._tempBaseDir = tempBaseDir
92
123
 
@@ -149,28 +180,6 @@ class ToilTest(unittest.TestCase):
149
180
  assert region
150
181
  return region.group(1)
151
182
 
152
- @classmethod
153
- def _getUtilScriptPath(cls, script_name: str) -> str:
154
- return os.path.join(toilPackageDirPath(), "utils", script_name + ".py")
155
-
156
- @classmethod
157
- def _projectRootPath(cls) -> str:
158
- """
159
- Return the path to the project root.
160
-
161
- i.e. the directory that typically contains the .git and src subdirectories.
162
- This method has limited utility. It only works if in "develop"
163
- mode, since it assumes the existence of a src subdirectory which, in a regular install
164
- wouldn't exist. Then again, in that mode project root has no meaning anyways.
165
- """
166
- assert re.search(r"__init__\.pyc?$", __file__)
167
- projectRootPath = os.path.dirname(os.path.abspath(__file__))
168
- packageComponents = __name__.split(".")
169
- expectedSuffix = os.path.join("src", *packageComponents)
170
- assert projectRootPath.endswith(expectedSuffix)
171
- projectRootPath = projectRootPath[: -len(expectedSuffix)]
172
- return projectRootPath
173
-
174
183
  def _createTempDir(self, purpose: Optional[str] = None) -> str:
175
184
  return self._createTempDirEx(self._testMethodName, purpose)
176
185
 
@@ -198,57 +207,6 @@ class ToilTest(unittest.TestCase):
198
207
  os.rmdir(path)
199
208
  return path
200
209
 
201
- @classmethod
202
- def _getSourceDistribution(cls) -> str:
203
- """
204
- Find the sdist tarball for this project and return the path to it.
205
-
206
- Also assert that the sdist is up-to date
207
- """
208
- sdistPath = os.path.join(
209
- cls._projectRootPath(), "dist", "toil-%s.tar.gz" % distVersion
210
- )
211
- assert os.path.isfile(sdistPath), (
212
- "Can't find Toil source distribution at %s. Run 'make sdist'." % sdistPath
213
- )
214
- excluded = set(
215
- cast(
216
- str,
217
- cls._run(
218
- "git",
219
- "ls-files",
220
- "--others",
221
- "-i",
222
- "--exclude-standard",
223
- capture=True,
224
- cwd=cls._projectRootPath(),
225
- ),
226
- ).splitlines()
227
- )
228
- dirty = cast(
229
- str,
230
- cls._run(
231
- "find",
232
- "src",
233
- "-type",
234
- "f",
235
- "-newer",
236
- sdistPath,
237
- capture=True,
238
- cwd=cls._projectRootPath(),
239
- ),
240
- ).splitlines()
241
- assert all(path.startswith("src") for path in dirty)
242
- dirty_set = set(dirty)
243
- dirty_set.difference_update(excluded)
244
- assert (
245
- not dirty_set
246
- ), "Run 'make clean_sdist sdist'. Files newer than {}: {!r}".format(
247
- sdistPath,
248
- list(dirty_set),
249
- )
250
- return sdistPath
251
-
252
210
  @classmethod
253
211
  def _run(cls, command: str, *args: str, **kwargs: Any) -> Optional[str]:
254
212
  """
@@ -362,6 +320,27 @@ def needs_rsync3(test_item: MT) -> MT:
362
320
  return test_item
363
321
 
364
322
 
323
+ def _has_rsync3() -> bool:
324
+ try:
325
+ versionInfo = subprocess.check_output(["rsync", "--version"]).decode("utf-8")
326
+ if int(versionInfo.split()[2].split(".")[0]) < 3:
327
+ return False
328
+ except subprocess.CalledProcessError:
329
+ return False
330
+ except ValueError:
331
+ # Don't have an int where we looked
332
+ return False
333
+ except IndexError:
334
+ # Don't have the field to look in
335
+ return False
336
+ return True
337
+
338
+
339
+ pneeds_rsync3 = pytest.mark.skipif(
340
+ not _has_rsync3(), reason="This test depends on rsync version 3.0.0+."
341
+ )
342
+
343
+
365
344
  def needs_online(test_item: MT) -> MT:
366
345
  """Use as a decorator before test classes or methods to run only if we are meant to talk to the Internet."""
367
346
  test_item = _mark_test("online", test_item)
@@ -370,10 +349,17 @@ def needs_online(test_item: MT) -> MT:
370
349
  return test_item
371
350
 
372
351
 
352
+ def _skip_online() -> bool:
353
+ return os.getenv("TOIL_SKIP_ONLINE", "").lower() == "true"
354
+
355
+
356
+ pneeds_online = pytest.mark.skipif(_skip_online(), reason="Skipping online test.")
357
+
358
+
373
359
  def needs_aws_s3(test_item: MT) -> MT:
374
360
  """Use as a decorator before test classes or methods to run only if AWS S3 is usable."""
375
361
  # TODO: we just check for generic access to the AWS account
376
- test_item = _mark_test("aws-s3", needs_online(test_item))
362
+ test_item = _mark_test("aws_s3", needs_online(test_item))
377
363
  try:
378
364
  from boto3 import Session
379
365
 
@@ -383,6 +369,10 @@ def needs_aws_s3(test_item: MT) -> MT:
383
369
  return unittest.skip("Install Toil with the 'aws' extra to include this test.")(
384
370
  test_item
385
371
  )
372
+ except ProxyConnectionError as e:
373
+ return unittest.skip(f"Proxy error: {e}, skipping this test.")(
374
+ test_item
375
+ )
386
376
  from toil.lib.aws import running_on_ec2
387
377
 
388
378
  if not (
@@ -396,23 +386,55 @@ def needs_aws_s3(test_item: MT) -> MT:
396
386
  return test_item
397
387
 
398
388
 
389
+ def _aws_s3_avail() -> bool:
390
+ """Use as a decorator before test classes or methods to run only if AWS S3 is usable."""
391
+ try:
392
+ from boto3 import Session
393
+
394
+ session = Session()
395
+ boto3_credentials = session.get_credentials()
396
+ except ImportError:
397
+ return False
398
+ from toil.lib.aws import running_on_ec2
399
+
400
+ if not (
401
+ boto3_credentials
402
+ or os.path.exists(os.path.expanduser("~/.aws/credentials"))
403
+ or running_on_ec2()
404
+ ):
405
+ return False
406
+ return True
407
+
408
+
409
+ pneeds_aws_s3 = pytest.mark.skipif(
410
+ _skip_online() or not _aws_s3_avail(),
411
+ reason="Install Toil with the 'aws' extra or configure AWS credentials to include this test.",
412
+ )
413
+
414
+
399
415
  def needs_aws_ec2(test_item: MT) -> MT:
400
416
  """Use as a decorator before test classes or methods to run only if AWS EC2 is usable."""
401
417
  # Assume we need S3 as well as EC2
402
- test_item = _mark_test("aws-ec2", needs_aws_s3(test_item))
418
+ test_item = _mark_test("aws_ec2", needs_aws_s3(test_item))
403
419
  # In addition to S3 we also need an SSH key to deploy with.
404
420
  # TODO: We assume that if this is set we have EC2 access.
405
421
  test_item = needs_env_var("TOIL_AWS_KEYNAME", "an AWS-stored SSH key")(test_item)
406
422
  return test_item
407
423
 
408
424
 
425
+ pneeds_aws_ec2 = pytest.mark.skipif(
426
+ _skip_online() or not _aws_s3_avail() or not os.getenv("TOIL_AWS_KEYNAME"),
427
+ reason="Set 'TOIL_AWS_KEYNAME' to an AWS-stored SSH key to run this test",
428
+ )
429
+
430
+
409
431
  def needs_aws_batch(test_item: MT) -> MT:
410
432
  """
411
433
  Use as a decorator before test classes or methods to run only if AWS Batch
412
434
  is usable.
413
435
  """
414
436
  # Assume we need S3 as well as Batch
415
- test_item = _mark_test("aws-batch", needs_aws_s3(test_item))
437
+ test_item = _mark_test("aws_batch", needs_aws_s3(test_item))
416
438
  # Assume we have Batch if the user has set these variables.
417
439
  test_item = needs_env_var("TOIL_AWS_BATCH_QUEUE", "an AWS Batch queue name or ARN")(
418
440
  test_item
@@ -443,9 +465,9 @@ def needs_google_storage(test_item: MT) -> MT:
443
465
  Cloud is installed and we ought to be able to access public Google Storage
444
466
  URIs.
445
467
  """
446
- test_item = _mark_test("google-storage", needs_online(test_item))
468
+ test_item = _mark_test("google_storage", needs_online(test_item))
447
469
  try:
448
- from google.cloud import storage # noqa
470
+ import google.clould.storage # type: ignore[import-untyped]
449
471
  except ImportError:
450
472
  return unittest.skip(
451
473
  "Install Toil with the 'google' extra to include this test."
@@ -458,7 +480,7 @@ def needs_google_project(test_item: MT) -> MT:
458
480
  """
459
481
  Use as a decorator before test classes or methods to run only if we have a Google Cloud project set.
460
482
  """
461
- test_item = _mark_test("google-project", needs_online(test_item))
483
+ test_item = _mark_test("google_project", needs_online(test_item))
462
484
  test_item = needs_env_var("TOIL_GOOGLE_PROJECTID", "a Google project ID")(test_item)
463
485
  return test_item
464
486
 
@@ -471,6 +493,11 @@ def needs_gridengine(test_item: MT) -> MT:
471
493
  return unittest.skip("Install GridEngine to include this test.")(test_item)
472
494
 
473
495
 
496
+ pneeds_gridengine = pytest.mark.skipif(
497
+ not which("qhost"), reason="Install GridEngine to include this test."
498
+ )
499
+
500
+
474
501
  def needs_torque(test_item: MT) -> MT:
475
502
  """Use as a decorator before test classes or methods to run only if PBS/Torque is installed."""
476
503
  test_item = _mark_test("torque", test_item)
@@ -479,6 +506,11 @@ def needs_torque(test_item: MT) -> MT:
479
506
  return unittest.skip("Install PBS/Torque to include this test.")(test_item)
480
507
 
481
508
 
509
+ pneeds_torque = pytest.mark.skipif(
510
+ not which("pbsnodes"), reason="Install PBS/Torque to include this test."
511
+ )
512
+
513
+
482
514
  def needs_kubernetes_installed(test_item: MT) -> MT:
483
515
  """Use as a decorator before test classes or methods to run only if Kubernetes is installed."""
484
516
  test_item = _mark_test("kubernetes", test_item)
@@ -493,28 +525,40 @@ def needs_kubernetes_installed(test_item: MT) -> MT:
493
525
  return test_item
494
526
 
495
527
 
496
- def needs_kubernetes(test_item: MT) -> MT:
497
- """Use as a decorator before test classes or methods to run only if Kubernetes is installed and configured."""
498
- test_item = needs_kubernetes_installed(needs_online(test_item))
528
+ def _is_kubernetes_installed_and_configured() -> bool:
499
529
  try:
500
530
  import kubernetes
501
531
 
502
532
  try:
503
- kubernetes.config.load_kube_config()
504
- except kubernetes.config.ConfigException:
533
+ kubernetes.config.load_kube_config() # type: ignore[attr-defined]
534
+ except kubernetes.config.ConfigException: # type: ignore[attr-defined]
505
535
  try:
506
- kubernetes.config.load_incluster_config()
507
- except kubernetes.config.ConfigException:
508
- return unittest.skip(
509
- "Configure Kubernetes (~/.kube/config, $KUBECONFIG, "
510
- "or current pod) to include this test."
511
- )(test_item)
536
+ kubernetes.config.load_incluster_config() # type: ignore[attr-defined]
537
+ except kubernetes.config.ConfigException: # type: ignore[attr-defined]
538
+ return False
512
539
  except ImportError:
513
- # We should already be skipping this test
514
- pass
540
+ return False
541
+ return True
542
+
543
+
544
+ def needs_kubernetes(test_item: MT) -> MT:
545
+ """Use as a decorator before test classes or methods to run only if Kubernetes is installed and configured."""
546
+ test_item = needs_kubernetes_installed(needs_online(test_item))
547
+ if not _is_kubernetes_installed_and_configured():
548
+ return unittest.skip(
549
+ "Configure Kubernetes (~/.kube/config, $KUBECONFIG, "
550
+ "or current pod) to include this test."
551
+ )(test_item)
515
552
  return test_item
516
553
 
517
554
 
555
+ pneeds_kubernetes = pytest.mark.skipif(
556
+ _skip_online() or not _is_kubernetes_installed_and_configured(),
557
+ reason="Configure Kubernetes (~/.kube/config, $KUBECONFIG, "
558
+ "or current pod) to include this test.",
559
+ )
560
+
561
+
518
562
  def needs_mesos(test_item: MT) -> MT:
519
563
  """Use as a decorator before test classes or methods to run only if Mesos is installed."""
520
564
  test_item = _mark_test("mesos", test_item)
@@ -524,7 +568,16 @@ def needs_mesos(test_item: MT) -> MT:
524
568
  )(test_item)
525
569
  try:
526
570
  import psutil # noqa
527
- import pymesos # noqa
571
+ # If pymesos is installed, because it isn't typed, mypy sees an
572
+ # import-untyped error here.
573
+ #
574
+ # If pymesos *isn't* installed, mypy sees an import-not-found error
575
+ # here.
576
+ #
577
+ # If we ignore mypy errors by name, we'll get a mypy error for ignoring
578
+ # whichever one isn't actually occurring on the current system. So we
579
+ # need a blanket ignore here, or a much cleverer mypy.
580
+ import pymesos # type: ignore
528
581
  except ImportError:
529
582
  return unittest.skip(
530
583
  "Install Mesos (and Toil with the 'mesos' extra) to include this test."
@@ -532,6 +585,23 @@ def needs_mesos(test_item: MT) -> MT:
532
585
  return test_item
533
586
 
534
587
 
588
+ def _mesos_avail() -> bool:
589
+ if not (which("mesos-master") or which("mesos-agent")):
590
+ return False
591
+ try:
592
+ import psutil
593
+ import pymesos
594
+ except ImportError:
595
+ return False
596
+ return True
597
+
598
+
599
+ pneeds_mesos = pytest.mark.skipif(
600
+ not _mesos_avail(),
601
+ reason="Install Mesos (and Toil with the 'mesos' extra) to include this test.",
602
+ )
603
+
604
+
535
605
  def needs_slurm(test_item: MT) -> MT:
536
606
  """Use as a decorator before test classes or methods to run only if Slurm is installed."""
537
607
  test_item = _mark_test("slurm", test_item)
@@ -540,11 +610,16 @@ def needs_slurm(test_item: MT) -> MT:
540
610
  return unittest.skip("Install Slurm to include this test.")(test_item)
541
611
 
542
612
 
613
+ pneeds_slurm = pytest.mark.skipif(
614
+ not which("squeue"), reason="Install Slurm to include this test."
615
+ )
616
+
617
+
543
618
  def needs_htcondor(test_item: MT) -> MT:
544
619
  """Use a decorator before test classes or methods to run only if the HTCondor is installed."""
545
620
  test_item = _mark_test("htcondor", test_item)
546
621
  try:
547
- import htcondor
622
+ import htcondor # type: ignore
548
623
 
549
624
  htcondor.Collector(os.getenv("TOIL_HTCONDOR_COLLECTOR")).query(
550
625
  constraint="False"
@@ -576,6 +651,11 @@ def needs_lsf(test_item: MT) -> MT:
576
651
  return unittest.skip("Install LSF to include this test.")(test_item)
577
652
 
578
653
 
654
+ pneeds_lsf = pytest.mark.skipif(
655
+ not which("bsub"), reason="Install LSF to include this test."
656
+ )
657
+
658
+
579
659
  def needs_java(test_item: MT) -> MT:
580
660
  """Use as a test decorator to run only if java is installed."""
581
661
  test_item = _mark_test("java", test_item)
@@ -599,6 +679,14 @@ def needs_docker(test_item: MT) -> MT:
599
679
  return unittest.skip("Install docker to include this test.")(test_item)
600
680
 
601
681
 
682
+ pneeds_docker = pytest.mark.skipif(
683
+ _skip_online()
684
+ or os.getenv("TOIL_SKIP_DOCKER", "").lower() == "true"
685
+ or not which("docker"),
686
+ reason="Requested to skip docker test or docker is not installed.",
687
+ )
688
+
689
+
602
690
  def needs_singularity(test_item: MT) -> MT:
603
691
  """
604
692
  Use as a decorator before test classes or methods to only run them if
@@ -641,6 +729,12 @@ def needs_local_cuda(test_item: MT) -> MT:
641
729
  )(test_item)
642
730
 
643
731
 
732
+ pneeds_local_cuda = pytest.mark.skipif(
733
+ not have_working_nvidia_smi(),
734
+ reason="Install nvidia-smi, an nvidia proprietary driver, and a CUDA-capable nvidia GPU to include this test.",
735
+ )
736
+
737
+
644
738
  def needs_docker_cuda(test_item: MT) -> MT:
645
739
  """
646
740
  Use as a decorator before test classes or methods to only run them if
@@ -655,6 +749,12 @@ def needs_docker_cuda(test_item: MT) -> MT:
655
749
  )(test_item)
656
750
 
657
751
 
752
+ pneeds_docker_cuda = pytest.mark.skipif(
753
+ _skip_online() or not have_working_nvidia_docker_runtime(),
754
+ reason="Install nvidia-container-runtime on your Docker server and configure an 'nvidia' runtime to include this test.",
755
+ )
756
+
757
+
658
758
  def needs_encryption(test_item: MT) -> MT:
659
759
  """
660
760
  Use as a decorator before test classes or methods to only run them if PyNaCl is installed
@@ -689,21 +789,46 @@ def needs_cwl(test_item: MT) -> MT:
689
789
  return test_item
690
790
 
691
791
 
792
+ def _cwl_available() -> bool:
793
+ try:
794
+ import cwltool
795
+ except ImportError:
796
+ return False
797
+ return True
798
+
799
+
800
+ pneeds_cwl = pytest.mark.skipif(
801
+ not _cwl_available(),
802
+ reason="Install Toil with the 'cwl' extra to include this test.",
803
+ )
804
+
805
+
806
+ def _wdl_available() -> bool:
807
+ try:
808
+ # noinspection PyUnresolvedReferences
809
+ import WDL # noqa
810
+ except ImportError:
811
+ return False
812
+ return True
813
+
814
+
692
815
  def needs_wdl(test_item: MT) -> MT:
693
816
  """
694
817
  Use as a decorator before test classes or methods to only run them if miniwdl is installed
695
818
  and configured.
696
819
  """
697
820
  test_item = _mark_test("wdl", test_item)
698
- try:
699
- # noinspection PyUnresolvedReferences
700
- import WDL # noqa
701
- except ImportError:
702
- return unittest.skip("Install Toil with the 'wdl' extra to include this test.")(
703
- test_item
704
- )
705
- else:
821
+ if _wdl_available():
706
822
  return test_item
823
+ return unittest.skip("Install Toil with the 'wdl' extra to include this test.")(
824
+ test_item
825
+ )
826
+
827
+
828
+ pneeds_wdl = pytest.mark.skipif(
829
+ not _wdl_available(),
830
+ reason="Install Toil with the 'wdl' extra to include this test.",
831
+ )
707
832
 
708
833
 
709
834
  def needs_server(test_item: MT) -> MT:
@@ -713,7 +838,7 @@ def needs_server(test_item: MT) -> MT:
713
838
  test_item = _mark_test("server_mode", test_item)
714
839
  try:
715
840
  # noinspection PyUnresolvedReferences
716
- import connexion
841
+ import connexion # type: ignore[import-untyped]
717
842
 
718
843
  print(connexion.__file__) # keep this import from being removed.
719
844
  except ImportError:
@@ -756,6 +881,23 @@ def needs_wes_server(test_item: MT) -> MT:
756
881
  return test_item
757
882
 
758
883
 
884
+ def _is_wes_server_avail() -> bool:
885
+ wes_url = os.environ.get("TOIL_WES_ENDPOINT")
886
+ if not wes_url:
887
+ return False
888
+ try:
889
+ urlopen(f"{wes_url}/ga4gh/wes/v1/service-info")
890
+ return True
891
+ except (HTTPError, URLError):
892
+ return False
893
+
894
+
895
+ pneeds_wes_server = pytest.mark.skipif(
896
+ _skip_online() or not _is_wes_server_avail(),
897
+ reason="Set TOIL_WES_ENDPOINT, or run a WES server at that location to include this test.",
898
+ )
899
+
900
+
759
901
  def needs_local_appliance(test_item: MT) -> MT:
760
902
  """
761
903
  Use as a decorator before test classes or methods to only run them if
@@ -833,6 +975,13 @@ def integrative(test_item: MT) -> MT:
833
975
  )(test_item)
834
976
 
835
977
 
978
+ pintegrative = pytest.mark.skipif(
979
+ os.getenv("TOIL_TEST_INTEGRATIVE", "").lower() != "true",
980
+ reason="Set TOIL_TEST_INTEGRATIVE=True to include this integration test, "
981
+ "or run `make integration_test_local` to run all integration tests.",
982
+ )
983
+
984
+
836
985
  def slow(test_item: MT) -> MT:
837
986
  """
838
987
  Use this decorator to identify tests that are slow and not critical.
@@ -845,6 +994,11 @@ def slow(test_item: MT) -> MT:
845
994
  return unittest.skip('Skipped because TOIL_TEST_QUICK is "True"')(test_item)
846
995
 
847
996
 
997
+ pslow = pytest.mark.skipif(
998
+ os.environ.get("TOIL_TEST_QUICK", "").lower() == "true",
999
+ reason='Skipped because TOIL_TEST_QUICK is "True"',
1000
+ )
1001
+
848
1002
  methodNamePartRegex = re.compile("^[a-zA-Z_0-9]+$")
849
1003
 
850
1004
 
@@ -881,7 +1035,11 @@ def timeLimit(seconds: int) -> Generator[None, None, None]:
881
1035
  signal.alarm(0)
882
1036
 
883
1037
 
884
- def make_tests(generalMethod, targetClass, **kwargs):
1038
+ def make_tests(
1039
+ generalMethod: Callable[[Any], Any],
1040
+ targetClass: Optional[Callable[[Any], Any]],
1041
+ **kwargs: Any,
1042
+ ) -> None:
885
1043
  """
886
1044
  This method dynamically generates test methods using the generalMethod as a template. Each
887
1045
  generated function is the result of a unique combination of parameters applied to the
@@ -937,7 +1095,9 @@ def make_tests(generalMethod, targetClass, **kwargs):
937
1095
 
938
1096
  """
939
1097
 
940
- def permuteIntoLeft(left, rParamName, right):
1098
+ def permuteIntoLeft(
1099
+ left: dict[str, dict[str, str]], rParamName: str, right: dict[str, str]
1100
+ ) -> None:
941
1101
  """
942
1102
  Permutes values in right dictionary into each parameter: value dict pair in the left
943
1103
  dictionary. Such that the left dictionary will contain a new set of keys each of which is
@@ -972,10 +1132,10 @@ def make_tests(generalMethod, targetClass, **kwargs):
972
1132
  left[prmValName + nextPrmVal] = aggDict
973
1133
  left.pop(prmValName)
974
1134
 
975
- def insertMethodToClass():
1135
+ def insertMethodToClass() -> None:
976
1136
  """Generate and insert test methods."""
977
1137
 
978
- def fx(self, prms=prms):
1138
+ def fx(self: Any, prms: Any = prms) -> Any:
979
1139
  if prms is not None:
980
1140
  return generalMethod(self, **prms)
981
1141
  else:
@@ -1085,7 +1245,7 @@ class ApplianceTestSupport(ToilTest):
1085
1245
  self.containerName = str(uuid.uuid4())
1086
1246
  self.popen: Optional[subprocess.Popen[bytes]] = None
1087
1247
 
1088
- def __enter__(self) -> "Appliance":
1248
+ def __enter__(self) -> Self:
1089
1249
  with self.lock:
1090
1250
  image = applianceSelf()
1091
1251
  # Omitting --rm, it's unreliable, see https://github.com/docker/docker/issues/16575