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
@@ -1,22 +1,25 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- import pytest
5
4
  import re
6
5
  import shutil
7
6
  import string
8
7
  import subprocess
9
8
  import unittest
10
- from typing import Any, Optional, Union
9
+ from collections.abc import Generator
10
+ from pathlib import Path
11
+ from typing import Any, Optional, Union, cast
11
12
  from unittest.mock import patch
12
13
  from uuid import uuid4
13
14
 
15
+ import pytest
14
16
  import WDL.Error
15
17
  import WDL.Expr
16
18
 
17
19
  from toil.fileStores import FileID
18
20
  from toil.test import (
19
21
  ToilTest,
22
+ get_data,
20
23
  needs_docker,
21
24
  needs_docker_cuda,
22
25
  needs_google_storage,
@@ -35,20 +38,6 @@ from toil.wdl.wdltoil import (
35
38
  logger = logging.getLogger(__name__)
36
39
 
37
40
 
38
- @needs_wdl
39
- class BaseWDLTest(ToilTest):
40
- """Base test class for WDL tests."""
41
-
42
- def setUp(self) -> None:
43
- """Runs anew before each test to create farm fresh temp dirs."""
44
- self.output_dir = os.path.join("/tmp/", "toil-wdl-test-" + str(uuid4()))
45
- os.makedirs(self.output_dir)
46
-
47
- def tearDown(self) -> None:
48
- if os.path.exists(self.output_dir):
49
- shutil.rmtree(self.output_dir)
50
-
51
-
52
41
  WDL_CONFORMANCE_TEST_REPO = "https://github.com/DataBiosphere/wdl-conformance-tests.git"
53
42
  WDL_CONFORMANCE_TEST_COMMIT = "baf44bcc7e6f6927540adf77d91b26a5558ae4b7"
54
43
  # These tests are known to require things not implemented by
@@ -71,31 +60,23 @@ WDL_UNIT_TESTS_UNSUPPORTED_BY_TOIL = [
71
60
  69, # Same as 68
72
61
  87, # MiniWDL does not handle metacharacters properly when running regex, https://github.com/chanzuckerberg/miniwdl/issues/709
73
62
  97, # miniwdl bug, see https://github.com/chanzuckerberg/miniwdl/issues/701
74
- 105, # miniwdl (and toil) bug, unserializable json is serialized, see https://github.com/chanzuckerberg/miniwdl/issues/702
75
- 107, # object not supported
76
- 108, # object not supported
77
- 109, # object not supported
78
- 110, # object not supported
79
- 120, # miniwdl bug, see https://github.com/chanzuckerberg/miniwdl/issues/699
80
- 131, # miniwdl bug, evalerror, see https://github.com/chanzuckerberg/miniwdl/issues/700
81
- 134, # same as 131
82
- 144 # miniwdl and toil bug
63
+ 105, # miniwdl (and toil) bug, unserializable json is serialized, see https://github.com/chanzuckerberg/miniwdl/issues/702
64
+ 107, # object not supported
65
+ 108, # object not supported
66
+ 109, # object not supported
67
+ 110, # object not supported
68
+ 120, # miniwdl bug, see https://github.com/chanzuckerberg/miniwdl/issues/699
69
+ 131, # miniwdl bug, evalerror, see https://github.com/chanzuckerberg/miniwdl/issues/700
70
+ 134, # same as 131
71
+ 144, # miniwdl and toil bug
83
72
  ]
84
73
 
85
74
 
86
-
87
- class WDLConformanceTests(BaseWDLTest):
88
- """
89
- WDL conformance tests for Toil.
90
- """
91
-
92
- wdl_dir = "wdl-conformance-tests"
93
-
94
- @classmethod
95
- def setUpClass(cls) -> None:
96
-
75
+ @pytest.fixture(scope="function")
76
+ def wdl_conformance_test_repo(tmp_path: Path) -> Generator[Path]:
77
+ try:
97
78
  p = subprocess.Popen(
98
- f"git clone {WDL_CONFORMANCE_TEST_REPO} {cls.wdl_dir} && cd {cls.wdl_dir} && git checkout {WDL_CONFORMANCE_TEST_COMMIT}",
79
+ f"git clone {WDL_CONFORMANCE_TEST_REPO} {str(tmp_path)} && cd {str(tmp_path)} && git checkout {WDL_CONFORMANCE_TEST_COMMIT}",
99
80
  shell=True,
100
81
  )
101
82
 
@@ -103,12 +84,17 @@ class WDLConformanceTests(BaseWDLTest):
103
84
 
104
85
  if p.returncode > 0:
105
86
  raise RuntimeError("Could not clone WDL conformance tests")
87
+ yield tmp_path
88
+ finally:
89
+ pass # no cleanup needed
106
90
 
107
- os.chdir(cls.wdl_dir)
108
91
 
109
- cls.base_command = [exactPython, "run.py", "--runner", "toil-wdl-runner"]
92
+ class TestWDLConformance:
93
+ """
94
+ WDL conformance tests for Toil.
95
+ """
110
96
 
111
- def check(self, p: subprocess.CompletedProcess) -> None:
97
+ def check(self, p: "subprocess.CompletedProcess[bytes]") -> None:
112
98
  """
113
99
  Make sure a call completed or explain why it failed.
114
100
  """
@@ -126,365 +112,653 @@ class WDLConformanceTests(BaseWDLTest):
126
112
  p.check_returncode()
127
113
 
128
114
  @slow
129
- def test_unit_tests_v11(self):
115
+ def test_unit_tests_v11(self, wdl_conformance_test_repo: Path) -> None:
130
116
  # There are still some bugs with the WDL spec, use a fixed version until
131
117
  # See comments of https://github.com/openwdl/wdl/pull/669
118
+ os.chdir(wdl_conformance_test_repo)
132
119
  repo_url = "https://github.com/stxue1/wdl.git"
133
120
  repo_branch = "wdl-1.1.3-fixes"
134
- command = f"{exactPython} setup_unit_tests.py -v 1.1 --extra-patch-data unit_tests_patch_data.yaml --repo {repo_url} --branch {repo_branch} --force-pull"
135
- p = subprocess.run(command.split(" "), capture_output=True)
136
- self.check(p)
137
- command = f"{exactPython} run_unit.py -r toil-wdl-runner -v 1.1 --progress --exclude-numbers {','.join([str(t) for t in WDL_UNIT_TESTS_UNSUPPORTED_BY_TOIL])}"
138
- p = subprocess.run(command.split(" "), capture_output=True)
139
- self.check(p)
121
+ commands1 = [
122
+ exactPython,
123
+ "setup_unit_tests.py",
124
+ "-v",
125
+ "1.1",
126
+ "--extra-patch-data",
127
+ "unit_tests_patch_data.yaml",
128
+ "--repo",
129
+ repo_url,
130
+ "--branch",
131
+ repo_branch,
132
+ "--force-pull",
133
+ ]
134
+ p1 = subprocess.run(commands1, capture_output=True)
135
+ self.check(p1)
136
+ commands2 = [
137
+ exactPython,
138
+ "run_unit.py",
139
+ "-r",
140
+ "toil-wdl-runner",
141
+ "-v",
142
+ "1.1",
143
+ "--progress",
144
+ "--exclude-numbers",
145
+ ",".join([str(t) for t in WDL_UNIT_TESTS_UNSUPPORTED_BY_TOIL]),
146
+ ]
147
+ p2 = subprocess.run(commands2, capture_output=True)
148
+ self.check(p2)
140
149
 
141
150
  # estimated running time: 10 minutes
142
151
  @slow
143
- def test_conformance_tests_v10(self):
144
- command = self.base_command + ["-v", "1.0"]
152
+ def test_conformance_tests_v10(self, wdl_conformance_test_repo: Path) -> None:
153
+ os.chdir(wdl_conformance_test_repo)
154
+ commands = [
155
+ exactPython,
156
+ "run.py",
157
+ "--runner",
158
+ "toil-wdl-runner",
159
+ "--conformance-file",
160
+ "conformance.yaml",
161
+ "-v",
162
+ "1.0",
163
+ ]
145
164
  if WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL:
146
- command.append("--exclude-numbers")
147
- command.append(
165
+ commands.append("--exclude-numbers")
166
+ commands.append(
148
167
  ",".join([str(t) for t in WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL])
149
168
  )
150
- p = subprocess.run(command, capture_output=True)
169
+ p = subprocess.run(commands, capture_output=True)
151
170
 
152
171
  self.check(p)
153
172
 
154
173
  # estimated running time: 10 minutes
155
174
  @slow
156
- def test_conformance_tests_v11(self):
157
- command = self.base_command + ["-v", "1.1"]
175
+ def test_conformance_tests_v11(self, wdl_conformance_test_repo: Path) -> None:
176
+ os.chdir(wdl_conformance_test_repo)
177
+ commands = [
178
+ exactPython,
179
+ "run.py",
180
+ "--runner",
181
+ "toil-wdl-runner",
182
+ "--conformance-file",
183
+ "conformance.yaml",
184
+ "-v",
185
+ "1.1",
186
+ ]
158
187
  if WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL:
159
- command.append("--exclude-numbers")
160
- command.append(
188
+ commands.append("--exclude-numbers")
189
+ commands.append(
161
190
  ",".join([str(t) for t in WDL_CONFORMANCE_TESTS_UNSUPPORTED_BY_TOIL])
162
191
  )
163
- p = subprocess.run(command, capture_output=True)
192
+ p = subprocess.run(commands, capture_output=True)
164
193
 
165
194
  self.check(p)
166
195
 
167
196
  @slow
168
- def test_conformance_tests_integration(self):
169
- ids_to_run = "encode,tut01,tut02,tut03,tut04"
197
+ def test_conformance_tests_integration(
198
+ self, wdl_conformance_test_repo: Path
199
+ ) -> None:
200
+ os.chdir(wdl_conformance_test_repo)
201
+ commands = [
202
+ exactPython,
203
+ "run.py",
204
+ "--runner",
205
+ "toil-wdl-runner",
206
+ "-v",
207
+ "1.0",
208
+ "--conformance-file",
209
+ "integration.yaml",
210
+ "--id",
211
+ "encode,tut01,tut02,tut03,tut04",
212
+ ]
170
213
  p = subprocess.run(
171
- self.base_command
172
- + [
173
- "-v",
174
- "1.0",
175
- "--conformance-file",
176
- "integration.yaml",
177
- "--id",
178
- ids_to_run,
179
- ],
214
+ commands,
180
215
  capture_output=True,
181
216
  )
182
217
 
183
218
  self.check(p)
184
219
 
185
- @classmethod
186
- def tearDownClass(cls) -> None:
187
- upper_dir = os.path.dirname(os.getcwd())
188
- os.chdir(upper_dir)
189
- shutil.rmtree("wdl-conformance-tests")
190
220
 
191
-
192
- class WDLTests(BaseWDLTest):
221
+ class TestWDL:
193
222
  """Tests for Toil's MiniWDL-based implementation."""
194
223
 
195
- @classmethod
196
- def setUpClass(cls) -> None:
197
- """Runs once for all tests."""
198
- cls.base_command = [exactPython, "-m", "toil.wdl.wdltoil"]
224
+ base_command = [exactPython, "-m", "toil.wdl.wdltoil"]
199
225
 
200
226
  # We inherit a testMD5sum but it is going to need Singularity or Docker
201
227
  # now. And also needs to have a WDL 1.0+ WDL file. So we replace it.
202
228
  @needs_singularity_or_docker
203
- def test_MD5sum(self):
229
+ def test_MD5sum(self, tmp_path: Path) -> None:
204
230
  """Test if Toil produces the same outputs as known good outputs for WDL's
205
231
  GATK tutorial #1."""
206
- wdl = os.path.abspath("src/toil/test/wdl/md5sum/md5sum.1.0.wdl")
207
- json_file = os.path.abspath("src/toil/test/wdl/md5sum/md5sum.json")
208
-
209
- result_json = subprocess.check_output(
210
- self.base_command
211
- + [wdl, json_file, "-o", self.output_dir, "--logDebug", "--retryCount=0"]
212
- )
213
- result = json.loads(result_json)
214
-
215
- assert "ga4ghMd5.value" in result
216
- assert isinstance(result["ga4ghMd5.value"], str)
217
- assert os.path.exists(result["ga4ghMd5.value"])
218
- assert os.path.basename(result["ga4ghMd5.value"]) == "md5sum.txt"
219
-
220
- def test_url_to_file(self):
232
+ with get_data("test/wdl/md5sum/md5sum.1.0.wdl") as wdl:
233
+ with get_data("test/wdl/md5sum/md5sum.json") as json_file:
234
+ result_json = subprocess.check_output(
235
+ self.base_command
236
+ + [
237
+ str(wdl),
238
+ str(json_file),
239
+ "-o",
240
+ str(tmp_path),
241
+ "--logDebug",
242
+ "--retryCount=0",
243
+ ]
244
+ )
245
+ result = json.loads(result_json)
246
+
247
+ assert "ga4ghMd5.value" in result
248
+ assert isinstance(result["ga4ghMd5.value"], str)
249
+ assert os.path.exists(result["ga4ghMd5.value"])
250
+ assert os.path.basename(result["ga4ghMd5.value"]) == "md5sum.txt"
251
+
252
+ def test_url_to_file(self, tmp_path: Path) -> None:
221
253
  """
222
254
  Test if web URL strings can be coerced to usable Files.
223
255
  """
224
- wdl = os.path.abspath("src/toil/test/wdl/testfiles/url_to_file.wdl")
256
+ with get_data("test/wdl/testfiles/url_to_file.wdl") as wdl:
257
+ result_json = subprocess.check_output(
258
+ self.base_command
259
+ + [str(wdl), "-o", str(tmp_path), "--logInfo", "--retryCount=0"]
260
+ )
261
+ result = json.loads(result_json)
225
262
 
226
- result_json = subprocess.check_output(
227
- self.base_command
228
- + [wdl, "-o", self.output_dir, "--logInfo", "--retryCount=0"]
229
- )
230
- result = json.loads(result_json)
263
+ assert "url_to_file.first_line" in result
264
+ assert isinstance(result["url_to_file.first_line"], str)
265
+ assert result["url_to_file.first_line"] == "chr1\t248387328"
231
266
 
232
- assert "url_to_file.first_line" in result
233
- assert isinstance(result["url_to_file.first_line"], str)
234
- self.assertEqual(result["url_to_file.first_line"], "chr1\t248387328")
267
+ def test_string_file_coercion(self, tmp_path: Path) -> None:
268
+ """
269
+ Test if input Files can be coerced to string and back.
270
+ """
271
+ with get_data("test/wdl/testfiles/string_file_coercion.wdl") as wdl:
272
+ with get_data("test/wdl/testfiles/string_file_coercion.json") as json_file:
273
+ result_json = subprocess.check_output(
274
+ self.base_command
275
+ + [
276
+ str(wdl),
277
+ str(json_file),
278
+ "-o",
279
+ str(tmp_path),
280
+ "--logInfo",
281
+ "--retryCount=0"
282
+ ]
283
+ )
284
+ result = json.loads(result_json)
285
+
286
+ assert "StringFileCoercion.output_file" in result
235
287
 
236
288
  @needs_docker
237
- def test_wait(self):
289
+ def test_wait(self, tmp_path: Path) -> None:
238
290
  """
239
291
  Test if Bash "wait" works in WDL scripts.
240
292
  """
241
- wdl = os.path.abspath("src/toil/test/wdl/testfiles/wait.wdl")
293
+ with get_data("test/wdl/testfiles/wait.wdl") as wdl:
294
+ result_json = subprocess.check_output(
295
+ self.base_command
296
+ + [
297
+ str(wdl),
298
+ "-o",
299
+ str(tmp_path),
300
+ "--logInfo",
301
+ "--retryCount=0",
302
+ "--wdlContainer=docker",
303
+ ]
304
+ )
305
+ result = json.loads(result_json)
242
306
 
243
- result_json = subprocess.check_output(
244
- self.base_command
245
- + [
246
- wdl,
247
- "-o",
248
- self.output_dir,
249
- "--logInfo",
250
- "--retryCount=0",
251
- "--wdlContainer=docker",
252
- ]
253
- )
254
- result = json.loads(result_json)
307
+ assert "wait.result" in result
308
+ assert isinstance(result["wait.result"], str)
309
+ assert result["wait.result"] == "waited"
310
+
311
+ @needs_singularity_or_docker
312
+ def test_workflow_file_deletion(self, tmp_path: Path) -> None:
313
+ """
314
+ Test if Toil can delete non-output outputs at the end of a workflow.
315
+ """
316
+ # Keep a job store around to inspect for files.
317
+ (tmp_path / "jobStore").mkdir()
318
+ job_store = tmp_path / "jobStore" / "tree"
319
+
320
+ # Make a working directory to run in
321
+ work_dir = tmp_path / "workDir"
322
+ work_dir.mkdir()
323
+ # Make the file that will be imported from a string in the workflow
324
+ referenced_file = work_dir / "localfile.txt"
325
+ with referenced_file.open("w") as f:
326
+ f.write("This file is imported by local path in the workflow")
327
+ # Make the file to pass as input
328
+ sent_in_file = work_dir / "sent_in.txt"
329
+ with sent_in_file.open("w") as f:
330
+ f.write("This file is sent in as input")
331
+
332
+ with get_data("test/wdl/testfiles/drop_files.wdl") as wdl:
333
+ result_json = subprocess.check_output(
334
+ self.base_command
335
+ + [
336
+ str(wdl),
337
+ "-o",
338
+ str(tmp_path / "output"),
339
+ "--jobStore",
340
+ job_store,
341
+ "--clean=never",
342
+ "--logInfo",
343
+ "--retryCount=0",
344
+ '--input={"file_in": "' + str(sent_in_file) + '"}',
345
+ ],
346
+ cwd=work_dir,
347
+ )
348
+ result = json.loads(result_json)
255
349
 
256
- assert "wait.result" in result
257
- assert isinstance(result["wait.result"], str)
258
- self.assertEqual(result["wait.result"], "waited")
350
+ # Get all the file values in the job store.
351
+ all_file_values = set()
352
+ for directory, _, files in os.walk(
353
+ job_store
354
+ ): # can't switch to job_store.walk() until Python 3.12 is the minimum version
355
+ for filename in files:
356
+ with (Path(directory) / filename).open(
357
+ encoding="utf-8", errors="replace"
358
+ ) as f:
359
+ all_file_values.add(f.read().rstrip())
360
+
361
+ # Make sure the files with the right contents are in the job store and
362
+ # the files with the wrong contents aren't anymore.
363
+ #
364
+ # This assumes no top-level cleanup in the main driver script.
365
+
366
+ # These are all created inside the workflow and not output
367
+ assert (
368
+ "This file is imported by local path in the workflow"
369
+ not in all_file_values
370
+ )
371
+ assert "This file is consumed by a task call" not in all_file_values
372
+ assert (
373
+ "This file is created in a task inputs section" not in all_file_values
374
+ )
375
+ assert "This file is created in a runtime section" not in all_file_values
376
+ assert "This task file is not used" not in all_file_values
377
+ assert "This file should be discarded" not in all_file_values
378
+ assert "This file is dropped by a subworkflow" not in all_file_values
379
+ assert "This file gets stored in a variable" not in all_file_values
380
+ assert "This file never gets stored in a variable" not in all_file_values
381
+
382
+ # These are created inside the workflow and output
383
+ assert "3" in all_file_values
384
+ assert "This file is collected as a task output twice" in all_file_values
385
+ assert "This file should be kept" in all_file_values
386
+ assert "This file is kept by a subworkflow" in all_file_values
387
+
388
+ # These are sent into the workflow from the enclosing environment and
389
+ # should not be deleted.
390
+ assert "This file is sent in as input" in all_file_values
391
+
392
+ # Make sure we didn't somehow delete the file sent as input
393
+ assert sent_in_file.exists()
259
394
 
260
395
  @needs_singularity_or_docker
261
- def test_all_call_outputs(self):
396
+ def test_all_call_outputs(self, tmp_path: Path) -> None:
262
397
  """
263
398
  Test if Toil can collect all call outputs from a workflow that doesn't expose them.
264
399
  """
265
- wdl = os.path.abspath("src/toil/test/wdl/testfiles/not_enough_outputs.wdl")
266
-
267
- # With no flag we don't include the call outputs
268
- result_json = subprocess.check_output(
269
- self.base_command
270
- + [wdl, "-o", self.output_dir, "--logInfo", "--retryCount=0"]
271
- )
272
- result = json.loads(result_json)
400
+ with get_data("test/wdl/testfiles/not_enough_outputs.wdl") as wdl:
401
+ # With no flag we don't include the call outputs
402
+ result_json = subprocess.check_output(
403
+ self.base_command
404
+ + [str(wdl), "-o", str(tmp_path), "--logInfo", "--retryCount=0"]
405
+ )
406
+ result = json.loads(result_json)
273
407
 
274
- assert "wf.only_result" in result
275
- assert "wf.do_math.square" not in result
276
- assert "wf.do_math.cube" not in result
277
- assert "wf.should_never_output" not in result
408
+ assert "wf.only_result" in result
409
+ assert "wf.do_math.square" not in result
410
+ assert "wf.do_math.cube" not in result
411
+ assert "wf.should_never_output" not in result
278
412
 
279
- # With flag off we don't include the call outputs
280
- result_json = subprocess.check_output(
281
- self.base_command
282
- + [
283
- wdl,
284
- "-o",
285
- self.output_dir,
286
- "--logInfo",
287
- "--retryCount=0",
288
- "--allCallOutputs=false",
289
- ]
290
- )
291
- result = json.loads(result_json)
413
+ # With flag off we don't include the call outputs
414
+ result_json = subprocess.check_output(
415
+ self.base_command
416
+ + [
417
+ str(wdl),
418
+ "-o",
419
+ str(tmp_path),
420
+ "--logInfo",
421
+ "--retryCount=0",
422
+ "--allCallOutputs=false",
423
+ ]
424
+ )
425
+ result = json.loads(result_json)
292
426
 
293
- assert "wf.only_result" in result
294
- assert "wf.do_math.square" not in result
295
- assert "wf.do_math.cube" not in result
296
- assert "wf.should_never_output" not in result
427
+ assert "wf.only_result" in result
428
+ assert "wf.do_math.square" not in result
429
+ assert "wf.do_math.cube" not in result
430
+ assert "wf.should_never_output" not in result
297
431
 
298
- # With flag on we do include the call outputs
299
- result_json = subprocess.check_output(
300
- self.base_command
301
- + [
302
- wdl,
303
- "-o",
304
- self.output_dir,
305
- "--logInfo",
306
- "--retryCount=0",
307
- "--allCallOutputs=on",
308
- ]
309
- )
310
- result = json.loads(result_json)
432
+ # With flag on we do include the call outputs
433
+ result_json = subprocess.check_output(
434
+ self.base_command
435
+ + [
436
+ str(wdl),
437
+ "-o",
438
+ str(tmp_path),
439
+ "--logInfo",
440
+ "--retryCount=0",
441
+ "--allCallOutputs=on",
442
+ ]
443
+ )
444
+ result = json.loads(result_json)
311
445
 
312
- assert "wf.only_result" in result
313
- assert "wf.do_math.square" in result
314
- assert "wf.do_math.cube" in result
315
- assert "wf.should_never_output" not in result
446
+ assert "wf.only_result" in result
447
+ assert "wf.do_math.square" in result
448
+ assert "wf.do_math.cube" in result
449
+ assert "wf.should_never_output" not in result
316
450
 
317
451
  @needs_singularity_or_docker
318
- def test_croo_detection(self):
452
+ def test_croo_detection(self, tmp_path: Path) -> None:
319
453
  """
320
454
  Test if Toil can detect and do something sensible with Cromwell Output Organizer workflows.
321
455
  """
322
- wdl = os.path.abspath("src/toil/test/wdl/testfiles/croo.wdl")
323
-
324
- # With no flag we should include all task outputs
325
- result_json = subprocess.check_output(
326
- self.base_command
327
- + [wdl, "-o", self.output_dir, "--logInfo", "--retryCount=0"]
328
- )
329
- result = json.loads(result_json)
456
+ with get_data("test/wdl/testfiles/croo.wdl") as wdl:
457
+ # With no flag we should include all task outputs
458
+ result_json = subprocess.check_output(
459
+ self.base_command
460
+ + [str(wdl), "-o", str(tmp_path), "--logInfo", "--retryCount=0"]
461
+ )
462
+ result = json.loads(result_json)
330
463
 
331
- assert "wf.only_result" in result
332
- assert "wf.do_math.square" in result
333
- assert "wf.do_math.cube" in result
334
- assert "wf.should_never_output" not in result
464
+ assert "wf.only_result" in result
465
+ assert "wf.do_math.square" in result
466
+ assert "wf.do_math.cube" in result
467
+ assert "wf.should_never_output" not in result
335
468
 
336
- # With flag off we obey the WDL spec even if we're suspicious
337
- result_json = subprocess.check_output(
338
- self.base_command
339
- + [
340
- wdl,
341
- "-o",
342
- self.output_dir,
343
- "--logInfo",
344
- "--retryCount=0",
345
- "--allCallOutputs=off",
346
- ]
347
- )
348
- result = json.loads(result_json)
469
+ # With flag off we obey the WDL spec even if we're suspicious
470
+ result_json = subprocess.check_output(
471
+ self.base_command
472
+ + [
473
+ str(wdl),
474
+ "-o",
475
+ str(tmp_path),
476
+ "--logInfo",
477
+ "--retryCount=0",
478
+ "--allCallOutputs=off",
479
+ ]
480
+ )
481
+ result = json.loads(result_json)
349
482
 
350
- assert "wf.only_result" in result
351
- assert "wf.do_math.square" not in result
352
- assert "wf.do_math.cube" not in result
353
- assert "wf.should_never_output" not in result
483
+ assert "wf.only_result" in result
484
+ assert "wf.do_math.square" not in result
485
+ assert "wf.do_math.cube" not in result
486
+ assert "wf.should_never_output" not in result
354
487
 
355
488
  @needs_singularity_or_docker
356
- def test_caching(self):
489
+ def test_caching(self, tmp_path: Path) -> None:
357
490
  """
358
491
  Test if Toil can cache task runs.
359
492
  """
360
- wdl = os.path.abspath('src/toil/test/wdl/testfiles/random.wdl')
361
-
362
- caching_env = dict(os.environ)
363
- caching_env["MINIWDL__CALL_CACHE__GET"] = "true"
364
- caching_env["MINIWDL__CALL_CACHE__PUT"] = "true"
365
- caching_env["MINIWDL__CALL_CACHE__DIR"] = self._createTempDir("cache")
366
-
367
- result_json = subprocess.check_output(
368
- self.base_command + [wdl, '-o', self.output_dir, '--logInfo', '--retryCount=0', '--inputs={"random.task_1_input": 1, "random.task_2_input": 1}'],
369
- env=caching_env)
370
- result_initial = json.loads(result_json)
371
-
372
- assert 'random.value_seen' in result_initial
373
- assert 'random.value_written' in result_initial
374
-
375
- result_json = subprocess.check_output(
376
- self.base_command + [wdl, '-o', self.output_dir, '--logInfo', '--retryCount=0', '--inputs={"random.task_1_input": 1, "random.task_2_input": 1}'],
377
- env=caching_env)
378
- result_cached = json.loads(result_json)
493
+ with get_data("test/wdl/testfiles/random.wdl") as wdl:
494
+ cachedir = tmp_path / "cache"
495
+ cachedir.mkdir()
496
+ caching_env = dict(os.environ)
497
+ caching_env["MINIWDL__CALL_CACHE__GET"] = "true"
498
+ caching_env["MINIWDL__CALL_CACHE__PUT"] = "true"
499
+ caching_env["MINIWDL__CALL_CACHE__DIR"] = str(cachedir)
379
500
 
380
- assert 'random.value_seen' in result_cached
381
- assert 'random.value_written' in result_cached
382
-
383
- assert result_cached['random.value_seen'] == result_initial['random.value_seen']
384
- assert result_cached['random.value_written'] == result_initial['random.value_written']
385
-
386
- result_json = subprocess.check_output(
387
- self.base_command + [wdl, '-o', self.output_dir, '--logInfo', '--retryCount=0', '--inputs={"random.task_1_input": 2, "random.task_2_input": 1}'],
388
- env=caching_env)
389
- result_not_cached = json.loads(result_json)
390
-
391
- assert 'random.value_seen' in result_not_cached
392
- assert 'random.value_written' in result_not_cached
393
-
394
- assert result_not_cached['random.value_seen'] != result_initial['random.value_seen']
395
- assert result_not_cached['random.value_written'] != result_initial['random.value_written']
501
+ result_json = subprocess.check_output(
502
+ self.base_command
503
+ + [
504
+ str(wdl),
505
+ "-o",
506
+ str(tmp_path / "out1"),
507
+ "--logInfo",
508
+ "--retryCount=0",
509
+ '--inputs={"random.task_1_input": 1, "random.task_2_input": 1}',
510
+ ],
511
+ env=caching_env,
512
+ )
513
+ result_initial = json.loads(result_json)
396
514
 
397
- result_json = subprocess.check_output(
398
- self.base_command + [wdl, '-o', self.output_dir, '--logInfo', '--retryCount=0', '--inputs={"random.task_1_input": 1, "random.task_2_input": 2}'],
399
- env=caching_env)
400
- result_part_cached = json.loads(result_json)
515
+ assert "random.value_seen" in result_initial
516
+ assert "random.value_written" in result_initial
401
517
 
402
- assert 'random.value_seen' in result_part_cached
403
- assert 'random.value_written' in result_part_cached
518
+ result_json = subprocess.check_output(
519
+ self.base_command
520
+ + [
521
+ str(wdl),
522
+ "-o",
523
+ str(tmp_path / "out2"),
524
+ "--logInfo",
525
+ "--retryCount=0",
526
+ '--inputs={"random.task_1_input": 1, "random.task_2_input": 1}',
527
+ ],
528
+ env=caching_env,
529
+ )
530
+ result_cached = json.loads(result_json)
404
531
 
405
- assert result_part_cached['random.value_seen'] == result_initial['random.value_seen']
406
- assert result_part_cached['random.value_written'] != result_initial['random.value_written']
407
- assert result_part_cached['random.value_written'] != result_not_cached['random.value_written']
532
+ assert "random.value_seen" in result_cached
533
+ assert "random.value_written" in result_cached
408
534
 
535
+ assert (
536
+ result_cached["random.value_seen"]
537
+ == result_initial["random.value_seen"]
538
+ )
539
+ assert (
540
+ result_cached["random.value_written"]
541
+ == result_initial["random.value_written"]
542
+ )
409
543
 
544
+ result_json = subprocess.check_output(
545
+ self.base_command
546
+ + [
547
+ str(wdl),
548
+ "-o",
549
+ str(tmp_path / "out3"),
550
+ "--logInfo",
551
+ "--retryCount=0",
552
+ '--inputs={"random.task_1_input": 2, "random.task_2_input": 1}',
553
+ ],
554
+ env=caching_env,
555
+ )
556
+ result_not_cached = json.loads(result_json)
410
557
 
411
- def test_url_to_optional_file(self):
412
- """
413
- Test if missing and error-producing URLs are handled correctly for optional File? values.
414
- """
415
- wdl = os.path.abspath("src/toil/test/wdl/testfiles/url_to_optional_file.wdl")
558
+ assert "random.value_seen" in result_not_cached
559
+ assert "random.value_written" in result_not_cached
416
560
 
417
- def run_for_code(code: int) -> dict:
418
- """
419
- Run a workflow coercing URL to File? where the URL returns the given status code.
561
+ assert (
562
+ result_not_cached["random.value_seen"]
563
+ != result_initial["random.value_seen"]
564
+ )
565
+ assert (
566
+ result_not_cached["random.value_written"]
567
+ != result_initial["random.value_written"]
568
+ )
420
569
 
421
- Return the parsed output.
422
- """
423
- logger.info("Test optional file with HTTP code %s", code)
424
- json_value = '{"url_to_optional_file.http_code": %d}' % code
425
570
  result_json = subprocess.check_output(
426
571
  self.base_command
427
572
  + [
428
- wdl,
429
- json_value,
573
+ str(wdl),
430
574
  "-o",
431
- self.output_dir,
575
+ str(tmp_path / "out4"),
432
576
  "--logInfo",
433
577
  "--retryCount=0",
434
- ]
578
+ '--inputs={"random.task_1_input": 1, "random.task_2_input": 2}',
579
+ ],
580
+ env=caching_env,
435
581
  )
436
- result = json.loads(result_json)
437
- return result
582
+ result_part_cached = json.loads(result_json)
438
583
 
439
- # Check files that exist
440
- result = run_for_code(200)
441
- assert "url_to_optional_file.out_file" in result
442
- self.assertNotEqual(result["url_to_optional_file.out_file"], None)
584
+ assert "random.value_seen" in result_part_cached
585
+ assert "random.value_written" in result_part_cached
443
586
 
444
- for code in (404, 410):
445
- # Check files that definitely don't
446
- result = run_for_code(code)
587
+ assert (
588
+ result_part_cached["random.value_seen"]
589
+ == result_initial["random.value_seen"]
590
+ )
591
+ assert (
592
+ result_part_cached["random.value_written"]
593
+ != result_initial["random.value_written"]
594
+ )
595
+ assert (
596
+ result_part_cached["random.value_written"]
597
+ != result_not_cached["random.value_written"]
598
+ )
599
+
600
+ def test_url_to_optional_file(self, tmp_path: Path) -> None:
601
+ """
602
+ Test if missing and error-producing URLs are handled correctly for optional File? values.
603
+ """
604
+ with get_data("test/wdl/testfiles/url_to_optional_file.wdl") as wdl:
605
+
606
+ def run_for_code(code: int) -> dict[str, Any]:
607
+ """
608
+ Run a workflow coercing URL to File? where the URL returns the given status code.
609
+
610
+ Return the parsed output.
611
+ """
612
+ logger.info("Test optional file with HTTP code %s", code)
613
+ json_value = '{"url_to_optional_file.http_code": %d}' % code
614
+ result_json = subprocess.check_output(
615
+ self.base_command
616
+ + [
617
+ str(wdl),
618
+ json_value,
619
+ "-o",
620
+ str(tmp_path),
621
+ "--logInfo",
622
+ "--retryCount=0",
623
+ ]
624
+ )
625
+ result = json.loads(result_json)
626
+ return cast(dict[str, Any], json.loads(result_json))
627
+
628
+ # Check files that exist
629
+ result = run_for_code(200)
447
630
  assert "url_to_optional_file.out_file" in result
448
- self.assertEqual(result["url_to_optional_file.out_file"], None)
631
+ assert result["url_to_optional_file.out_file"] is not None
632
+
633
+ for code in (404, 410):
634
+ # Check files that definitely don't
635
+ result = run_for_code(code)
636
+ assert "url_to_optional_file.out_file" in result
637
+ assert result["url_to_optional_file.out_file"] is None
449
638
 
450
- for code in (402, 418, 500, 502):
451
- # Check that cases where the server refuses to say if the file
452
- # exists stop the workflow.
453
- with self.assertRaises(subprocess.CalledProcessError):
454
- run_for_code(code)
639
+ for code in (402, 418, 500, 502):
640
+ # Check that cases where the server refuses to say if the file
641
+ # exists stop the workflow.
642
+ with pytest.raises(subprocess.CalledProcessError):
643
+ run_for_code(code)
455
644
 
456
- def test_missing_output_directory(self):
645
+ def test_missing_output_directory(self, tmp_path: Path) -> None:
457
646
  """
458
647
  Test if Toil can run a WDL workflow into a new directory.
459
648
  """
460
- wdl = os.path.abspath("src/toil/test/wdl/md5sum/md5sum.1.0.wdl")
461
- json_file = os.path.abspath("src/toil/test/wdl/md5sum/md5sum.json")
462
- subprocess.check_call(
463
- self.base_command
464
- + [
465
- wdl,
466
- json_file,
467
- "-o",
468
- os.path.join(self.output_dir, "does", "not", "exist"),
469
- "--logDebug",
470
- "--retryCount=0",
471
- ]
472
- )
649
+ with get_data("test/wdl/md5sum/md5sum.1.0.wdl") as wdl:
650
+ with get_data("test/wdl/md5sum/md5sum.json") as json_file:
651
+ subprocess.check_call(
652
+ self.base_command
653
+ + [
654
+ str(wdl),
655
+ str(json_file),
656
+ "-o",
657
+ str(tmp_path / "does" / "not" / "exist"),
658
+ "--logDebug",
659
+ "--retryCount=0",
660
+ ]
661
+ )
473
662
 
474
663
  @needs_singularity_or_docker
475
- def test_miniwdl_self_test(self, extra_args: Optional[list[str]] = None) -> None:
664
+ def test_miniwdl_self_test(
665
+ self, tmp_path: Path, extra_args: Optional[list[str]] = None
666
+ ) -> None:
476
667
  """Test if the MiniWDL self test runs and produces the expected output."""
477
- wdl_file = os.path.abspath("src/toil/test/wdl/miniwdl_self_test/self_test.wdl")
478
- json_file = os.path.abspath("src/toil/test/wdl/miniwdl_self_test/inputs.json")
668
+ with get_data("test/wdl/miniwdl_self_test/self_test.wdl") as wdl_file:
669
+ with get_data("test/wdl/miniwdl_self_test/inputs.json") as json_file:
670
+
671
+ result_json = subprocess.check_output(
672
+ self.base_command
673
+ + [
674
+ str(wdl_file),
675
+ str(json_file),
676
+ "--logDebug",
677
+ "-o",
678
+ str(tmp_path),
679
+ "--outputDialect",
680
+ "miniwdl",
681
+ ]
682
+ + (extra_args or [])
683
+ )
684
+ result = json.loads(result_json)
685
+
686
+ # Expect MiniWDL-style output with a designated "dir"
687
+
688
+ assert "dir" in result
689
+ assert isinstance(result["dir"], str)
690
+ out_dir = result["dir"]
691
+
692
+ assert "outputs" in result
693
+ assert isinstance(result["outputs"], dict)
694
+ outputs = result["outputs"]
695
+
696
+ assert "hello_caller.message_files" in outputs
697
+ assert isinstance(outputs["hello_caller.message_files"], list)
698
+ assert len(outputs["hello_caller.message_files"]) == 2
699
+ for item in outputs["hello_caller.message_files"]:
700
+ # All the files should be strings in the "out" directory
701
+ assert isinstance(item, str), "File output must be a string"
702
+ assert item.startswith(
703
+ out_dir
704
+ ), "File output must be in the output directory"
705
+
706
+ # Look at the filename within that directory
707
+ name_in_out_dir = item[len(out_dir) :]
708
+
709
+ # Ity should contain the job name of "hello", so they are human-readable.
710
+ assert (
711
+ "hello" in name_in_out_dir
712
+ ), f"File output {name_in_out_dir} should have the originating task name in it"
713
+
714
+ # And it should not contain non-human-readable content.
715
+ #
716
+ # We use a threshold number of digits as a proxy for this, but
717
+ # don't try and get around this by just rolling other random
718
+ # strings; we want these outputs to be human-readable!!!
719
+ digit_count = len(
720
+ [c for c in name_in_out_dir if c in string.digits]
721
+ )
722
+ assert (
723
+ digit_count < 3
724
+ ), f"File output {name_in_out_dir} has {digit_count} digits, which is too many to be plausibly human-readable"
725
+
726
+ assert "hello_caller.messages" in outputs
727
+ assert outputs["hello_caller.messages"] == [
728
+ "Hello, Alyssa P. Hacker!",
729
+ "Hello, Ben Bitdiddle!",
730
+ ]
731
+
732
+ @needs_singularity_or_docker
733
+ def test_miniwdl_self_test_by_reference(self, tmp_path: Path) -> None:
734
+ """
735
+ Test if the MiniWDL self test works when passing input files by URL reference.
736
+ """
737
+ self.test_miniwdl_self_test(
738
+ tmp_path=tmp_path, extra_args=["--referenceInputs=True"]
739
+ )
740
+
741
+ @pytest.mark.integrative
742
+ @needs_singularity_or_docker
743
+ def test_dockstore_trs(
744
+ self, tmp_path: Path, extra_args: Optional[list[str]] = None
745
+ ) -> None:
746
+ wdl_file = "#workflow/github.com/dockstore/bcc2020-training/HelloWorld:master"
747
+ # Needs an input but doesn't provide a good one.
748
+ json_input = json.dumps(
749
+ {
750
+ "hello_world.hello.myName": "https://raw.githubusercontent.com/dockstore/bcc2020-training/refs/heads/master/wdl-training/exercise1/name.txt"
751
+ }
752
+ )
479
753
 
480
754
  result_json = subprocess.check_output(
481
755
  self.base_command
482
756
  + [
483
757
  wdl_file,
484
- json_file,
758
+ json_input,
485
759
  "--logDebug",
486
760
  "-o",
487
- self.output_dir,
761
+ str(tmp_path),
488
762
  "--outputDialect",
489
763
  "miniwdl",
490
764
  ]
@@ -492,89 +766,67 @@ class WDLTests(BaseWDLTest):
492
766
  )
493
767
  result = json.loads(result_json)
494
768
 
495
- # Expect MiniWDL-style output with a designated "dir"
496
-
497
- assert "dir" in result
498
- assert isinstance(result["dir"], str)
499
- out_dir = result["dir"]
500
-
501
- assert "outputs" in result
502
- assert isinstance(result["outputs"], dict)
503
- outputs = result["outputs"]
504
-
505
- assert "hello_caller.message_files" in outputs
506
- assert isinstance(outputs["hello_caller.message_files"], list)
507
- assert len(outputs["hello_caller.message_files"]) == 2
508
- for item in outputs["hello_caller.message_files"]:
509
- # All the files should be strings in the "out" directory
510
- assert isinstance(item, str), "File output must be a string"
511
- assert item.startswith(
512
- out_dir
513
- ), "File output must be in the output directory"
514
-
515
- # Look at the filename within that directory
516
- name_in_out_dir = item[len(out_dir) :]
517
-
518
- # Ity should contain the job name of "hello", so they are human-readable.
519
- assert (
520
- "hello" in name_in_out_dir
521
- ), f"File output {name_in_out_dir} should have the originating task name in it"
522
-
523
- # And it should not contain non-human-readable content.
524
- #
525
- # We use a threshold number of digits as a proxy for this, but
526
- # don't try and get around this by just rolling other random
527
- # strings; we want these outputs to be human-readable!!!
528
- digit_count = len([c for c in name_in_out_dir if c in string.digits])
529
- assert (
530
- digit_count < 3
531
- ), f"File output {name_in_out_dir} has {digit_count} digits, which is too many to be plausibly human-readable"
769
+ with open(result.get("outputs", {}).get("hello_world.helloFile")) as f:
770
+ result_text = f.read().strip()
532
771
 
533
- assert "hello_caller.messages" in outputs
534
- assert outputs["hello_caller.messages"] == [
535
- "Hello, Alyssa P. Hacker!",
536
- "Hello, Ben Bitdiddle!",
537
- ]
538
-
539
- @needs_singularity_or_docker
540
- def test_miniwdl_self_test_by_reference(self) -> None:
541
- """
542
- Test if the MiniWDL self test works when passing input files by URL reference.
543
- """
544
- self.test_miniwdl_self_test(extra_args=["--referenceInputs=True"])
772
+ assert result_text == "Hello World!\nMy name is potato."
545
773
 
774
+ # TODO: Should this move to the TRS/Dockstore tests file?
546
775
  @pytest.mark.integrative
547
776
  @needs_singularity_or_docker
548
- def test_dockstore_trs(self, extra_args: Optional[list[str]] = None) -> None:
777
+ def test_dockstore_metrics_publication(
778
+ self, tmp_path: Path, extra_args: Optional[list[str]] = None
779
+ ) -> None:
549
780
  wdl_file = "#workflow/github.com/dockstore/bcc2020-training/HelloWorld:master"
550
781
  # Needs an input but doesn't provide a good one.
551
- json_input = json.dumps({"hello_world.hello.myName": "https://raw.githubusercontent.com/dockstore/bcc2020-training/refs/heads/master/wdl-training/exercise1/name.txt"})
782
+ json_input = json.dumps(
783
+ {
784
+ "hello_world.hello.myName": "https://raw.githubusercontent.com/dockstore/bcc2020-training/refs/heads/master/wdl-training/exercise1/name.txt"
785
+ }
786
+ )
552
787
 
553
- result_json = subprocess.check_output(
554
- self.base_command + [wdl_file, json_input, '--logDebug', '-o', self.output_dir, '--outputDialect',
555
- 'miniwdl'] + (extra_args or []))
556
- result = json.loads(result_json)
788
+ env = dict(os.environ)
789
+ # Set credentials we got permission to publish from the Dockstore team,
790
+ # and work on the staging Dockstore.
791
+ env["TOIL_TRS_ROOT"] = "https://staging.dockstore.org"
792
+ env["TOIL_DOCKSTORE_TOKEN"] = "99cf5578ebe94b194d7864630a86258fa3d6cedcc17d757b5dd49e64ee3b68c3"
793
+ # Enable history for when <https://github.com/DataBiosphere/toil/pull/5258> merges
794
+ env["TOIL_HISTORY"] = "True"
557
795
 
558
- with open(result.get("outputs", {}).get("hello_world.helloFile")) as f:
559
- result_text = f.read().strip()
796
+ output_log = subprocess.check_output(
797
+ self.base_command
798
+ + [
799
+ wdl_file,
800
+ json_input,
801
+ "--logDebug",
802
+ "-o",
803
+ str(tmp_path),
804
+ "--outputDialect",
805
+ "miniwdl",
806
+ "--publishWorkflowMetrics=current",
807
+ ]
808
+ + (extra_args or []),
809
+ stderr=subprocess.STDOUT,
810
+ env=env,
811
+ )
560
812
 
561
- self.assertEqual(result_text, "Hello World!\nMy name is potato.")
813
+ assert b'Workflow metrics were accepted by Dockstore.' in output_log
562
814
 
563
815
  @slow
564
816
  @needs_docker_cuda
565
- def test_giraffe_deepvariant(self):
817
+ def test_giraffe_deepvariant(self, tmp_path: Path) -> None:
566
818
  """Test if Giraffe and GPU DeepVariant run. This could take 25 minutes."""
567
819
  # TODO: enable test if nvidia-container-runtime and Singularity are installed but Docker isn't.
568
820
 
569
- json_dir = self._createTempDir()
821
+ json_dir = tmp_path / "json"
822
+ json_dir.mkdir()
570
823
  base_uri = "https://raw.githubusercontent.com/vgteam/vg_wdl/65dd739aae765f5c4dedd14f2e42d5a263f9267a"
571
824
 
572
825
  wdl_file = f"{base_uri}/workflows/giraffe_and_deepvariant.wdl"
573
- json_file = os.path.abspath(os.path.join(json_dir, "inputs.json"))
574
- with open(json_file, "w") as fp:
826
+ json_file = json_dir / "inputs.json"
827
+ with json_file.open("w") as fp:
575
828
  # Write some inputs. We need to override the example inputs to use a GPU container, but that means we need absolute input URLs.
576
829
  json.dump(
577
- fp,
578
830
  {
579
831
  "GiraffeDeepVariant.INPUT_READ_FILE_1": f"{base_uri}/tests/small_sim_graph/reads_1.fastq.gz",
580
832
  "GiraffeDeepVariant.INPUT_READ_FILE_2": f"{base_uri}/tests/small_sim_graph/reads_2.fastq.gz",
@@ -587,11 +839,19 @@ class WDLTests(BaseWDLTest):
587
839
  "GiraffeDeepVariant.OUTPUT_GAF": True,
588
840
  "GiraffeDeepVariant.runDeepVariantCallVariants.in_dv_gpu_container": "google/deepvariant:1.3.0-gpu",
589
841
  },
842
+ fp,
590
843
  )
591
844
 
592
845
  result_json = subprocess.check_output(
593
846
  self.base_command
594
- + [wdl_file, json_file, "-o", self.output_dir, "--outputDialect", "miniwdl"]
847
+ + [
848
+ wdl_file,
849
+ json_file,
850
+ "-o",
851
+ str(tmp_path / "out"),
852
+ "--outputDialect",
853
+ "miniwdl",
854
+ ]
595
855
  )
596
856
  result = json.loads(result_json)
597
857
 
@@ -611,13 +871,12 @@ class WDLTests(BaseWDLTest):
611
871
 
612
872
  @slow
613
873
  @needs_singularity_or_docker
614
- def test_giraffe(self):
874
+ def test_giraffe(self, tmp_path: Path) -> None:
615
875
  """Test if Giraffe runs. This could take 12 minutes. Also we scale it down but it still demands lots of memory."""
616
876
  # TODO: enable test if nvidia-container-runtime and Singularity are installed but Docker isn't.
617
877
  # TODO: Reduce memory requests with custom/smaller inputs.
618
878
  # TODO: Skip if node lacks enough memory.
619
879
 
620
- json_dir = self._createTempDir()
621
880
  base_uri = "https://raw.githubusercontent.com/vgteam/vg_wdl/65dd739aae765f5c4dedd14f2e42d5a263f9267a"
622
881
  wdl_file = f"{base_uri}/workflows/giraffe.wdl"
623
882
  json_file = f"{base_uri}/params/giraffe.json"
@@ -628,7 +887,7 @@ class WDLTests(BaseWDLTest):
628
887
  wdl_file,
629
888
  json_file,
630
889
  "-o",
631
- self.output_dir,
890
+ str(tmp_path),
632
891
  "--outputDialect",
633
892
  "miniwdl",
634
893
  "--scale",
@@ -654,26 +913,43 @@ class WDLTests(BaseWDLTest):
654
913
 
655
914
  @needs_singularity_or_docker
656
915
  @needs_google_storage
657
- def test_gs_uri(self):
916
+ def test_gs_uri(self, tmp_path: Path) -> None:
658
917
  """Test if Toil can access Google Storage URIs."""
659
- wdl = os.path.abspath("src/toil/test/wdl/md5sum/md5sum.1.0.wdl")
660
- json_file = os.path.abspath("src/toil/test/wdl/md5sum/md5sum-gs.json")
661
-
662
- result_json = subprocess.check_output(
663
- self.base_command + [wdl, json_file, "-o", self.output_dir, "--logDebug"]
664
- )
665
- result = json.loads(result_json)
666
-
667
- assert "ga4ghMd5.value" in result
668
- assert isinstance(result["ga4ghMd5.value"], str)
669
- assert os.path.exists(result["ga4ghMd5.value"])
670
- assert os.path.basename(result["ga4ghMd5.value"]) == "md5sum.txt"
671
-
672
-
673
- class WDLToilBenchTests(ToilTest):
918
+ with get_data("test/wdl/md5sum/md5sum.1.0.wdl") as wdl:
919
+ with get_data("test/wdl/md5sum/md5sum-gs.json") as json_file:
920
+ result_json = subprocess.check_output(
921
+ self.base_command
922
+ + [str(wdl), str(json_file), "-o", str(tmp_path), "--logDebug"]
923
+ )
924
+ result = json.loads(result_json)
925
+
926
+ assert "ga4ghMd5.value" in result
927
+ assert isinstance(result["ga4ghMd5.value"], str)
928
+ assert os.path.exists(result["ga4ghMd5.value"])
929
+ assert os.path.basename(result["ga4ghMd5.value"]) == "md5sum.txt"
930
+
931
+ def test_check(self, tmp_path: Path) -> None:
932
+ """Test that Toil's lint check works"""
933
+ with get_data("test/wdl/lint_error.wdl") as wdl:
934
+ out = subprocess.check_output(
935
+ self.base_command + [str(wdl), "-o", str(tmp_path), "--logInfo"], stderr=subprocess.STDOUT)
936
+
937
+ assert b'UnnecessaryQuantifier' in out
938
+
939
+ p = subprocess.Popen(
940
+ self.base_command + [wdl, "--strict=True", "--logCritical"], stderr=subprocess.PIPE)
941
+ # Not actually a test assert; we need this to teach MyPy that we
942
+ # get an stderr when we pass stderr=subprocess.PIPE.
943
+ assert p.stderr is not None
944
+ stderr = p.stderr.read()
945
+ p.wait()
946
+ assert p.returncode == 2
947
+ assert b'Workflow did not pass linting in strict mode' in stderr
948
+
949
+ class TestWDLToilBench(unittest.TestCase):
674
950
  """Tests for Toil's MiniWDL-based implementation that don't run workflows."""
675
951
 
676
- def test_coalesce(self):
952
+ def test_coalesce(self) -> None:
677
953
  """
678
954
  Test if WDLSectionJob can coalesce WDL decls.
679
955
 
@@ -792,7 +1068,7 @@ class WDLToilBenchTests(ToilTest):
792
1068
 
793
1069
  return WDL.Expr.String(pos, parts)
794
1070
 
795
- def test_remove_common_leading_whitespace(self):
1071
+ def test_remove_common_leading_whitespace(self) -> None:
796
1072
  """
797
1073
  Make sure leading whitespace removal works properly.
798
1074
  """
@@ -907,7 +1183,7 @@ class WDLToilBenchTests(ToilTest):
907
1183
  trimmed = remove_common_leading_whitespace(expr)
908
1184
  assert trimmed.command == True
909
1185
 
910
- def test_choose_human_readable_directory(self):
1186
+ def test_choose_human_readable_directory(self) -> None:
911
1187
  """
912
1188
  Test to make sure that we pick sensible but non-colliding directories to put files in.
913
1189
  """
@@ -931,18 +1207,18 @@ class WDLToilBenchTests(ToilTest):
931
1207
  same_id = choose_human_readable_directory(
932
1208
  "root", "taskname", "111-222-333", state
933
1209
  )
934
- self.assertEqual(same_id, first_chosen)
1210
+ assert same_id == first_chosen
935
1211
 
936
1212
  # If we use a different ID we should get a different result still obeying the constraints
937
1213
  diff_id = choose_human_readable_directory(
938
1214
  "root", "taskname", "222-333-444", state
939
1215
  )
940
- self.assertNotEqual(diff_id, first_chosen)
1216
+ assert diff_id != first_chosen
941
1217
  assert diff_id.startswith("root")
942
1218
  assert "taskname" in diff_id
943
1219
  assert "222-333-444" not in diff_id
944
1220
 
945
- def test_uri_packing(self):
1221
+ def test_uri_packing(self) -> None:
946
1222
  """
947
1223
  Test to make sure Toil URI packing brings through the required information.
948
1224
  """
@@ -960,53 +1236,50 @@ class WDLToilBenchTests(ToilTest):
960
1236
  unpacked = unpack_toil_uri(uri)
961
1237
 
962
1238
  # Make sure we got what we put in
963
- self.assertEqual(unpacked[0], file_id)
964
- self.assertEqual(unpacked[0].size, file_id.size)
965
- self.assertEqual(unpacked[0].executable, file_id.executable)
1239
+ assert unpacked[0] == file_id
1240
+ assert unpacked[0].size == file_id.size
1241
+ assert unpacked[0].executable == file_id.executable
966
1242
 
967
- self.assertEqual(unpacked[1], task_path)
1243
+ assert unpacked[1] == task_path
968
1244
 
969
1245
  # TODO: We don't make the UUIDs back into UUID objects
970
- self.assertEqual(unpacked[2], str(dir_id))
1246
+ assert unpacked[2] == str(dir_id)
971
1247
 
972
- self.assertEqual(unpacked[3], file_basename)
1248
+ assert unpacked[3] == file_basename
973
1249
 
974
- def test_disk_parse(self):
1250
+ def test_disk_parse(self) -> None:
975
1251
  """
976
1252
  Test to make sure the disk parsing is correct
977
1253
  """
978
1254
  # Test cromwell compatibility
979
1255
  spec = "local-disk 5 SSD"
980
1256
  specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
981
- self.assertEqual(specified_mount_point, None)
982
- self.assertEqual(part_size, 5)
983
- self.assertEqual(part_suffix, "GB")
1257
+ assert specified_mount_point is None
1258
+ assert part_size == 5
1259
+ assert part_suffix == "GB"
984
1260
 
985
1261
  # Test spec conformance
986
1262
  # https://github.com/openwdl/wdl/blob/e43e042104b728df1f1ad6e6145945d2b32331a6/SPEC.md?plain=1#L5072-L5082
987
1263
  spec = "10"
988
1264
  specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
989
- self.assertEqual(specified_mount_point, None)
990
- self.assertEqual(part_size, 10)
991
- self.assertEqual(part_suffix, "GiB") # WDL spec default
1265
+ assert specified_mount_point is None
1266
+ assert part_size == 10
1267
+ assert part_suffix == "GiB" # WDL spec default
992
1268
 
993
1269
  spec = "1 MB"
994
1270
  specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
995
- self.assertEqual(specified_mount_point, None)
996
- self.assertEqual(part_size, 1)
997
- self.assertEqual(part_suffix, "MB")
1271
+ assert specified_mount_point is None
1272
+ assert part_size == 1
1273
+ assert part_suffix == "MB"
998
1274
 
999
1275
  spec = "MOUNT_POINT 3"
1000
1276
  specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
1001
- self.assertEqual(specified_mount_point, "MOUNT_POINT")
1002
- self.assertEqual(part_size, 3)
1003
- self.assertEqual(part_suffix, "GiB")
1277
+ assert specified_mount_point == "MOUNT_POINT"
1278
+ assert part_size == 3
1279
+ assert part_suffix == "GiB"
1004
1280
 
1005
1281
  spec = "MOUNT_POINT 2 MB"
1006
1282
  specified_mount_point, part_size, part_suffix = parse_disks(spec, spec)
1007
- self.assertEqual(specified_mount_point, "MOUNT_POINT")
1008
- self.assertEqual(part_size, 2)
1009
- self.assertEqual(part_suffix, "MB")
1010
-
1011
- if __name__ == "__main__":
1012
- unittest.main() # run all tests
1283
+ assert specified_mount_point == "MOUNT_POINT"
1284
+ assert part_size == 2
1285
+ assert part_suffix == "MB"