toil 8.0.0__py3-none-any.whl → 8.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. toil/__init__.py +4 -39
  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/options.py +1 -0
  11. toil/batchSystems/singleMachine.py +1 -1
  12. toil/batchSystems/slurm.py +229 -84
  13. toil/bus.py +5 -3
  14. toil/common.py +198 -54
  15. toil/cwl/cwltoil.py +32 -11
  16. toil/job.py +110 -86
  17. toil/jobStores/abstractJobStore.py +24 -3
  18. toil/jobStores/aws/jobStore.py +46 -10
  19. toil/jobStores/fileJobStore.py +25 -1
  20. toil/jobStores/googleJobStore.py +104 -30
  21. toil/leader.py +9 -0
  22. toil/lib/accelerators.py +3 -1
  23. toil/lib/aws/session.py +14 -3
  24. toil/lib/aws/utils.py +92 -35
  25. toil/lib/aws/utils.py.orig +504 -0
  26. toil/lib/bioio.py +1 -1
  27. toil/lib/docker.py +252 -91
  28. toil/lib/dockstore.py +387 -0
  29. toil/lib/ec2nodes.py +3 -2
  30. toil/lib/exceptions.py +5 -3
  31. toil/lib/history.py +1345 -0
  32. toil/lib/history_submission.py +695 -0
  33. toil/lib/io.py +56 -23
  34. toil/lib/misc.py +25 -1
  35. toil/lib/resources.py +2 -1
  36. toil/lib/retry.py +10 -10
  37. toil/lib/threading.py +11 -10
  38. toil/lib/{integration.py → trs.py} +95 -46
  39. toil/lib/web.py +38 -0
  40. toil/options/common.py +25 -2
  41. toil/options/cwl.py +10 -0
  42. toil/options/wdl.py +11 -0
  43. toil/provisioners/gceProvisioner.py +4 -4
  44. toil/server/api_spec/LICENSE +201 -0
  45. toil/server/api_spec/README.rst +5 -0
  46. toil/server/cli/wes_cwl_runner.py +5 -4
  47. toil/server/utils.py +2 -3
  48. toil/statsAndLogging.py +35 -1
  49. toil/test/__init__.py +275 -115
  50. toil/test/batchSystems/batchSystemTest.py +227 -205
  51. toil/test/batchSystems/test_slurm.py +199 -2
  52. toil/test/cactus/pestis.tar.gz +0 -0
  53. toil/test/conftest.py +7 -0
  54. toil/test/cwl/2.fasta +11 -0
  55. toil/test/cwl/2.fastq +12 -0
  56. toil/test/cwl/conftest.py +39 -0
  57. toil/test/cwl/cwlTest.py +1015 -780
  58. toil/test/cwl/directory/directory/file.txt +15 -0
  59. toil/test/cwl/download_directory_file.json +4 -0
  60. toil/test/cwl/download_directory_s3.json +4 -0
  61. toil/test/cwl/download_file.json +6 -0
  62. toil/test/cwl/download_http.json +6 -0
  63. toil/test/cwl/download_https.json +6 -0
  64. toil/test/cwl/download_s3.json +6 -0
  65. toil/test/cwl/download_subdirectory_file.json +5 -0
  66. toil/test/cwl/download_subdirectory_s3.json +5 -0
  67. toil/test/cwl/empty.json +1 -0
  68. toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
  69. toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
  70. toil/test/cwl/optional-file-exists.json +6 -0
  71. toil/test/cwl/optional-file-missing.json +6 -0
  72. toil/test/cwl/optional-file.cwl +18 -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/lib/aws/test_iam.py +3 -1
  123. toil/test/lib/dockerTest.py +205 -122
  124. toil/test/lib/test_history.py +236 -0
  125. toil/test/lib/test_trs.py +161 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
  127. toil/test/provisioners/clusterTest.py +4 -4
  128. toil/test/provisioners/gceProvisionerTest.py +16 -14
  129. toil/test/sort/sort.py +4 -1
  130. toil/test/src/busTest.py +17 -17
  131. toil/test/src/deferredFunctionTest.py +145 -132
  132. toil/test/src/importExportFileTest.py +71 -63
  133. toil/test/src/jobEncapsulationTest.py +27 -28
  134. toil/test/src/jobServiceTest.py +149 -133
  135. toil/test/src/jobTest.py +219 -211
  136. toil/test/src/miscTests.py +66 -60
  137. toil/test/src/promisedRequirementTest.py +163 -169
  138. toil/test/src/regularLogTest.py +24 -24
  139. toil/test/src/resourceTest.py +82 -76
  140. toil/test/src/restartDAGTest.py +51 -47
  141. toil/test/src/resumabilityTest.py +24 -19
  142. toil/test/src/retainTempDirTest.py +60 -57
  143. toil/test/src/systemTest.py +17 -13
  144. toil/test/src/threadingTest.py +29 -32
  145. toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
  146. toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
  147. toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
  148. toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
  149. toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
  150. toil/test/utils/toilDebugTest.py +117 -102
  151. toil/test/utils/toilKillTest.py +54 -53
  152. toil/test/utils/utilsTest.py +303 -229
  153. toil/test/wdl/lint_error.wdl +9 -0
  154. toil/test/wdl/md5sum/empty_file.json +1 -0
  155. toil/test/wdl/md5sum/md5sum-gs.json +1 -0
  156. toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
  157. toil/test/wdl/md5sum/md5sum.input +1 -0
  158. toil/test/wdl/md5sum/md5sum.json +1 -0
  159. toil/test/wdl/md5sum/md5sum.wdl +25 -0
  160. toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
  161. toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
  162. toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
  163. toil/test/wdl/standard_library/as_map.json +16 -0
  164. toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
  165. toil/test/wdl/standard_library/as_pairs.json +7 -0
  166. toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
  167. toil/test/wdl/standard_library/ceil.json +3 -0
  168. toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
  169. toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
  170. toil/test/wdl/standard_library/collect_by_key.json +1 -0
  171. toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
  172. toil/test/wdl/standard_library/cross.json +11 -0
  173. toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
  174. toil/test/wdl/standard_library/flatten.json +7 -0
  175. toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
  176. toil/test/wdl/standard_library/floor.json +3 -0
  177. toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
  178. toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
  179. toil/test/wdl/standard_library/keys.json +8 -0
  180. toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
  181. toil/test/wdl/standard_library/length.json +7 -0
  182. toil/test/wdl/standard_library/length_as_input.wdl +16 -0
  183. toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
  184. toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
  185. toil/test/wdl/standard_library/length_invalid.json +3 -0
  186. toil/test/wdl/standard_library/range.json +3 -0
  187. toil/test/wdl/standard_library/range_0.json +3 -0
  188. toil/test/wdl/standard_library/range_as_input.wdl +17 -0
  189. toil/test/wdl/standard_library/range_invalid.json +3 -0
  190. toil/test/wdl/standard_library/read_boolean.json +3 -0
  191. toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
  192. toil/test/wdl/standard_library/read_float.json +3 -0
  193. toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
  194. toil/test/wdl/standard_library/read_int.json +3 -0
  195. toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
  196. toil/test/wdl/standard_library/read_json.json +3 -0
  197. toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
  198. toil/test/wdl/standard_library/read_lines.json +3 -0
  199. toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
  200. toil/test/wdl/standard_library/read_map.json +3 -0
  201. toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
  202. toil/test/wdl/standard_library/read_string.json +3 -0
  203. toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
  204. toil/test/wdl/standard_library/read_tsv.json +3 -0
  205. toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
  206. toil/test/wdl/standard_library/round.json +3 -0
  207. toil/test/wdl/standard_library/round_as_command.wdl +16 -0
  208. toil/test/wdl/standard_library/round_as_input.wdl +16 -0
  209. toil/test/wdl/standard_library/size.json +3 -0
  210. toil/test/wdl/standard_library/size_as_command.wdl +17 -0
  211. toil/test/wdl/standard_library/size_as_output.wdl +36 -0
  212. toil/test/wdl/standard_library/stderr.json +3 -0
  213. toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
  214. toil/test/wdl/standard_library/stdout.json +3 -0
  215. toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
  216. toil/test/wdl/standard_library/sub.json +3 -0
  217. toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
  218. toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
  219. toil/test/wdl/standard_library/transpose.json +6 -0
  220. toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
  221. toil/test/wdl/standard_library/write_json.json +6 -0
  222. toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
  223. toil/test/wdl/standard_library/write_lines.json +7 -0
  224. toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
  225. toil/test/wdl/standard_library/write_map.json +6 -0
  226. toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
  227. toil/test/wdl/standard_library/write_tsv.json +6 -0
  228. toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
  229. toil/test/wdl/standard_library/zip.json +12 -0
  230. toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
  231. toil/test/wdl/test.csv +3 -0
  232. toil/test/wdl/test.tsv +3 -0
  233. toil/test/wdl/testfiles/croo.wdl +38 -0
  234. toil/test/wdl/testfiles/drop_files.wdl +62 -0
  235. toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
  236. toil/test/wdl/testfiles/empty.txt +0 -0
  237. toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
  238. toil/test/wdl/testfiles/random.wdl +66 -0
  239. toil/test/wdl/testfiles/string_file_coercion.json +1 -0
  240. toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
  241. toil/test/wdl/testfiles/test.json +4 -0
  242. toil/test/wdl/testfiles/test_boolean.txt +1 -0
  243. toil/test/wdl/testfiles/test_float.txt +1 -0
  244. toil/test/wdl/testfiles/test_int.txt +1 -0
  245. toil/test/wdl/testfiles/test_lines.txt +5 -0
  246. toil/test/wdl/testfiles/test_map.txt +2 -0
  247. toil/test/wdl/testfiles/test_string.txt +1 -0
  248. toil/test/wdl/testfiles/url_to_file.wdl +13 -0
  249. toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
  250. toil/test/wdl/testfiles/vocab.json +1 -0
  251. toil/test/wdl/testfiles/vocab.wdl +66 -0
  252. toil/test/wdl/testfiles/wait.wdl +34 -0
  253. toil/test/wdl/wdl_specification/type_pair.json +23 -0
  254. toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
  255. toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
  256. toil/test/wdl/wdl_specification/v1_spec.json +1 -0
  257. toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
  258. toil/test/wdl/wdltoil_test.py +681 -408
  259. toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
  260. toil/version.py +10 -10
  261. toil/wdl/wdltoil.py +350 -123
  262. toil/worker.py +113 -33
  263. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/METADATA +13 -7
  264. toil-8.2.0.dist-info/RECORD +439 -0
  265. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
  266. toil/test/lib/test_integration.py +0 -104
  267. toil-8.0.0.dist-info/RECORD +0 -253
  268. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
  269. {toil-8.0.0.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
  270. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/top_level.txt +0 -0
toil/test/cwl/cwlTest.py CHANGED
@@ -12,6 +12,7 @@
12
12
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
+ from collections.abc import Generator
15
16
  import json
16
17
  import logging
17
18
  import os
@@ -20,7 +21,6 @@ import shutil
20
21
  import stat
21
22
  import subprocess
22
23
  import sys
23
- import unittest
24
24
  import uuid
25
25
  import zipfile
26
26
  from functools import partial
@@ -50,22 +50,23 @@ from toil.fileStores import FileID
50
50
  from toil.fileStores.abstractFileStore import AbstractFileStore
51
51
  from toil.lib.threading import cpu_count
52
52
  from toil.test import (
53
- ToilTest,
54
- needs_aws_s3,
55
- needs_cwl,
56
- needs_docker,
57
- needs_docker_cuda,
58
- needs_gridengine,
59
- needs_kubernetes,
60
- needs_local_cuda,
61
- needs_lsf,
62
- needs_mesos,
63
- needs_online,
64
- needs_singularity_or_docker,
65
- needs_slurm,
66
- needs_torque,
67
- needs_wes_server,
68
- slow,
53
+ get_data,
54
+ )
55
+ from toil.test import (
56
+ pslow as slow,
57
+ pneeds_docker as needs_docker,
58
+ pneeds_cwl as needs_cwl,
59
+ pneeds_aws_s3 as needs_aws_s3,
60
+ pneeds_docker_cuda as needs_docker_cuda,
61
+ pneeds_gridengine as needs_gridengine,
62
+ pneeds_kubernetes as needs_kubernetes,
63
+ pneeds_local_cuda as needs_local_cuda,
64
+ pneeds_lsf as needs_lsf,
65
+ pneeds_mesos as needs_mesos,
66
+ pneeds_online as needs_online,
67
+ pneeds_slurm as needs_slurm,
68
+ pneeds_torque as needs_torque,
69
+ pneeds_wes_server as needs_wes_server,
69
70
  )
70
71
 
71
72
  log = logging.getLogger(__name__)
@@ -222,72 +223,58 @@ def run_conformance_tests(
222
223
  log.info("Unsuccessful return code is OK")
223
224
 
224
225
 
225
- TesterFuncType = Callable[[str, str, "CWLObjectType"], None]
226
+ TesterFuncType = Callable[[Path, Path, "CWLObjectType", Path], None]
226
227
 
227
228
 
228
229
  @needs_cwl
229
- class CWLWorkflowTest(ToilTest):
230
+ @pytest.mark.cwl
231
+ class TestCWLWorkflow:
230
232
  """
231
233
  CWL tests included in Toil that don't involve the whole CWL conformance
232
234
  test suite. Tests Toil-specific functions like URL types supported for
233
235
  inputs.
234
236
  """
235
237
 
236
- def setUp(self) -> None:
237
- """Runs anew before each test to create farm fresh temp dirs."""
238
- self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
239
- os.makedirs(self.outDir)
240
- self.rootDir = self._projectRootPath()
241
- self.jobStoreDir = f"./jobstore-{str(uuid.uuid4())}"
242
-
243
- def tearDown(self) -> None:
244
- """Clean up outputs."""
245
- if os.path.exists(self.outDir):
246
- shutil.rmtree(self.outDir)
247
- if os.path.exists(self.jobStoreDir):
248
- shutil.rmtree(self.jobStoreDir)
249
- unittest.TestCase.tearDown(self)
250
-
251
238
  def test_cwl_cmdline_input(self) -> None:
252
239
  """
253
240
  Test that running a CWL workflow with inputs specified on the command line passes.
254
241
  """
255
242
  from toil.cwl import cwltoil
256
243
 
257
- cwlfile = "src/toil/test/cwl/conditional_wf.cwl"
258
- args = [cwlfile, "--message", "str", "--sleep", "2"]
259
- st = StringIO()
260
- # If the workflow runs, it must have had options
261
- cwltoil.main(args, stdout=st)
244
+ with get_data("test/cwl/conditional_wf.cwl") as cwlfile:
245
+ args = [str(cwlfile), "--message", "str", "--sleep", "2"]
246
+ st = StringIO()
247
+ # If the workflow runs, it must have had options
248
+ cwltoil.main(args, stdout=st)
262
249
 
263
250
  def _tester(
264
251
  self,
265
- cwlfile: str,
266
- jobfile: str,
252
+ cwlfile: Path,
253
+ jobfile: Path,
267
254
  expect: "CWLObjectType",
268
- main_args: list[str] = [],
255
+ outdir: Path,
269
256
  out_name: str = "output",
270
- output_here: bool = False,
257
+ main_args: Optional[list[str]] = None,
271
258
  ) -> None:
272
259
  from toil.cwl import cwltoil
273
260
 
274
261
  st = StringIO()
275
- main_args = main_args[:]
276
- if not output_here:
277
- # Don't just dump output in the working directory.
278
- main_args.extend(["--logDebug", "--outdir", self.outDir])
279
- main_args.extend(
262
+ real_main_args = main_args or []
263
+ real_main_args.extend(
280
264
  [
281
- os.path.join(self.rootDir, cwlfile),
282
- os.path.join(self.rootDir, jobfile),
265
+ "--logDebug",
266
+ "--outdir",
267
+ str(outdir),
268
+ str(cwlfile),
269
+ str(jobfile),
283
270
  ]
284
271
  )
285
- cwltoil.main(main_args, stdout=st)
272
+ cwltoil.main(real_main_args, stdout=st)
286
273
  out = json.loads(st.getvalue())
287
274
  out.get(out_name, {}).pop("http://commonwl.org/cwltool#generation", None)
288
275
  out.get(out_name, {}).pop("nameext", None)
289
276
  out.get(out_name, {}).pop("nameroot", None)
290
- self.assertEqual(out, expect)
277
+ assert out == expect
291
278
 
292
279
  for k, v in expect.items():
293
280
  if (
@@ -298,11 +285,11 @@ class CWLWorkflowTest(ToilTest):
298
285
  ):
299
286
  # This is a top-level output file.
300
287
  # None of our output files should be executable.
301
- self.assertTrue(os.path.exists(v["path"]))
302
- self.assertFalse(os.stat(v["path"]).st_mode & stat.S_IXUSR)
288
+ assert os.path.exists(v["path"]) is True
289
+ assert (os.stat(v["path"]).st_mode & stat.S_IXUSR) == 0
303
290
 
304
291
  def _debug_worker_tester(
305
- self, cwlfile: str, jobfile: str, expect: "CWLObjectType"
292
+ self, cwlfile: Path, jobfile: Path, expect: "CWLObjectType", outdir: Path
306
293
  ) -> None:
307
294
  from toil.cwl import cwltoil
308
295
 
@@ -311,9 +298,9 @@ class CWLWorkflowTest(ToilTest):
311
298
  [
312
299
  "--debugWorker",
313
300
  "--outdir",
314
- self.outDir,
315
- os.path.join(self.rootDir, cwlfile),
316
- os.path.join(self.rootDir, jobfile),
301
+ str(outdir),
302
+ str(cwlfile),
303
+ str(jobfile),
317
304
  ],
318
305
  stdout=st,
319
306
  )
@@ -321,172 +308,216 @@ class CWLWorkflowTest(ToilTest):
321
308
  out["output"].pop("http://commonwl.org/cwltool#generation", None)
322
309
  out["output"].pop("nameext", None)
323
310
  out["output"].pop("nameroot", None)
324
- self.assertEqual(out, expect)
325
-
326
- def revsort(self, cwl_filename: str, tester_fn: TesterFuncType) -> None:
327
- tester_fn(
328
- "src/toil/test/cwl/" + cwl_filename,
329
- "src/toil/test/cwl/revsort-job.json",
330
- self._expected_revsort_output(self.outDir),
331
- )
332
-
333
- def revsort_no_checksum(self, cwl_filename: str, tester_fn: TesterFuncType) -> None:
334
- tester_fn(
335
- "src/toil/test/cwl/" + cwl_filename,
336
- "src/toil/test/cwl/revsort-job.json",
337
- self._expected_revsort_nochecksum_output(self.outDir),
338
- )
339
-
340
- def download(self, inputs: str, tester_fn: TesterFuncType) -> None:
341
- input_location = os.path.join("src/toil/test/cwl", inputs)
342
- tester_fn(
343
- "src/toil/test/cwl/download.cwl",
344
- input_location,
345
- self._expected_download_output(self.outDir),
346
- )
311
+ assert out == expect
347
312
 
348
- def load_contents(self, inputs: str, tester_fn: TesterFuncType) -> None:
349
- input_location = os.path.join("src/toil/test/cwl", inputs)
350
- tester_fn(
351
- "src/toil/test/cwl/load_contents.cwl",
352
- input_location,
353
- self._expected_load_contents_output(self.outDir),
354
- )
355
-
356
- def download_directory(self, inputs: str, tester_fn: TesterFuncType) -> None:
357
- input_location = os.path.join("src/toil/test/cwl", inputs)
358
- tester_fn(
359
- "src/toil/test/cwl/download_directory.cwl",
360
- input_location,
361
- self._expected_download_output(self.outDir),
362
- )
363
-
364
- def download_subdirectory(self, inputs: str, tester_fn: TesterFuncType) -> None:
365
- input_location = os.path.join("src/toil/test/cwl", inputs)
366
- tester_fn(
367
- "src/toil/test/cwl/download_subdirectory.cwl",
368
- input_location,
369
- self._expected_download_output(self.outDir),
370
- )
371
-
372
- def test_mpi(self) -> None:
313
+ def revsort(
314
+ self, cwl_filename: str, tester_fn: TesterFuncType, out_dir: Path
315
+ ) -> None:
316
+ with get_data(f"test/cwl/{cwl_filename}") as cwl_file:
317
+ with get_data("test/cwl/revsort-job.json") as job_file:
318
+ tester_fn(
319
+ cwl_file, job_file, self._expected_revsort_output(out_dir), out_dir
320
+ )
321
+
322
+ def revsort_no_checksum(
323
+ self, cwl_filename: str, tester_fn: TesterFuncType, out_dir: Path
324
+ ) -> None:
325
+ with get_data(f"test/cwl/{cwl_filename}") as cwl_file:
326
+ with get_data("test/cwl/revsort-job.json") as job_file:
327
+ tester_fn(
328
+ cwl_file,
329
+ job_file,
330
+ self._expected_revsort_nochecksum_output(out_dir),
331
+ out_dir,
332
+ )
333
+
334
+ def download(self, inputs: str, tester_fn: TesterFuncType, out_dir: Path) -> None:
335
+ with get_data(f"test/cwl/{inputs}") as input_location:
336
+ with get_data("test/cwl/download.cwl") as cwl_file:
337
+ tester_fn(
338
+ cwl_file,
339
+ input_location,
340
+ self._expected_download_output(out_dir),
341
+ out_dir,
342
+ )
343
+
344
+ def load_contents(
345
+ self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
346
+ ) -> None:
347
+ with get_data(f"test/cwl/{inputs}") as input_location:
348
+ with get_data("test/cwl/load_contents.cwl") as cwl_file:
349
+ tester_fn(
350
+ cwl_file,
351
+ input_location,
352
+ self._expected_load_contents_output(out_dir),
353
+ out_dir,
354
+ )
355
+
356
+ def download_directory(
357
+ self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
358
+ ) -> None:
359
+ with get_data(f"test/cwl/{inputs}") as input_location:
360
+ with get_data("test/cwl/download_directory.cwl") as cwl_file:
361
+ tester_fn(
362
+ cwl_file,
363
+ input_location,
364
+ self._expected_download_output(out_dir),
365
+ out_dir,
366
+ )
367
+
368
+ def download_subdirectory(
369
+ self, inputs: str, tester_fn: TesterFuncType, out_dir: Path
370
+ ) -> None:
371
+ with get_data(f"test/cwl/{inputs}") as input_location:
372
+ with get_data("test/cwl/download_subdirectory.cwl") as cwl_file:
373
+ tester_fn(
374
+ cwl_file,
375
+ input_location,
376
+ self._expected_download_output(out_dir),
377
+ out_dir,
378
+ )
379
+
380
+ def test_mpi(self, tmp_path: Path) -> None:
373
381
  from toil.cwl import cwltoil
374
382
 
375
383
  stdout = StringIO()
376
- main_args = [
377
- "--outdir",
378
- self.outDir,
379
- "--enable-dev",
380
- "--enable-ext",
381
- "--mpi-config-file",
382
- os.path.join(self.rootDir, "src/toil/test/cwl/mock_mpi/fake_mpi.yml"),
383
- os.path.join(self.rootDir, "src/toil/test/cwl/mpi_simple.cwl"),
384
- ]
385
- path = os.environ["PATH"]
386
- os.environ["PATH"] = f"{path}:{self.rootDir}/src/toil/test/cwl/mock_mpi/"
387
- cwltoil.main(main_args, stdout=stdout)
388
- os.environ["PATH"] = path
389
- out = json.loads(stdout.getvalue())
390
- with open(out.get("pids", {}).get("location")[len("file://") :]) as f:
391
- two_pids = [int(i) for i in f.read().split()]
392
- self.assertEqual(len(two_pids), 2)
393
- self.assertTrue(isinstance(two_pids[0], int))
394
- self.assertTrue(isinstance(two_pids[1], int))
384
+ with get_data("test/cwl/mock_mpi/fake_mpi.yml") as mpi_config_file:
385
+ with get_data("test/cwl/mpi_simple.cwl") as cwl_file:
386
+ with get_data("test/cwl/mock_mpi/fake_mpi_run.py") as fake_mpi_run:
387
+ main_args = [
388
+ "--logDebug",
389
+ "--outdir",
390
+ str(tmp_path),
391
+ "--enable-dev",
392
+ "--enable-ext",
393
+ "--mpi-config-file",
394
+ str(mpi_config_file),
395
+ str(cwl_file),
396
+ ]
397
+ path = os.environ["PATH"]
398
+ os.environ["PATH"] = f"{path}:{fake_mpi_run.parent}"
399
+ cwltoil.main(main_args, stdout=stdout)
400
+ os.environ["PATH"] = path
401
+ stdout_text = stdout.getvalue()
402
+ assert "pids" in stdout_text
403
+ out = json.loads(stdout_text)
404
+ with open(
405
+ out.get("pids", {}).get("location")[len("file://") :]
406
+ ) as f:
407
+ two_pids = [int(i) for i in f.read().split()]
408
+ assert len(two_pids) == 2
409
+ assert isinstance(two_pids[0], int)
410
+ assert isinstance(two_pids[1], int)
395
411
 
396
412
  @needs_aws_s3
397
- def test_s3_as_secondary_file(self) -> None:
413
+ @pytest.mark.aws_s3
414
+ @pytest.mark.online
415
+ def test_s3_as_secondary_file(self, tmp_path: Path) -> None:
398
416
  from toil.cwl import cwltoil
399
417
 
400
418
  stdout = StringIO()
401
- main_args = [
402
- "--outdir",
403
- self.outDir,
404
- os.path.join(self.rootDir, "src/toil/test/cwl/s3_secondary_file.cwl"),
405
- os.path.join(self.rootDir, "src/toil/test/cwl/s3_secondary_file.json"),
406
- ]
407
- cwltoil.main(main_args, stdout=stdout)
408
- out = json.loads(stdout.getvalue())
409
- self.assertEqual(
410
- out["output"]["checksum"], "sha1$d14dd02e354918b4776b941d154c18ebc15b9b38"
411
- )
412
- self.assertEqual(out["output"]["size"], 24)
413
- with open(out["output"]["location"][len("file://") :]) as f:
414
- self.assertEqual(f.read().strip(), "When is s4 coming out?")
419
+ with get_data("test/cwl/s3_secondary_file.cwl") as cwl_file:
420
+ with get_data("test/cwl/s3_secondary_file.json") as inputs_file:
421
+ main_args = ["--outdir", str(tmp_path), str(cwl_file), str(inputs_file)]
422
+ cwltoil.main(main_args, stdout=stdout)
423
+ out = json.loads(stdout.getvalue())
424
+ assert (
425
+ out["output"]["checksum"]
426
+ == "sha1$d14dd02e354918b4776b941d154c18ebc15b9b38"
427
+ )
428
+
429
+ assert out["output"]["size"] == 24
430
+ with open(out["output"]["location"][len("file://") :]) as f:
431
+ assert f.read().strip() == "When is s4 coming out?"
415
432
 
416
- def test_run_revsort(self) -> None:
417
- self.revsort("revsort.cwl", self._tester)
433
+ @needs_docker
434
+ @pytest.mark.docker
435
+ @pytest.mark.online
436
+ def test_run_revsort(self, tmp_path: Path) -> None:
437
+ self.revsort("revsort.cwl", self._tester, tmp_path)
418
438
 
419
- def test_run_revsort_nochecksum(self) -> None:
439
+ @needs_docker
440
+ @pytest.mark.docker
441
+ @pytest.mark.online
442
+ def test_run_revsort_nochecksum(self, tmp_path: Path) -> None:
420
443
  self.revsort_no_checksum(
421
- "revsort.cwl", partial(self._tester, main_args=["--no-compute-checksum"])
444
+ "revsort.cwl",
445
+ partial(self._tester, main_args=["--no-compute-checksum"]),
446
+ tmp_path,
422
447
  )
423
448
 
424
- def test_run_revsort_no_container(self) -> None:
449
+ def test_run_revsort_no_container(self, tmp_path: Path) -> None:
425
450
  self.revsort(
426
- "revsort.cwl", partial(self._tester, main_args=["--no-container"])
451
+ "revsort.cwl", partial(self._tester, main_args=["--no-container"]), tmp_path
427
452
  )
428
453
 
429
- def test_run_revsort2(self) -> None:
430
- self.revsort("revsort2.cwl", self._tester)
454
+ @needs_docker
455
+ @pytest.mark.docker
456
+ @pytest.mark.online
457
+ def test_run_revsort2(self, tmp_path: Path) -> None:
458
+ self.revsort("revsort2.cwl", self._tester, tmp_path)
431
459
 
432
- def test_run_revsort_debug_worker(self) -> None:
433
- self.revsort("revsort.cwl", self._debug_worker_tester)
460
+ @needs_docker
461
+ @pytest.mark.docker
462
+ @pytest.mark.online
463
+ def test_run_revsort_debug_worker(self, tmp_path: Path) -> None:
464
+ self.revsort("revsort.cwl", self._debug_worker_tester, tmp_path)
465
+
466
+ @needs_docker
467
+ @pytest.mark.docker
468
+ @pytest.mark.online
469
+ def test_run_colon_output(self, tmp_path: Path) -> None:
470
+ with get_data("test/cwl/colon_test_output.cwl") as cwl_file:
471
+ with get_data("test/cwl/colon_test_output_job.yaml") as inputs_file:
472
+ self._tester(
473
+ cwl_file,
474
+ inputs_file,
475
+ self._expected_colon_output(tmp_path),
476
+ tmp_path,
477
+ out_name="result",
478
+ )
434
479
 
435
- def test_run_colon_output(self) -> None:
436
- self._tester(
437
- "src/toil/test/cwl/colon_test_output.cwl",
438
- "src/toil/test/cwl/colon_test_output_job.yaml",
439
- self._expected_colon_output(self.outDir),
440
- out_name="result",
441
- )
442
-
443
480
  @pytest.mark.integrative
444
- @needs_singularity_or_docker
445
- def test_run_dockstore_trs(self) -> None:
481
+ @needs_docker
482
+ @pytest.mark.docker
483
+ @pytest.mark.online
484
+ def test_run_dockstore_trs(self, tmp_path: Path) -> None:
446
485
  from toil.cwl import cwltoil
447
486
 
448
487
  stdout = StringIO()
449
488
  main_args = [
450
489
  "--outdir",
451
- self.outDir,
452
- "#workflow/github.com/dockstore-testing/md5sum-checker",
453
- "https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/refs/heads/master/md5sum/md5sum-input-cwl.json"
490
+ str(tmp_path),
491
+ "#workflow/github.com/dockstore-testing/md5sum-checker:master",
492
+ "https://raw.githubusercontent.com/dockstore-testing/md5sum-checker/refs/heads/master/md5sum/md5sum-input-cwl.json",
454
493
  ]
455
494
  cwltoil.main(main_args, stdout=stdout)
456
495
  out = json.loads(stdout.getvalue())
457
496
  with open(out.get("output_file", {}).get("location")[len("file://") :]) as f:
458
497
  computed_hash = f.read().strip()
459
- self.assertEqual(computed_hash, "00579a00e3e7fa0674428ac7049423e2")
498
+ assert computed_hash == "00579a00e3e7fa0674428ac7049423e2"
460
499
 
461
- def test_glob_dir_bypass_file_store(self) -> None:
500
+ def test_glob_dir_bypass_file_store(self, tmp_path: Path) -> None:
462
501
  self.maxDiff = 1000
463
- try:
464
- # We need to output to the current directory to make sure that
465
- # works.
466
- self._tester(
467
- "src/toil/test/cwl/glob_dir.cwl",
468
- "src/toil/test/cwl/empty.json",
469
- self._expected_glob_dir_output(os.getcwd()),
470
- main_args=["--bypass-file-store"],
471
- output_here=True,
472
- )
473
- finally:
474
- # Clean up anything we made in the current directory.
475
- try:
476
- shutil.rmtree(os.path.join(os.getcwd(), "shouldmake"))
477
- except FileNotFoundError:
478
- pass
479
-
480
- def test_required_input_condition_protection(self) -> None:
502
+ with get_data("test/cwl/glob_dir.cwl") as cwl_file:
503
+ with get_data("test/cwl/empty.json") as inputs_file:
504
+ self._tester(
505
+ cwl_file,
506
+ inputs_file,
507
+ self._expected_glob_dir_output(tmp_path),
508
+ tmp_path,
509
+ main_args=["--bypass-file-store"],
510
+ )
511
+
512
+ def test_required_input_condition_protection(self, tmp_path: Path) -> None:
481
513
  # This doesn't run containerized
482
- self._tester(
483
- "src/toil/test/cwl/not_run_required_input.cwl",
484
- "src/toil/test/cwl/empty.json",
485
- {},
486
- )
514
+ with get_data("test/cwl/not_run_required_input.cwl") as cwl_file:
515
+ with get_data("test/cwl/empty.json") as inputs_file:
516
+ self._tester(cwl_file, inputs_file, {}, tmp_path)
487
517
 
488
518
  @needs_slurm
489
- def test_slurm_node_memory(self) -> None:
519
+ @pytest.mark.slurm
520
+ def test_slurm_node_memory(self, tmp_path: Path) -> None:
490
521
  pass
491
522
 
492
523
  # Run the workflow. This will either finish quickly and tell us the
@@ -497,28 +528,29 @@ class CWLWorkflowTest(ToilTest):
497
528
  # And if we run out of time we need to stop the workflow gracefully and
498
529
  # cancel the Slurm jobs.
499
530
 
500
- main_args = [
501
- f"--jobStore={self.jobStoreDir}",
502
- # Avoid racing to toil kill before the jobstore is removed
503
- "--clean=never",
504
- "--batchSystem=slurm",
505
- "--no-cwl-default-ram",
506
- "--slurmDefaultAllMem=True",
507
- "--outdir",
508
- self.outDir,
509
- os.path.join(self.rootDir, "src/toil/test/cwl/measure_default_memory.cwl"),
510
- ]
511
531
  try:
512
- log.debug("Start test workflow")
513
- child = subprocess.Popen(
514
- ["toil-cwl-runner"] + main_args, stdout=subprocess.PIPE
515
- )
516
- output, _ = child.communicate(timeout=60)
532
+ with get_data("test/cwl/measure_default_memory.cwl") as cwl_file:
533
+ main_args = [
534
+ f"--jobStore={str(tmp_path / 'jobStoreDir')}",
535
+ # Avoid racing to toil kill before the jobstore is removed
536
+ "--clean=never",
537
+ "--batchSystem=slurm",
538
+ "--no-cwl-default-ram",
539
+ "--slurmDefaultAllMem=True",
540
+ "--outdir",
541
+ str(tmp_path / "outdir"),
542
+ str(cwl_file),
543
+ ]
544
+ log.debug("Start test workflow")
545
+ child = subprocess.Popen(
546
+ ["toil-cwl-runner"] + main_args, stdout=subprocess.PIPE
547
+ )
548
+ output, _ = child.communicate(timeout=60)
517
549
  except subprocess.TimeoutExpired:
518
550
  # The job didn't finish quickly; presumably waiting for a full node.
519
551
  # Stop the workflow
520
552
  log.debug("Workflow might be waiting for a full node. Stop it.")
521
- subprocess.check_call(["toil", "kill", self.jobStoreDir])
553
+ subprocess.check_call(["toil", "kill", str(tmp_path / "jobStoreDir")])
522
554
  # Wait another little bit for it to clean up, making sure to collect output in case it is blocked on writing
523
555
  child.communicate(timeout=20)
524
556
  # Kill it off in case it is still running
@@ -538,113 +570,151 @@ class CWLWorkflowTest(ToilTest):
538
570
  else:
539
571
  result = int(memory_string)
540
572
  # We should see more than the CWL default or the Toil default, assuming Slurm nodes of reasonable size (3 GiB).
541
- self.assertGreater(result, 3 * 1024 * 1024)
573
+ assert result > (3 * 1024 * 1024)
542
574
 
543
575
  @needs_aws_s3
544
- def test_download_s3(self) -> None:
545
- self.download("download_s3.json", self._tester)
576
+ @pytest.mark.aws_s3
577
+ @pytest.mark.online
578
+ def test_download_s3(self, tmp_path: Path) -> None:
579
+ self.download("download_s3.json", self._tester, tmp_path)
546
580
 
547
- def test_download_http(self) -> None:
548
- self.download("download_http.json", self._tester)
581
+ def test_download_http(self, tmp_path: Path) -> None:
582
+ self.download("download_http.json", self._tester, tmp_path)
549
583
 
550
- def test_download_https(self) -> None:
551
- self.download("download_https.json", self._tester)
584
+ def test_download_https(self, tmp_path: Path) -> None:
585
+ self.download("download_https.json", self._tester, tmp_path)
552
586
 
553
- def test_download_https_reference(self) -> None:
587
+ def test_download_https_reference(self, tmp_path: Path) -> None:
554
588
  self.download(
555
589
  "download_https.json",
556
590
  partial(self._tester, main_args=["--reference-inputs"]),
591
+ tmp_path,
557
592
  )
558
593
 
559
- def test_download_file(self) -> None:
560
- self.download("download_file.json", self._tester)
594
+ def test_download_file(self, tmp_path: Path) -> None:
595
+ self.download("download_file.json", self._tester, tmp_path)
561
596
 
562
597
  @needs_aws_s3
563
- def test_download_directory_s3(self) -> None:
564
- self.download_directory("download_directory_s3.json", self._tester)
598
+ @pytest.mark.aws_s3
599
+ @pytest.mark.online
600
+ def test_download_directory_s3(self, tmp_path: Path) -> None:
601
+ self.download_directory("download_directory_s3.json", self._tester, tmp_path)
565
602
 
566
603
  @needs_aws_s3
567
- def test_download_directory_s3_reference(self) -> None:
604
+ @pytest.mark.aws_s3
605
+ @pytest.mark.online
606
+ def test_download_directory_s3_reference(self, tmp_path: Path) -> None:
568
607
  self.download_directory(
569
608
  "download_directory_s3.json",
570
609
  partial(self._tester, main_args=["--reference-inputs"]),
610
+ tmp_path,
571
611
  )
572
612
 
573
- def test_download_directory_file(self) -> None:
574
- self.download_directory("download_directory_file.json", self._tester)
613
+ def test_download_directory_file(self, tmp_path: Path) -> None:
614
+ self.download_directory("download_directory_file.json", self._tester, tmp_path)
575
615
 
576
616
  @needs_aws_s3
577
- def test_download_subdirectory_s3(self) -> None:
578
- self.download_subdirectory("download_subdirectory_s3.json", self._tester)
617
+ @pytest.mark.aws_s3
618
+ @pytest.mark.online
619
+ def test_download_subdirectory_s3(self, tmp_path: Path) -> None:
620
+ self.download_subdirectory(
621
+ "download_subdirectory_s3.json", self._tester, tmp_path
622
+ )
579
623
 
580
- def test_download_subdirectory_file(self) -> None:
581
- self.download_subdirectory("download_subdirectory_file.json", self._tester)
624
+ def test_download_subdirectory_file(self, tmp_path: Path) -> None:
625
+ self.download_subdirectory(
626
+ "download_subdirectory_file.json", self._tester, tmp_path
627
+ )
582
628
 
583
629
  # We also want to make sure we can run a bare tool with loadContents on the inputs, which requires accessing the input data early in the leader.
584
630
 
585
631
  @needs_aws_s3
586
- def test_load_contents_s3(self) -> None:
587
- self.load_contents("download_s3.json", self._tester)
632
+ @pytest.mark.aws_s3
633
+ @pytest.mark.online
634
+ def test_load_contents_s3(self, tmp_path: Path) -> None:
635
+ self.load_contents("download_s3.json", self._tester, tmp_path)
588
636
 
589
- def test_load_contents_http(self) -> None:
590
- self.load_contents("download_http.json", self._tester)
637
+ def test_load_contents_http(self, tmp_path: Path) -> None:
638
+ self.load_contents("download_http.json", self._tester, tmp_path)
591
639
 
592
- def test_load_contents_https(self) -> None:
593
- self.load_contents("download_https.json", self._tester)
640
+ def test_load_contents_https(self, tmp_path: Path) -> None:
641
+ self.load_contents("download_https.json", self._tester, tmp_path)
594
642
 
595
- def test_load_contents_file(self) -> None:
596
- self.load_contents("download_file.json", self._tester)
643
+ def test_load_contents_file(self, tmp_path: Path) -> None:
644
+ self.load_contents("download_file.json", self._tester, tmp_path)
597
645
 
598
646
  @slow
599
647
  @pytest.mark.integrative
600
- @unittest.skip("Fails too often due to remote service")
601
- def test_bioconda(self) -> None:
602
- self._tester(
603
- "src/toil/test/cwl/seqtk_seq.cwl",
604
- "src/toil/test/cwl/seqtk_seq_job.json",
605
- self._expected_seqtk_output(self.outDir),
606
- main_args=["--beta-conda-dependencies"],
607
- out_name="output1",
608
- )
648
+ @pytest.mark.slow
649
+ @pytest.mark.skip("Fails too often due to remote service")
650
+ def test_bioconda(self, tmp_path: Path) -> None:
651
+ with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
652
+ with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
653
+ self._tester(
654
+ cwl_file,
655
+ inputs_file,
656
+ self._expected_seqtk_output(tmp_path),
657
+ tmp_path,
658
+ main_args=["--beta-conda-dependencies"],
659
+ out_name="output1",
660
+ )
609
661
 
610
662
  @needs_docker
611
- def test_default_args(self) -> None:
612
- self._tester(
613
- "src/toil/test/cwl/seqtk_seq.cwl",
614
- "src/toil/test/cwl/seqtk_seq_job.json",
615
- self._expected_seqtk_output(self.outDir),
616
- main_args=[
617
- "--default-container",
618
- "quay.io/biocontainers/seqtk:1.4--he4a0461_1",
619
- ],
620
- out_name="output1",
621
- )
663
+ @pytest.mark.docker
664
+ @pytest.mark.online
665
+ def test_default_args(self, tmp_path: Path) -> None:
666
+ with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
667
+ with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
668
+ self._tester(
669
+ cwl_file,
670
+ inputs_file,
671
+ self._expected_seqtk_output(tmp_path),
672
+ tmp_path,
673
+ main_args=[
674
+ "--default-container",
675
+ "quay.io/biocontainers/seqtk:1.4--he4a0461_1",
676
+ ],
677
+ out_name="output1",
678
+ )
622
679
 
623
680
  @needs_docker
681
+ @pytest.mark.docker
624
682
  @pytest.mark.integrative
625
- @unittest.skip("Fails too often due to remote service")
626
- def test_biocontainers(self) -> None:
627
- self._tester(
628
- "src/toil/test/cwl/seqtk_seq.cwl",
629
- "src/toil/test/cwl/seqtk_seq_job.json",
630
- self._expected_seqtk_output(self.outDir),
631
- main_args=["--beta-use-biocontainers"],
632
- out_name="output1",
633
- )
683
+ @pytest.mark.online
684
+ @pytest.mark.skip(reason="Fails too often due to remote service")
685
+ def test_biocontainers(self, tmp_path: Path) -> None:
686
+ with get_data("test/cwl/seqtk_seq.cwl") as cwl_file:
687
+ with get_data("test/cwl/seqtk_seq_job.json") as inputs_file:
688
+ self._tester(
689
+ cwl_file,
690
+ inputs_file,
691
+ self._expected_seqtk_output(tmp_path),
692
+ tmp_path,
693
+ main_args=["--beta-use-biocontainers"],
694
+ out_name="output1",
695
+ )
634
696
 
635
697
  @needs_docker
636
698
  @needs_docker_cuda
637
699
  @needs_local_cuda
638
- def test_cuda(self) -> None:
639
- self._tester(
640
- "src/toil/test/cwl/nvidia_smi.cwl",
641
- "src/toil/test/cwl/empty.json",
642
- {},
643
- out_name="result",
644
- )
700
+ @pytest.mark.docker
701
+ @pytest.mark.online
702
+ @pytest.mark.docker_cuda
703
+ @pytest.mark.local_cuda
704
+ def test_cuda(self, tmp_path: Path) -> None:
705
+ with get_data("test/cwl/nvidia_smi.cwl") as cwl_file:
706
+ with get_data("test/cwl/empty.json") as inputs_file:
707
+ self._tester(
708
+ cwl_file,
709
+ inputs_file,
710
+ {},
711
+ tmp_path,
712
+ out_name="result",
713
+ )
645
714
 
646
715
  @slow
647
- def test_restart(self) -> None:
716
+ @pytest.mark.slow
717
+ def test_restart(self, tmp_path: Path) -> None:
648
718
  """
649
719
  Enable restarts with toil-cwl-runner -- run failing test, re-run correct test.
650
720
  Only implemented for single machine.
@@ -652,151 +722,203 @@ class CWLWorkflowTest(ToilTest):
652
722
  log.info("Running CWL Test Restart. Expecting failure, then success.")
653
723
  from toil.cwl import cwltoil
654
724
 
655
- outDir = self._createTempDir()
656
- cwlDir = os.path.join(self._projectRootPath(), "src", "toil", "test", "cwl")
657
- cmd = [
658
- "--outdir",
659
- outDir,
660
- "--jobStore",
661
- os.path.join(outDir, "jobStore"),
662
- "--no-container",
663
- os.path.join(cwlDir, "revsort.cwl"),
664
- os.path.join(cwlDir, "revsort-job.json"),
665
- ]
725
+ outDir = tmp_path / "outDir"
726
+ outDir.mkdir()
727
+ jobStore = tmp_path / "jobStore"
728
+ with get_data("test/cwl/revsort.cwl") as cwl_file:
729
+ with get_data("test/cwl/revsort-job.json") as job_file:
730
+ cmd = [
731
+ "--outdir",
732
+ str(outDir),
733
+ "--jobStore",
734
+ str(jobStore),
735
+ "--no-container",
736
+ str(cwl_file),
737
+ str(job_file),
738
+ ]
739
+
740
+ # create a fake rev bin that actually points to the "date" binary
741
+ cal_path = [
742
+ d
743
+ for d in os.environ["PATH"].split(":")
744
+ if os.path.exists(os.path.join(d, "date"))
745
+ ][-1]
746
+ os.symlink(
747
+ os.path.realpath(os.path.join(cal_path, "date")), outDir / "rev"
748
+ )
749
+
750
+ def path_with_bogus_rev() -> str:
751
+ # append to the front of the PATH so that we check there first
752
+ return f"{str(outDir)}:" + os.environ["PATH"]
753
+
754
+ orig_path = os.environ["PATH"]
755
+ # Force a failure by trying to use an incorrect version of `rev` from the PATH
756
+ os.environ["PATH"] = path_with_bogus_rev()
757
+ try:
758
+ subprocess.check_output(
759
+ ["toil-cwl-runner"] + cmd,
760
+ env=os.environ.copy(),
761
+ stderr=subprocess.STDOUT,
762
+ )
763
+ pytest.fail("Expected problem job with incorrect PATH did not fail")
764
+ except subprocess.CalledProcessError:
765
+ pass
766
+ # Finish the job with a correct PATH
767
+ os.environ["PATH"] = orig_path
768
+ cmd.insert(0, "--restart")
769
+ cwltoil.main(cmd)
770
+ # Should fail because previous job completed successfully
771
+ try:
772
+ subprocess.check_output(
773
+ ["toil-cwl-runner"] + cmd,
774
+ env=os.environ.copy(),
775
+ stderr=subprocess.STDOUT,
776
+ )
777
+ pytest.fail("Restart with missing directory did not fail")
778
+ except subprocess.CalledProcessError:
779
+ pass
780
+
781
+ def test_caching(self, tmp_path: Path) -> None:
782
+ log.info("Running CWL caching test.")
783
+ from toil.cwl import cwltoil
666
784
 
667
- # create a fake rev bin that actually points to the "date" binary
668
- cal_path = [
669
- d
670
- for d in os.environ["PATH"].split(":")
671
- if os.path.exists(os.path.join(d, "date"))
672
- ][-1]
673
- os.symlink(os.path.join(cal_path, "date"), f'{os.path.join(outDir, "rev")}')
674
-
675
- def path_with_bogus_rev() -> str:
676
- # append to the front of the PATH so that we check there first
677
- return f"{outDir}:" + os.environ["PATH"]
678
-
679
- orig_path = os.environ["PATH"]
680
- # Force a failure by trying to use an incorrect version of `rev` from the PATH
681
- os.environ["PATH"] = path_with_bogus_rev()
682
- try:
683
- subprocess.check_output(
684
- ["toil-cwl-runner"] + cmd,
685
- env=os.environ.copy(),
686
- stderr=subprocess.STDOUT,
687
- )
688
- self.fail("Expected problem job with incorrect PATH did not fail")
689
- except subprocess.CalledProcessError:
690
- pass
691
- # Finish the job with a correct PATH
692
- os.environ["PATH"] = orig_path
693
- cmd.insert(0, "--restart")
694
- cwltoil.main(cmd)
695
- # Should fail because previous job completed successfully
696
- try:
697
- subprocess.check_output(
698
- ["toil-cwl-runner"] + cmd,
699
- env=os.environ.copy(),
700
- stderr=subprocess.STDOUT,
701
- )
702
- self.fail("Restart with missing directory did not fail")
703
- except subprocess.CalledProcessError:
704
- pass
785
+ outDir = tmp_path / "outDir"
786
+ cacheDir = tmp_path / "cacheDir"
787
+ log_path = outDir / "log"
788
+
789
+ with get_data("test/cwl/revsort.cwl") as cwl_file:
790
+ with get_data("test/cwl/revsort-job.json") as job_file:
791
+ cmd = [
792
+ "--outdir",
793
+ str(outDir),
794
+ "--jobStore",
795
+ str(tmp_path / "jobStore"),
796
+ "--clean=always",
797
+ "--no-container",
798
+ "--cachedir",
799
+ str(cacheDir),
800
+ str(cwl_file),
801
+ str(job_file),
802
+ ]
803
+ st = StringIO()
804
+ ret = cwltoil.main(cmd, stdout=st)
805
+ assert ret == 0
806
+ # cwltool hashes certain steps into directories, ensure it exists
807
+ # since cwltool caches per task and revsort has 2 cwl tasks, there should be 2 directories and 2 status files
808
+ assert sum(1 for _ in cacheDir.iterdir()) == 4
809
+
810
+ # Rerun the workflow to ensure there is a cache hit and that we don't rerun the tools
811
+ st = StringIO()
812
+ cmd = [
813
+ "--writeLogsFromAllJobs=True",
814
+ "--writeLogs",
815
+ str(log_path),
816
+ ] + cmd
817
+ ret = cwltoil.main(cmd, stdout=st)
818
+ assert ret == 0
819
+
820
+ # Ensure all of the worker logs are using their cached outputs
821
+ for file in log_path.iterdir():
822
+ assert "Using cached output" in file.read_text(encoding="utf-8")
705
823
 
706
824
  @needs_aws_s3
707
- def test_streamable(self, extra_args: Optional[list[str]] = None) -> None:
825
+ @pytest.mark.aws_s3
826
+ @pytest.mark.online
827
+ def test_streamable(
828
+ self, tmp_path: Path, extra_args: Optional[list[str]] = None
829
+ ) -> None:
708
830
  """
709
831
  Test that a file with 'streamable'=True is a named pipe.
710
832
  This is a CWL1.2 feature.
711
833
  """
712
- cwlfile = "src/toil/test/cwl/stream.cwl"
713
- jobfile = "src/toil/test/cwl/stream.json"
714
- out_name = "output"
715
- jobstore = f"--jobStore=aws:us-west-1:toil-stream-{uuid.uuid4()}"
716
- from toil.cwl import cwltoil
717
-
718
834
  st = StringIO()
719
- args = [
720
- "--logDebug",
721
- "--outdir",
722
- self.outDir,
723
- jobstore,
724
- os.path.join(self.rootDir, cwlfile),
725
- os.path.join(self.rootDir, jobfile),
726
- ]
727
- if extra_args:
728
- args = extra_args + args
729
- log.info("Run CWL run: %s", " ".join(args))
730
- cwltoil.main(args, stdout=st)
835
+ outDir = tmp_path / "outDir"
836
+ with get_data("test/cwl/stream.cwl") as cwlfile:
837
+ with get_data("test/cwl/stream.json") as jobfile:
838
+ out_name = "output"
839
+ jobstore = f"--jobStore=aws:us-west-1:toil-stream-{uuid.uuid4()}"
840
+ from toil.cwl import cwltoil
841
+
842
+ args = [
843
+ "--logDebug",
844
+ "--outdir",
845
+ str(outDir),
846
+ jobstore,
847
+ str(cwlfile),
848
+ str(jobfile),
849
+ ]
850
+ if extra_args:
851
+ args = extra_args + args
852
+ log.info("Run CWL run: %s", " ".join(args))
853
+ cwltoil.main(args, stdout=st)
731
854
  out = json.loads(st.getvalue())
732
855
  out[out_name].pop("http://commonwl.org/cwltool#generation", None)
733
856
  out[out_name].pop("nameext", None)
734
857
  out[out_name].pop("nameroot", None)
735
- self.assertEqual(out, self._expected_streaming_output(self.outDir))
858
+ assert out == self._expected_streaming_output(outDir)
736
859
  with open(out[out_name]["location"][len("file://") :]) as f:
737
- self.assertEqual(f.read().strip(), "When is s4 coming out?")
860
+ assert f.read().strip() == "When is s4 coming out?"
738
861
 
739
862
  @needs_aws_s3
740
- def test_streamable_reference(self) -> None:
863
+ @pytest.mark.aws_s3
864
+ @pytest.mark.online
865
+ def test_streamable_reference(self, tmp_path: Path) -> None:
741
866
  """
742
867
  Test that a streamable file is a stream even when passed around by URI.
743
868
  """
744
- self.test_streamable(extra_args=["--reference-inputs"])
869
+ self.test_streamable(tmp_path=tmp_path, extra_args=["--reference-inputs"])
745
870
 
746
- def test_preemptible(self) -> None:
871
+ def test_preemptible(self, tmp_path: Path) -> None:
747
872
  """
748
873
  Tests that the http://arvados.org/cwl#UsePreemptible extension is supported.
749
874
  """
750
- cwlfile = "src/toil/test/cwl/preemptible.cwl"
751
- jobfile = "src/toil/test/cwl/empty.json"
752
- out_name = "output"
753
875
  from toil.cwl import cwltoil
754
876
 
755
877
  st = StringIO()
756
- args = [
757
- "--outdir",
758
- self.outDir,
759
- os.path.join(self.rootDir, cwlfile),
760
- os.path.join(self.rootDir, jobfile),
761
- ]
762
- cwltoil.main(args, stdout=st)
878
+ out_name = "output"
879
+ with get_data("test/cwl/preemptible.cwl") as cwlfile:
880
+ with get_data("test/cwl/empty.json") as jobfile:
881
+ args = [
882
+ "--outdir",
883
+ str(tmp_path / "outDir"),
884
+ str(cwlfile),
885
+ str(jobfile),
886
+ ]
887
+ cwltoil.main(args, stdout=st)
763
888
  out = json.loads(st.getvalue())
764
889
  out[out_name].pop("http://commonwl.org/cwltool#generation", None)
765
890
  out[out_name].pop("nameext", None)
766
891
  out[out_name].pop("nameroot", None)
767
892
  with open(out[out_name]["location"][len("file://") :]) as f:
768
- self.assertEqual(f.read().strip(), "hello")
893
+ assert f.read().strip() == "hello"
769
894
 
770
- def test_preemptible_expression(self) -> None:
895
+ def test_preemptible_expression(self, tmp_path: Path) -> None:
771
896
  """
772
897
  Tests that the http://arvados.org/cwl#UsePreemptible extension is validated.
773
898
  """
774
- cwlfile = "src/toil/test/cwl/preemptible_expression.cwl"
775
- jobfile = "src/toil/test/cwl/preemptible_expression.json"
776
899
  from toil.cwl import cwltoil
777
900
 
778
901
  st = StringIO()
779
- args = [
780
- "--outdir",
781
- self.outDir,
782
- os.path.join(self.rootDir, cwlfile),
783
- os.path.join(self.rootDir, jobfile),
784
- ]
785
- try:
786
- cwltoil.main(args, stdout=st)
787
- raise RuntimeError("Did not raise correct exception")
788
- except ValidationException as e:
789
- # Make sure we chastise the user appropriately.
790
- assert "expressions are not allowed" in str(e)
902
+ with get_data("test/cwl/preemptible_expression.cwl") as cwlfile:
903
+ with get_data("test/cwl/preemptible_expression.json") as jobfile:
904
+ args = [
905
+ "--outdir",
906
+ str(tmp_path),
907
+ str(cwlfile),
908
+ str(jobfile),
909
+ ]
910
+ with pytest.raises(
911
+ ValidationException, match=re.escape("expressions are not allowed")
912
+ ):
913
+ cwltoil.main(args, stdout=st)
791
914
 
792
915
  @staticmethod
793
- def _expected_seqtk_output(outDir: str) -> "CWLObjectType":
794
- path = os.path.join(outDir, "out")
795
- loc = "file://" + path
916
+ def _expected_seqtk_output(outDir: Path) -> "CWLObjectType":
917
+ path = outDir / "out"
796
918
  return {
797
919
  "output1": {
798
- "location": loc,
799
- "path": path,
920
+ "location": path.as_uri(),
921
+ "path": str(path),
800
922
  "checksum": "sha1$322e001e5a99f19abdce9f02ad0f02a17b5066c2",
801
923
  "basename": "out",
802
924
  "class": "File",
@@ -805,13 +927,12 @@ class CWLWorkflowTest(ToilTest):
805
927
  }
806
928
 
807
929
  @staticmethod
808
- def _expected_revsort_output(outDir: str) -> "CWLObjectType":
809
- path = os.path.join(outDir, "output.txt")
810
- loc = "file://" + path
930
+ def _expected_revsort_output(outDir: Path) -> "CWLObjectType":
931
+ path = outDir / "output.txt"
811
932
  return {
812
933
  "output": {
813
- "location": loc,
814
- "path": path,
934
+ "location": path.as_uri(),
935
+ "path": str(path),
815
936
  "basename": "output.txt",
816
937
  "size": 1111,
817
938
  "class": "File",
@@ -820,13 +941,12 @@ class CWLWorkflowTest(ToilTest):
820
941
  }
821
942
 
822
943
  @staticmethod
823
- def _expected_revsort_nochecksum_output(outDir: str) -> "CWLObjectType":
824
- path = os.path.join(outDir, "output.txt")
825
- loc = "file://" + path
944
+ def _expected_revsort_nochecksum_output(outDir: Path) -> "CWLObjectType":
945
+ path = outDir / "output.txt"
826
946
  return {
827
947
  "output": {
828
- "location": loc,
829
- "path": path,
948
+ "location": path.as_uri(),
949
+ "path": str(path),
830
950
  "basename": "output.txt",
831
951
  "size": 1111,
832
952
  "class": "File",
@@ -834,30 +954,29 @@ class CWLWorkflowTest(ToilTest):
834
954
  }
835
955
 
836
956
  @staticmethod
837
- def _expected_download_output(outDir: str) -> "CWLObjectType":
838
- path = os.path.join(outDir, "output.txt")
839
- loc = "file://" + path
957
+ def _expected_download_output(outDir: Path) -> "CWLObjectType":
958
+ path = outDir / "output.txt"
840
959
  return {
841
960
  "output": {
842
- "location": loc,
961
+ "location": path.as_uri(),
843
962
  "basename": "output.txt",
844
963
  "size": 0,
845
964
  "class": "File",
846
965
  "checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
847
- "path": path,
966
+ "path": str(path),
848
967
  }
849
968
  }
850
969
 
851
970
  @staticmethod
852
- def _expected_glob_dir_output(out_dir: str) -> "CWLObjectType":
853
- dir_path = os.path.join(out_dir, "shouldmake")
854
- dir_loc = "file://" + dir_path
855
- file_path = os.path.join(dir_path, "test.txt")
856
- file_loc = os.path.join(dir_loc, "test.txt")
971
+ def _expected_glob_dir_output(out_dir: Path) -> "CWLObjectType":
972
+ dir_path = out_dir / "shouldmake"
973
+ dir_loc = dir_path.as_uri()
974
+ file_path = dir_path / "test.txt"
975
+ file_loc = file_path.as_uri()
857
976
  return {
858
977
  "shouldmake": {
859
978
  "location": dir_loc,
860
- "path": dir_path,
979
+ "path": str(dir_path),
861
980
  "basename": "shouldmake",
862
981
  "nameroot": "shouldmake",
863
982
  "nameext": "",
@@ -866,7 +985,7 @@ class CWLWorkflowTest(ToilTest):
866
985
  {
867
986
  "class": "File",
868
987
  "location": file_loc,
869
- "path": file_path,
988
+ "path": str(file_path),
870
989
  "basename": "test.txt",
871
990
  "checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
872
991
  "size": 0,
@@ -878,7 +997,7 @@ class CWLWorkflowTest(ToilTest):
878
997
  }
879
998
 
880
999
  @classmethod
881
- def _expected_load_contents_output(cls, out_dir: str) -> "CWLObjectType":
1000
+ def _expected_load_contents_output(cls, out_dir: Path) -> "CWLObjectType":
882
1001
  """
883
1002
  Generate the putput we expect from load_contents.cwl, when sending
884
1003
  output files to the given directory.
@@ -888,13 +1007,15 @@ class CWLWorkflowTest(ToilTest):
888
1007
  return expected
889
1008
 
890
1009
  @staticmethod
891
- def _expected_colon_output(outDir: str) -> "CWLObjectType":
892
- path = os.path.join(outDir, "A:Gln2Cys_result")
893
- loc = "file://" + os.path.join(outDir, "A%3AGln2Cys_result")
1010
+ def _expected_colon_output(outDir: Path) -> "CWLObjectType":
1011
+ path = outDir / "A:Gln2Cys_result"
1012
+ loc = "file://" + os.path.join(
1013
+ outDir, "A%3AGln2Cys_result"
1014
+ ) # not using .as_uri to ensure the expected escaping
894
1015
  return {
895
1016
  "result": {
896
1017
  "location": loc,
897
- "path": path,
1018
+ "path": str(path),
898
1019
  "basename": "A:Gln2Cys_result",
899
1020
  "class": "Directory",
900
1021
  "listing": [
@@ -912,13 +1033,12 @@ class CWLWorkflowTest(ToilTest):
912
1033
  }
913
1034
  }
914
1035
 
915
- def _expected_streaming_output(self, outDir: str) -> "CWLObjectType":
916
- path = os.path.join(outDir, "output.txt")
917
- loc = "file://" + path
1036
+ def _expected_streaming_output(self, outDir: Path) -> "CWLObjectType":
1037
+ path = outDir / "output.txt"
918
1038
  return {
919
1039
  "output": {
920
- "location": loc,
921
- "path": path,
1040
+ "location": path.as_uri(),
1041
+ "path": str(path),
922
1042
  "basename": "output.txt",
923
1043
  "size": 24,
924
1044
  "class": "File",
@@ -926,50 +1046,100 @@ class CWLWorkflowTest(ToilTest):
926
1046
  }
927
1047
  }
928
1048
 
1049
+ @needs_docker
1050
+ @pytest.mark.docker
1051
+ @pytest.mark.online
1052
+ def test_missing_import(self, tmp_path: Path) -> None:
1053
+ with get_data("test/cwl/revsort.cwl") as cwl_file:
1054
+ with get_data("test/cwl/revsort-job-missing.json") as inputs_file:
1055
+ cmd = [
1056
+ "toil-cwl-runner",
1057
+ f"--outdir={str(tmp_path)}",
1058
+ "--clean=always",
1059
+ str(cwl_file),
1060
+ str(inputs_file),
1061
+ ]
1062
+ p = subprocess.run(cmd, capture_output=True, text=True)
1063
+ # Make sure that the missing file is mentioned in the log so the user knows
1064
+ assert p.returncode == 1, p.stderr
1065
+ assert "missing.txt" in p.stderr
1066
+
1067
+ @needs_aws_s3
1068
+ @pytest.mark.aws_s3
1069
+ @pytest.mark.online
1070
+ def test_optional_secondary_files_exists(self, tmp_path: Path) -> None:
1071
+ from toil.cwl import cwltoil
1072
+
1073
+ with get_data("test/cwl/optional-file.cwl") as cwlfile:
1074
+ with get_data("test/cwl/optional-file-exists.json") as jobfile:
1075
+ args = [str(cwlfile), str(jobfile), f"--outdir={str(tmp_path)}"]
1076
+ ret = cwltoil.main(args)
1077
+ assert ret == 0
1078
+ assert (tmp_path / "wdl_templates_old.zip").exists()
1079
+
1080
+ @needs_aws_s3
1081
+ @pytest.mark.aws_s3
1082
+ @pytest.mark.online
1083
+ def test_optional_secondary_files_missing(self, tmp_path: Path) -> None:
1084
+ from toil.cwl import cwltoil
1085
+
1086
+ with get_data("test/cwl/optional-file.cwl") as cwlfile:
1087
+ with get_data("test/cwl/optional-file-missing.json") as jobfile:
1088
+ args = [str(cwlfile), str(jobfile), f"--outdir={str(tmp_path)}"]
1089
+ ret = cwltoil.main(args)
1090
+ assert ret == 0
1091
+ assert not (tmp_path / "hello_old.zip").exists()
1092
+
1093
+
1094
+ @pytest.fixture(scope="function")
1095
+ def cwl_v1_0_spec(tmp_path: Path) -> Generator[Path]:
1096
+ # The latest cwl git commit hash from https://github.com/common-workflow-language/common-workflow-language.
1097
+ # Update it to get the latest tests.
1098
+ testhash = (
1099
+ "6a955874ade22080b8ef962b4e0d6e408112c1ef" # Date: Tue Dec 16 2020 8:43pm PST
1100
+ )
1101
+ url = (
1102
+ "https://github.com/common-workflow-language/common-workflow-language/archive/%s.zip"
1103
+ % testhash
1104
+ )
1105
+ urlretrieve(url, "spec.zip")
1106
+ with zipfile.ZipFile("spec.zip", "r") as z:
1107
+ z.extractall()
1108
+ shutil.move("common-workflow-language-%s" % testhash, str(tmp_path))
1109
+ os.remove("spec.zip")
1110
+ try:
1111
+ yield tmp_path / ("common-workflow-language-%s" % testhash)
1112
+ finally:
1113
+ pass # no cleanup
1114
+
929
1115
 
930
1116
  @needs_cwl
931
1117
  @needs_online
932
- class CWLv10Test(ToilTest):
1118
+ @pytest.mark.cwl
1119
+ @pytest.mark.online
1120
+ class TestCWLv10:
933
1121
  """
934
1122
  Run the CWL 1.0 conformance tests in various environments.
935
1123
  """
936
1124
 
937
- def setUp(self) -> None:
938
- """Runs anew before each test to create farm fresh temp dirs."""
939
- self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
940
- os.makedirs(self.outDir)
941
- self.rootDir = self._projectRootPath()
942
- self.cwlSpec = os.path.join(self.rootDir, "src/toil/test/cwl/spec")
943
- self.workDir = os.path.join(self.cwlSpec, "v1.0")
944
- # The latest cwl git commit hash from https://github.com/common-workflow-language/common-workflow-language.
945
- # Update it to get the latest tests.
946
- testhash = "6a955874ade22080b8ef962b4e0d6e408112c1ef" # Date: Tue Dec 16 2020 8:43pm PST
947
- url = (
948
- "https://github.com/common-workflow-language/common-workflow-language/archive/%s.zip"
949
- % testhash
950
- )
951
- if not os.path.exists(self.cwlSpec):
952
- urlretrieve(url, "spec.zip")
953
- with zipfile.ZipFile("spec.zip", "r") as z:
954
- z.extractall()
955
- shutil.move("common-workflow-language-%s" % testhash, self.cwlSpec)
956
- os.remove("spec.zip")
957
-
958
- def tearDown(self) -> None:
959
- """Clean up outputs."""
960
- if os.path.exists(self.outDir):
961
- shutil.rmtree(self.outDir)
962
- unittest.TestCase.tearDown(self)
963
-
964
1125
  @slow
1126
+ @needs_docker
1127
+ @pytest.mark.slow
1128
+ @pytest.mark.docker
1129
+ @pytest.mark.online
965
1130
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
966
- def test_run_conformance_with_caching(self) -> None:
967
- self.test_run_conformance(caching=True)
1131
+ def test_run_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1132
+ self.test_run_conformance(cwl_v1_0_spec, caching=True)
968
1133
 
969
1134
  @slow
1135
+ @needs_docker
1136
+ @pytest.mark.slow
1137
+ @pytest.mark.docker
1138
+ @pytest.mark.online
970
1139
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
971
1140
  def test_run_conformance(
972
1141
  self,
1142
+ cwl_v1_0_spec: Path,
973
1143
  batchSystem: Optional[str] = None,
974
1144
  caching: bool = False,
975
1145
  selected_tests: Optional[str] = None,
@@ -977,8 +1147,8 @@ class CWLv10Test(ToilTest):
977
1147
  extra_args: Optional[list[str]] = None,
978
1148
  ) -> None:
979
1149
  run_conformance_tests(
980
- workDir=self.workDir,
981
- yml="conformance_test_v1.0.yaml",
1150
+ workDir=str(cwl_v1_0_spec / "v1.0"),
1151
+ yml=str(cwl_v1_0_spec / "v1.0" / "conformance_test_v1.0.yaml"),
982
1152
  caching=caching,
983
1153
  batchSystem=batchSystem,
984
1154
  selected_tests=selected_tests,
@@ -988,38 +1158,66 @@ class CWLv10Test(ToilTest):
988
1158
 
989
1159
  @slow
990
1160
  @needs_lsf
991
- @unittest.skip("Not run")
992
- def test_lsf_cwl_conformance(self, caching: bool = False) -> None:
993
- self.test_run_conformance(batchSystem="lsf", caching=caching)
1161
+ @pytest.mark.slow
1162
+ @pytest.mark.lsf
1163
+ @pytest.mark.skip("Not run")
1164
+ def test_lsf_cwl_conformance(
1165
+ self, cwl_v1_0_spec: Path, caching: bool = False
1166
+ ) -> None:
1167
+ self.test_run_conformance(cwl_v1_0_spec, batchSystem="lsf", caching=caching)
994
1168
 
995
1169
  @slow
996
1170
  @needs_slurm
997
- @unittest.skip("Not run")
998
- def test_slurm_cwl_conformance(self, caching: bool = False) -> None:
999
- self.test_run_conformance(batchSystem="slurm", caching=caching)
1171
+ @pytest.mark.slow
1172
+ @pytest.mark.slurm
1173
+ @pytest.mark.skip("Not run")
1174
+ def test_slurm_cwl_conformance(
1175
+ self, cwl_v1_0_spec: Path, caching: bool = False
1176
+ ) -> None:
1177
+ self.test_run_conformance(cwl_v1_0_spec, batchSystem="slurm", caching=caching)
1000
1178
 
1001
1179
  @slow
1002
1180
  @needs_torque
1003
- @unittest.skip("Not run")
1004
- def test_torque_cwl_conformance(self, caching: bool = False) -> None:
1005
- self.test_run_conformance(batchSystem="torque", caching=caching)
1181
+ @pytest.mark.slow
1182
+ @pytest.mark.torque
1183
+ @pytest.mark.skip("Not run")
1184
+ def test_torque_cwl_conformance(
1185
+ self, cwl_v1_0_spec: Path, caching: bool = False
1186
+ ) -> None:
1187
+ self.test_run_conformance(cwl_v1_0_spec, batchSystem="torque", caching=caching)
1006
1188
 
1007
1189
  @slow
1008
1190
  @needs_gridengine
1009
- @unittest.skip("Not run")
1010
- def test_gridengine_cwl_conformance(self, caching: bool = False) -> None:
1011
- self.test_run_conformance(batchSystem="grid_engine", caching=caching)
1191
+ @pytest.mark.slow
1192
+ @pytest.mark.gridengine
1193
+ @pytest.mark.skip("Not run")
1194
+ def test_gridengine_cwl_conformance(
1195
+ self, cwl_v1_0_spec: Path, caching: bool = False
1196
+ ) -> None:
1197
+ self.test_run_conformance(
1198
+ cwl_v1_0_spec, batchSystem="grid_engine", caching=caching
1199
+ )
1012
1200
 
1013
1201
  @slow
1014
1202
  @needs_mesos
1015
- @unittest.skip("Not run")
1016
- def test_mesos_cwl_conformance(self, caching: bool = False) -> None:
1017
- self.test_run_conformance(batchSystem="mesos", caching=caching)
1203
+ @pytest.mark.slow
1204
+ @pytest.mark.mesos
1205
+ @pytest.mark.skip("Not run")
1206
+ def test_mesos_cwl_conformance(
1207
+ self, cwl_v1_0_spec: Path, caching: bool = False
1208
+ ) -> None:
1209
+ self.test_run_conformance(cwl_v1_0_spec, batchSystem="mesos", caching=caching)
1018
1210
 
1019
1211
  @slow
1020
1212
  @needs_kubernetes
1021
- def test_kubernetes_cwl_conformance(self, caching: bool = False) -> None:
1213
+ @pytest.mark.slow
1214
+ @pytest.mark.kubernetes
1215
+ @pytest.mark.online
1216
+ def test_kubernetes_cwl_conformance(
1217
+ self, cwl_v1_0_spec: Path, caching: bool = False
1218
+ ) -> None:
1022
1219
  self.test_run_conformance(
1220
+ cwl_v1_0_spec,
1023
1221
  caching=caching,
1024
1222
  batchSystem="kubernetes",
1025
1223
  extra_args=["--retryCount=3"],
@@ -1031,82 +1229,98 @@ class CWLv10Test(ToilTest):
1031
1229
 
1032
1230
  @slow
1033
1231
  @needs_lsf
1034
- @unittest.skip("Not run")
1035
- def test_lsf_cwl_conformance_with_caching(self) -> None:
1036
- self.test_lsf_cwl_conformance(caching=True)
1232
+ @pytest.mark.slow
1233
+ @pytest.mark.lsf
1234
+ @pytest.mark.skip(reason="Not run")
1235
+ def test_lsf_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1236
+ self.test_lsf_cwl_conformance(cwl_v1_0_spec, caching=True)
1037
1237
 
1038
1238
  @slow
1039
1239
  @needs_slurm
1040
- @unittest.skip("Not run")
1041
- def test_slurm_cwl_conformance_with_caching(self) -> None:
1042
- self.test_slurm_cwl_conformance(caching=True)
1240
+ @pytest.mark.slow
1241
+ @pytest.mark.slurm
1242
+ @pytest.mark.skip(reason="Not run")
1243
+ def test_slurm_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1244
+ self.test_slurm_cwl_conformance(cwl_v1_0_spec, caching=True)
1043
1245
 
1044
1246
  @slow
1045
1247
  @needs_torque
1046
- @unittest.skip("Not run")
1047
- def test_torque_cwl_conformance_with_caching(self) -> None:
1048
- self.test_torque_cwl_conformance(caching=True)
1248
+ @pytest.mark.slow
1249
+ @pytest.mark.torque
1250
+ @pytest.mark.skip(reason="Not run")
1251
+ def test_torque_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1252
+ self.test_torque_cwl_conformance(cwl_v1_0_spec, caching=True)
1049
1253
 
1050
1254
  @slow
1051
1255
  @needs_gridengine
1052
- @unittest.skip("Not run")
1053
- def test_gridengine_cwl_conformance_with_caching(self) -> None:
1054
- self.test_gridengine_cwl_conformance(caching=True)
1256
+ @pytest.mark.slow
1257
+ @pytest.mark.gridengine
1258
+ @pytest.mark.skip(reason="Not run")
1259
+ def test_gridengine_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1260
+ self.test_gridengine_cwl_conformance(cwl_v1_0_spec, caching=True)
1055
1261
 
1056
1262
  @slow
1057
1263
  @needs_mesos
1058
- @unittest.skip("Not run")
1059
- def test_mesos_cwl_conformance_with_caching(self) -> None:
1060
- self.test_mesos_cwl_conformance(caching=True)
1264
+ @pytest.mark.slow
1265
+ @pytest.mark.mesos
1266
+ @pytest.mark.skip(reason="Not run")
1267
+ def test_mesos_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1268
+ self.test_mesos_cwl_conformance(cwl_v1_0_spec, caching=True)
1061
1269
 
1062
1270
  @slow
1063
1271
  @needs_kubernetes
1064
- def test_kubernetes_cwl_conformance_with_caching(self) -> None:
1065
- self.test_kubernetes_cwl_conformance(caching=True)
1272
+ @pytest.mark.slow
1273
+ @pytest.mark.kubernetes
1274
+ @pytest.mark.online
1275
+ def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_0_spec: Path) -> None:
1276
+ self.test_kubernetes_cwl_conformance(cwl_v1_0_spec, caching=True)
1277
+
1278
+
1279
+ @pytest.fixture(scope="function")
1280
+ def cwl_v1_1_spec(tmp_path: Path) -> Generator[Path]:
1281
+ # The latest cwl git commit hash from https://github.com/common-workflow-language/cwl-v1.1
1282
+ # Update it to get the latest tests.
1283
+ testhash = "664835e83eb5e57eee18a04ce7b05fb9d70d77b7"
1284
+ url = (
1285
+ "https://github.com/common-workflow-language/cwl-v1.1/archive/%s.zip" % testhash
1286
+ )
1287
+ urlretrieve(url, "spec.zip")
1288
+ with zipfile.ZipFile("spec.zip", "r") as z:
1289
+ z.extractall()
1290
+ shutil.move("cwl-v1.1-%s" % testhash, str(tmp_path))
1291
+ os.remove("spec.zip")
1292
+ try:
1293
+ yield tmp_path / ("cwl-v1.1-%s" % testhash)
1294
+ finally:
1295
+ pass # no cleanup
1066
1296
 
1067
1297
 
1068
1298
  @needs_cwl
1069
1299
  @needs_online
1070
- class CWLv11Test(ToilTest):
1300
+ @pytest.mark.cwl
1301
+ @pytest.mark.online
1302
+ class TestCWLv11:
1071
1303
  """
1072
1304
  Run the CWL 1.1 conformance tests in various environments.
1073
1305
  """
1074
1306
 
1075
- rootDir: str
1076
- cwlSpec: str
1077
- test_yaml: str
1078
-
1079
- @classmethod
1080
- def setUpClass(cls) -> None:
1081
- """Runs anew before each test."""
1082
- cls.rootDir = cls._projectRootPath()
1083
- cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v11")
1084
- cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
1085
- # TODO: Use a commit zip in case someone decides to rewrite master's history?
1086
- url = "https://github.com/common-workflow-language/cwl-v1.1.git"
1087
- commit = "664835e83eb5e57eee18a04ce7b05fb9d70d77b7"
1088
- p = subprocess.Popen(
1089
- f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
1090
- shell=True,
1091
- )
1092
- p.communicate()
1093
-
1094
- def tearDown(self) -> None:
1095
- """Clean up outputs."""
1096
- unittest.TestCase.tearDown(self)
1097
-
1098
1307
  @slow
1308
+ @needs_docker
1309
+ @pytest.mark.slow
1310
+ @pytest.mark.docker
1311
+ @pytest.mark.online
1099
1312
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
1100
1313
  def test_run_conformance(
1101
1314
  self,
1315
+ cwl_v1_1_spec: Path,
1102
1316
  caching: bool = False,
1103
1317
  batchSystem: Optional[str] = None,
1104
1318
  skipped_tests: Optional[str] = None,
1105
1319
  extra_args: Optional[list[str]] = None,
1106
1320
  ) -> None:
1107
1321
  run_conformance_tests(
1108
- workDir=self.cwlSpec,
1109
- yml=self.test_yaml,
1322
+ workDir=str(cwl_v1_1_spec),
1323
+ yml=str(cwl_v1_1_spec / "conformance_tests.yaml"),
1110
1324
  caching=caching,
1111
1325
  batchSystem=batchSystem,
1112
1326
  skipped_tests=skipped_tests,
@@ -1114,14 +1328,24 @@ class CWLv11Test(ToilTest):
1114
1328
  )
1115
1329
 
1116
1330
  @slow
1331
+ @needs_docker
1332
+ @pytest.mark.slow
1333
+ @pytest.mark.docker
1334
+ @pytest.mark.online
1117
1335
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
1118
- def test_run_conformance_with_caching(self) -> None:
1119
- self.test_run_conformance(caching=True)
1336
+ def test_run_conformance_with_caching(self, cwl_v1_1_spec: Path) -> None:
1337
+ self.test_run_conformance(cwl_v1_1_spec, caching=True)
1120
1338
 
1121
1339
  @slow
1122
1340
  @needs_kubernetes
1123
- def test_kubernetes_cwl_conformance(self, caching: bool = False) -> None:
1341
+ @pytest.mark.slow
1342
+ @pytest.mark.kubernetes
1343
+ @pytest.mark.online
1344
+ def test_kubernetes_cwl_conformance(
1345
+ self, cwl_v1_1_spec: Path, caching: bool = False
1346
+ ) -> None:
1124
1347
  self.test_run_conformance(
1348
+ cwl_v1_1_spec,
1125
1349
  batchSystem="kubernetes",
1126
1350
  extra_args=["--retryCount=3"],
1127
1351
  # These tests don't work with
@@ -1133,44 +1357,50 @@ class CWLv11Test(ToilTest):
1133
1357
 
1134
1358
  @slow
1135
1359
  @needs_kubernetes
1136
- def test_kubernetes_cwl_conformance_with_caching(self) -> None:
1137
- self.test_kubernetes_cwl_conformance(caching=True)
1360
+ @pytest.mark.slow
1361
+ @pytest.mark.kubernetes
1362
+ @pytest.mark.online
1363
+ def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_1_spec: Path) -> None:
1364
+ self.test_kubernetes_cwl_conformance(cwl_v1_1_spec, caching=True)
1365
+
1366
+
1367
+ @pytest.fixture(scope="function")
1368
+ def cwl_v1_2_spec(tmp_path: Path) -> Generator[Path]:
1369
+ # The latest cwl git commit hash from https://github.com/common-workflow-language/cwl-v1.2
1370
+ # Update it to get the latest tests.
1371
+ testhash = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
1372
+ url = (
1373
+ "https://github.com/common-workflow-language/cwl-v1.2/archive/%s.zip" % testhash
1374
+ )
1375
+ urlretrieve(url, "spec.zip")
1376
+ with zipfile.ZipFile("spec.zip", "r") as z:
1377
+ z.extractall()
1378
+ shutil.move("cwl-v1.2-%s" % testhash, str(tmp_path))
1379
+ os.remove("spec.zip")
1380
+ try:
1381
+ yield tmp_path / ("cwl-v1.2-%s" % testhash)
1382
+ finally:
1383
+ pass # no cleanup
1138
1384
 
1139
1385
 
1140
1386
  @needs_cwl
1141
1387
  @needs_online
1142
- class CWLv12Test(ToilTest):
1388
+ @pytest.mark.cwl
1389
+ @pytest.mark.online
1390
+ class TestCWLv12:
1143
1391
  """
1144
1392
  Run the CWL 1.2 conformance tests in various environments.
1145
1393
  """
1146
1394
 
1147
- rootDir: str
1148
- cwlSpec: str
1149
- test_yaml: str
1150
-
1151
- @classmethod
1152
- def setUpClass(cls) -> None:
1153
- """Runs anew before each test."""
1154
- cls.rootDir = cls._projectRootPath()
1155
- cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v12")
1156
- cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
1157
- # TODO: Use a commit zip in case someone decides to rewrite master's history?
1158
- url = "https://github.com/common-workflow-language/cwl-v1.2.git"
1159
- commit = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
1160
- p = subprocess.Popen(
1161
- f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
1162
- shell=True,
1163
- )
1164
- p.communicate()
1165
-
1166
- def tearDown(self) -> None:
1167
- """Clean up outputs."""
1168
- unittest.TestCase.tearDown(self)
1169
-
1170
1395
  @slow
1396
+ @needs_docker
1397
+ @pytest.mark.slow
1398
+ @pytest.mark.docker
1399
+ @pytest.mark.online
1171
1400
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
1172
1401
  def test_run_conformance(
1173
1402
  self,
1403
+ cwl_v1_2_spec: Path,
1174
1404
  runner: Optional[str] = None,
1175
1405
  caching: bool = False,
1176
1406
  batchSystem: Optional[str] = None,
@@ -1181,10 +1411,10 @@ class CWLv12Test(ToilTest):
1181
1411
  junit_file: Optional[str] = None,
1182
1412
  ) -> None:
1183
1413
  if junit_file is None:
1184
- junit_file = os.path.join(self.rootDir, "conformance-1.2.junit.xml")
1414
+ junit_file = os.path.abspath("conformance-1.2.junit.xml")
1185
1415
  run_conformance_tests(
1186
- workDir=self.cwlSpec,
1187
- yml=self.test_yaml,
1416
+ workDir=str(cwl_v1_2_spec),
1417
+ yml=str(cwl_v1_2_spec / "conformance_tests.yaml"),
1188
1418
  runner=runner,
1189
1419
  caching=caching,
1190
1420
  batchSystem=batchSystem,
@@ -1196,39 +1426,65 @@ class CWLv12Test(ToilTest):
1196
1426
  )
1197
1427
 
1198
1428
  @slow
1429
+ @needs_docker
1430
+ @pytest.mark.slow
1431
+ @pytest.mark.docker
1432
+ @pytest.mark.online
1199
1433
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
1200
- def test_run_conformance_with_caching(self) -> None:
1434
+ def test_run_conformance_with_caching(self, cwl_v1_2_spec: Path) -> None:
1201
1435
  self.test_run_conformance(
1436
+ cwl_v1_2_spec,
1202
1437
  caching=True,
1203
- junit_file=os.path.join(self.rootDir, "caching-conformance-1.2.junit.xml"),
1438
+ junit_file=os.path.abspath("caching-conformance-1.2.junit.xml"),
1439
+ )
1440
+
1441
+ @slow
1442
+ @needs_docker
1443
+ @pytest.mark.slow
1444
+ @pytest.mark.docker
1445
+ @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
1446
+ def test_run_conformance_with_task_caching(
1447
+ self, cwl_v1_2_spec: Path, tmp_path: Path
1448
+ ) -> None:
1449
+ self.test_run_conformance(
1450
+ cwl_v1_2_spec,
1451
+ junit_file=os.path.abspath("task-caching-conformance-1.2.junit.xml"),
1452
+ extra_args=["--cachedir", str(tmp_path / "task_cache")],
1204
1453
  )
1205
1454
 
1206
1455
  @slow
1456
+ @needs_docker
1457
+ @pytest.mark.slow
1458
+ @pytest.mark.docker
1207
1459
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
1208
- def test_run_conformance_with_in_place_update(self) -> None:
1460
+ def test_run_conformance_with_in_place_update(self, cwl_v1_2_spec: Path) -> None:
1209
1461
  """
1210
1462
  Make sure that with --bypass-file-store we properly support in place
1211
1463
  update on a single node, and that this doesn't break any other
1212
1464
  features.
1213
1465
  """
1214
1466
  self.test_run_conformance(
1467
+ cwl_v1_2_spec,
1215
1468
  extra_args=["--bypass-file-store"],
1216
1469
  must_support_all_features=True,
1217
- junit_file=os.path.join(
1218
- self.rootDir, "in-place-update-conformance-1.2.junit.xml"
1219
- ),
1470
+ junit_file=os.path.abspath("in-place-update-conformance-1.2.junit.xml"),
1220
1471
  )
1221
1472
 
1222
1473
  @slow
1223
1474
  @needs_kubernetes
1475
+ @pytest.mark.slow
1476
+ @pytest.mark.kubernetes
1477
+ @pytest.mark.online
1224
1478
  def test_kubernetes_cwl_conformance(
1225
- self, caching: bool = False, junit_file: Optional[str] = None
1479
+ self,
1480
+ cwl_v1_2_spec: Path,
1481
+ caching: bool = False,
1482
+ junit_file: Optional[str] = None,
1226
1483
  ) -> None:
1227
1484
  if junit_file is None:
1228
- junit_file = os.path.join(
1229
- self.rootDir, "kubernetes-conformance-1.2.junit.xml"
1230
- )
1485
+ junit_file = os.path.abspath("kubernetes-conformance-1.2.junit.xml")
1231
1486
  self.test_run_conformance(
1487
+ cwl_v1_2_spec,
1232
1488
  caching=caching,
1233
1489
  batchSystem="kubernetes",
1234
1490
  extra_args=["--retryCount=3"],
@@ -1243,17 +1499,22 @@ class CWLv12Test(ToilTest):
1243
1499
 
1244
1500
  @slow
1245
1501
  @needs_kubernetes
1246
- def test_kubernetes_cwl_conformance_with_caching(self) -> None:
1502
+ @pytest.mark.slow
1503
+ @pytest.mark.kubernetes
1504
+ @pytest.mark.online
1505
+ def test_kubernetes_cwl_conformance_with_caching(self, cwl_v1_2_spec: Path) -> None:
1247
1506
  self.test_kubernetes_cwl_conformance(
1507
+ cwl_v1_2_spec,
1248
1508
  caching=True,
1249
- junit_file=os.path.join(
1250
- self.rootDir, "kubernetes-caching-conformance-1.2.junit.xml"
1251
- ),
1509
+ junit_file=os.path.abspath("kubernetes-caching-conformance-1.2.junit.xml"),
1252
1510
  )
1253
1511
 
1254
1512
  @slow
1255
1513
  @needs_wes_server
1256
- def test_wes_server_cwl_conformance(self) -> None:
1514
+ @pytest.mark.slow
1515
+ @pytest.mark.wes_server
1516
+ @pytest.mark.online
1517
+ def test_wes_server_cwl_conformance(self, cwl_v1_2_spec: Path) -> None:
1257
1518
  """
1258
1519
  Run the CWL conformance tests via WES. TOIL_WES_ENDPOINT must be
1259
1520
  specified. If the WES server requires authentication, set TOIL_WES_USER
@@ -1264,7 +1525,7 @@ class CWLv12Test(ToilTest):
1264
1525
  TOIL_WES_ENDPOINT=http://localhost:8080 \
1265
1526
  TOIL_WES_USER=test \
1266
1527
  TOIL_WES_PASSWORD=password \
1267
- python -m pytest src/toil/test/cwl/cwlTest.py::CWLv12Test::test_wes_server_cwl_conformance -vv --log-level INFO --log-cli-level INFO
1528
+ python -m pytest src/toil/test/cwl/cwlTest.py::TestCWLv12::test_wes_server_cwl_conformance -vv --log-level INFO --log-cli-level INFO
1268
1529
  """
1269
1530
  endpoint = os.environ.get("TOIL_WES_ENDPOINT")
1270
1531
  extra_args = [f"--wes_endpoint={endpoint}"]
@@ -1279,6 +1540,7 @@ class CWLv12Test(ToilTest):
1279
1540
  # e.g.: https://github.com/common-workflow-language/cwl-v1.2/blob/1.2.1_proposed/tests/mixed-versions/wf-v10.cwl#L4-L10
1280
1541
 
1281
1542
  self.test_run_conformance(
1543
+ cwl_v1_2_spec,
1282
1544
  runner="toil-wes-cwl-runner",
1283
1545
  selected_tests="1-309,313-337",
1284
1546
  extra_args=extra_args,
@@ -1286,183 +1548,162 @@ class CWLv12Test(ToilTest):
1286
1548
 
1287
1549
 
1288
1550
  @needs_cwl
1551
+ @pytest.mark.cwl
1289
1552
  @pytest.mark.cwl_small_log_dir
1290
1553
  def test_workflow_echo_string_scatter_stderr_log_dir(tmp_path: Path) -> None:
1291
1554
  log_dir = tmp_path / "cwl-logs"
1292
- job_store = "test_workflow_echo_string_scatter_stderr_log_dir"
1293
- toil = "toil-cwl-runner"
1294
- jobstore = f"--jobStore={job_store}"
1295
- option_1 = "--strict-memory-limit"
1296
- option_2 = "--force-docker-pull"
1297
- option_3 = "--clean=always"
1298
- option_4 = f"--log-dir={log_dir}"
1299
- cwl = os.path.join(
1300
- os.path.dirname(__file__), "echo_string_scatter_capture_stdout.cwl"
1301
- )
1302
- cmd = [toil, jobstore, option_1, option_2, option_3, option_4, cwl]
1303
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1304
- stdout, stderr = p.communicate()
1305
- outputs = json.loads(stdout)
1306
- out_list = outputs["list_out"]
1307
- assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
1308
- out_base = outputs["list_out"][0]
1309
- # This is a test on the scatter functionality and stdout.
1310
- # Each value of scatter should generate a separate file in the output.
1311
- for index, file in enumerate(out_list):
1312
- if index > 0:
1313
- new_file_loc = out_base["location"] + f"_{index + 1}"
1314
- else:
1315
- new_file_loc = out_base["location"]
1316
- assert (
1317
- new_file_loc == file["location"]
1318
- ), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
1555
+ with get_data("test/cwl/echo_string_scatter_capture_stdout.cwl") as cwl_file:
1556
+ cmd = [
1557
+ "toil-cwl-runner",
1558
+ f"--jobStore={tmp_path / 'jobstore'}",
1559
+ "--strict-memory-limit",
1560
+ f"--log-dir={log_dir}",
1561
+ str(cwl_file),
1562
+ ]
1563
+ p = subprocess.run(cmd, capture_output=True, text=True)
1564
+ outputs = json.loads(p.stdout)
1565
+ out_list = outputs["list_out"]
1566
+ assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
1567
+ out_base = outputs["list_out"][0]
1568
+ # This is a test on the scatter functionality and stdout.
1569
+ # Each value of scatter should generate a separate file in the output.
1570
+ for index, file in enumerate(out_list):
1571
+ if index > 0:
1572
+ new_file_loc = out_base["location"] + f"_{index + 1}"
1573
+ else:
1574
+ new_file_loc = out_base["location"]
1575
+ assert (
1576
+ new_file_loc == file["location"]
1577
+ ), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
1319
1578
 
1320
- assert b"Finished toil run successfully" in stderr
1321
- assert p.returncode == 0
1579
+ assert "Finished toil run successfully" in p.stderr
1580
+ assert p.returncode == 0
1322
1581
 
1323
- assert log_dir.exists()
1324
- scatter_0 = log_dir / "echo-test-scatter.0.scatter"
1325
- scatter_1 = log_dir / "echo-test-scatter.1.scatter"
1326
- list_0 = log_dir / "echo-test-scatter.0.list"
1327
- list_1 = log_dir / "echo-test-scatter.1.list"
1328
- assert scatter_0.exists()
1329
- assert scatter_1.exists()
1330
- assert list_0.exists()
1331
- assert list_1.exists()
1582
+ assert log_dir.exists()
1583
+ scatter_0 = log_dir / "echo-test-scatter.0.scatter"
1584
+ scatter_1 = log_dir / "echo-test-scatter.1.scatter"
1585
+ list_0 = log_dir / "echo-test-scatter.0.list"
1586
+ list_1 = log_dir / "echo-test-scatter.1.list"
1587
+ assert scatter_0.exists()
1588
+ assert scatter_1.exists()
1589
+ assert list_0.exists()
1590
+ assert list_1.exists()
1332
1591
 
1333
1592
 
1334
1593
  @needs_cwl
1594
+ @pytest.mark.cwl
1335
1595
  @pytest.mark.cwl_small_log_dir
1336
1596
  def test_log_dir_echo_no_output(tmp_path: Path) -> None:
1337
1597
  log_dir = tmp_path / "cwl-logs"
1338
- job_store = "test_log_dir_echo_no_output"
1339
- toil = "toil-cwl-runner"
1340
- jobstore = f"--jobStore={job_store}"
1341
- option_1 = "--strict-memory-limit"
1342
- option_2 = "--force-docker-pull"
1343
- option_3 = "--clean=always"
1344
- option_4 = f"--log-dir={log_dir}"
1345
- cwl = os.path.join(os.path.dirname(__file__), "echo-stdout-log-dir.cwl")
1346
- cmd = [toil, jobstore, option_1, option_2, option_3, option_4, cwl]
1347
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1348
- stdout, stderr = p.communicate()
1349
-
1350
- tmp_path = log_dir
1351
-
1352
- assert log_dir.exists()
1353
- assert len(list(tmp_path.iterdir())) == 1
1354
-
1355
- subdir = next(tmp_path.iterdir())
1356
- assert subdir.name == "echo"
1357
- assert subdir.is_dir()
1358
- assert len(list(subdir.iterdir())) == 1
1359
- result = next(subdir.iterdir())
1360
- assert result.name == "out.txt"
1361
- output = open(result).read()
1362
- assert "hello" in output
1598
+ job_store = tmp_path / "test_log_dir_echo_no_output"
1599
+ with get_data("test/cwl/echo-stdout-log-dir.cwl") as cwl_file:
1600
+ cmd = [
1601
+ "toil-cwl-runner",
1602
+ f"--jobStore={job_store}",
1603
+ "--strict-memory-limit",
1604
+ f"--log-dir={str(log_dir)}",
1605
+ str(cwl_file),
1606
+ ]
1607
+ subprocess.run(cmd)
1608
+
1609
+ assert log_dir.exists()
1610
+ assert sum(1 for _ in log_dir.iterdir()) == 1
1611
+
1612
+ subdir = next(log_dir.iterdir())
1613
+ assert subdir.name == "echo"
1614
+ assert subdir.is_dir()
1615
+ assert sum(1 for _ in subdir.iterdir()) == 1
1616
+ result = next(subdir.iterdir())
1617
+ assert result.name == "out.txt"
1618
+ assert "hello" in result.read_text()
1363
1619
 
1364
1620
 
1365
1621
  @needs_cwl
1622
+ @pytest.mark.cwl
1366
1623
  @pytest.mark.cwl_small_log_dir
1367
1624
  def test_log_dir_echo_stderr(tmp_path: Path) -> None:
1368
1625
  log_dir = tmp_path / "cwl-logs"
1626
+ log_dir.mkdir()
1627
+ with get_data("test/cwl/echo-stderr.cwl") as cwl_file:
1628
+ cmd = [
1629
+ "toil-cwl-runner",
1630
+ f"--jobStore={str(tmp_path / 'test_log_dir_echo_stderr')}",
1631
+ "--strict-memory-limit",
1632
+ "--force-docker-pull",
1633
+ "--clean=always",
1634
+ f"--log-dir={str(log_dir)}",
1635
+ str(cwl_file),
1636
+ ]
1637
+ subprocess.run(cmd)
1638
+ tmp_path = log_dir
1369
1639
 
1370
- job_store = "test_log_dir_echo_stderr"
1371
- toil = "toil-cwl-runner"
1372
- jobstore = f"--jobStore={job_store}"
1373
- option_1 = "--strict-memory-limit"
1374
- option_2 = "--force-docker-pull"
1375
- option_3 = "--clean=always"
1376
- option_4 = f"--log-dir={log_dir}"
1377
- cwl = os.path.join(os.path.dirname(__file__), "echo-stderr.cwl")
1378
- cmd = [toil, jobstore, option_1, option_2, option_3, option_4, cwl]
1379
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1380
- stdout, stderr = p.communicate()
1381
- tmp_path = log_dir
1382
-
1383
- assert len(list(tmp_path.iterdir())) == 1
1384
-
1385
- subdir = next(tmp_path.iterdir())
1386
- assert subdir.name == "echo-stderr.cwl"
1387
- assert subdir.is_dir()
1388
- assert len(list(subdir.iterdir())) == 1
1389
- result = next(subdir.iterdir())
1390
- assert result.name == "out.txt"
1391
- output = open(result).read()
1392
- assert output == "hello\n"
1640
+ assert len(list(tmp_path.iterdir())) == 1
1641
+
1642
+ subdir = next(tmp_path.iterdir())
1643
+ assert subdir.name == "echo-stderr.cwl"
1644
+ assert subdir.is_dir()
1645
+ assert len(list(subdir.iterdir())) == 1
1646
+ result = next(subdir.iterdir())
1647
+ assert result.name == "out.txt"
1648
+ output = open(result).read()
1649
+ assert output == "hello\n"
1393
1650
 
1394
1651
 
1395
1652
  # TODO: It's not clear how this test tests filename conflict resolution; it
1396
1653
  # seems like it runs a python script to copy some files and makes sure the
1397
1654
  # workflow doesn't fail.
1398
1655
  @needs_cwl
1656
+ @pytest.mark.cwl
1399
1657
  @pytest.mark.cwl_small_log_dir
1400
1658
  def test_filename_conflict_resolution(tmp_path: Path) -> None:
1401
- out_dir = tmp_path / "cwl-out-dir"
1402
- toil = "toil-cwl-runner"
1403
- options = [
1404
- f"--outdir={out_dir}",
1405
- "--clean=always",
1406
- ]
1407
- cwl = os.path.join(
1408
- os.path.dirname(__file__), "test_filename_conflict_resolution.cwl"
1409
- )
1410
- input = os.path.join(
1411
- os.path.dirname(__file__), "test_filename_conflict_resolution.ms"
1412
- )
1413
- cwl_inputs = ["--msin", input]
1414
- cmd = [toil] + options + [cwl] + cwl_inputs
1415
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1416
- stdout, stderr = p.communicate()
1417
- assert b"Finished toil run successfully" in stderr
1418
- assert p.returncode == 0
1659
+ with get_data("test/cwl/test_filename_conflict_resolution.cwl") as cwl_file:
1660
+ with get_data("test/cwl/test_filename_conflict_resolution.ms") as msin:
1661
+ cmd = [
1662
+ "toil-cwl-runner",
1663
+ f"--outdir={tmp_path}",
1664
+ str(cwl_file),
1665
+ "--msin",
1666
+ str(msin),
1667
+ ]
1668
+ p = subprocess.run(cmd, capture_output=True, text=True)
1669
+ assert "Finished toil run successfully" in p.stderr
1670
+ assert p.returncode == 0
1419
1671
 
1420
1672
 
1421
1673
  @needs_cwl
1674
+ @pytest.mark.cwl
1422
1675
  @pytest.mark.cwl_small_log_dir
1423
1676
  def test_filename_conflict_resolution_3_or_more(tmp_path: Path) -> None:
1424
- out_dir = tmp_path / "cwl-out-dir"
1425
- toil = "toil-cwl-runner"
1426
- options = [
1427
- f"--outdir={out_dir}",
1428
- "--clean=always",
1429
- ]
1430
- cwl = os.path.join(os.path.dirname(__file__), "scatter_duplicate_outputs.cwl")
1431
- cmd = [toil] + options + [cwl]
1432
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1433
- stdout, stderr = p.communicate()
1434
- assert b"Finished toil run successfully" in stderr
1435
- assert p.returncode == 0
1436
- assert (
1437
- len(os.listdir(out_dir)) == 9
1438
- ), "All 9 files made by the scatter should be in the directory"
1677
+ with get_data("test/cwl/scatter_duplicate_outputs.cwl") as cwl_file:
1678
+ cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
1679
+ p = subprocess.run(cmd, capture_output=True, text=True)
1680
+ assert "Finished toil run successfully" in p.stderr
1681
+ assert p.returncode == 0
1682
+ assert (
1683
+ sum(1 for _ in tmp_path.iterdir()) == 9
1684
+ ), f"All 9 files made by the scatter should be in the directory: {tmp_path}"
1439
1685
 
1440
1686
 
1441
1687
  @needs_cwl
1442
1688
  @needs_docker
1689
+ @pytest.mark.cwl
1690
+ @pytest.mark.docker
1443
1691
  @pytest.mark.cwl_small_log_dir
1444
1692
  def test_filename_conflict_detection(tmp_path: Path) -> None:
1445
1693
  """
1446
1694
  Make sure we don't just stage files over each other when using a container.
1447
1695
  """
1448
- out_dir = tmp_path / "cwl-out-dir"
1449
- toil = "toil-cwl-runner"
1450
- options = [
1451
- f"--outdir={out_dir}",
1452
- "--clean=always",
1453
- ]
1454
- cwl = os.path.join(
1455
- os.path.dirname(__file__), "test_filename_conflict_detection.cwl"
1456
- )
1457
- cmd = [toil] + options + [cwl]
1458
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1459
- stdout, stderr = p.communicate()
1460
- assert b"File staging conflict" in stderr
1461
- assert p.returncode != 0
1696
+ with get_data("test/cwl/test_filename_conflict_detection.cwl") as cwl_file:
1697
+ cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
1698
+ p = subprocess.run(cmd, capture_output=True, text=True)
1699
+ assert "File staging conflict" in p.stderr
1700
+ assert p.returncode != 0
1462
1701
 
1463
1702
 
1464
1703
  @needs_cwl
1465
1704
  @needs_docker
1705
+ @pytest.mark.cwl
1706
+ @pytest.mark.docker
1466
1707
  @pytest.mark.cwl_small_log_dir
1467
1708
  def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
1468
1709
  """
@@ -1470,101 +1711,89 @@ def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
1470
1711
 
1471
1712
  Specifically, when using a container and the files are at the root of the work dir.
1472
1713
  """
1473
- out_dir = tmp_path / "cwl-out-dir"
1474
- toil = "toil-cwl-runner"
1475
- options = [
1476
- f"--outdir={out_dir}",
1477
- "--clean=always",
1478
- ]
1479
- cwl = os.path.join(
1480
- os.path.dirname(__file__), "test_filename_conflict_detection_at_root.cwl"
1481
- )
1482
- cmd = [toil] + options + [cwl]
1483
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1484
- stdout, stderr = p.communicate()
1485
- assert b"File staging conflict" in stderr
1486
- assert p.returncode != 0
1714
+ with get_data("test/cwl/test_filename_conflict_detection_at_root.cwl") as cwl_file:
1715
+ cmd = ["toil-cwl-runner", f"--outdir={tmp_path}", str(cwl_file)]
1716
+ p = subprocess.run(cmd, capture_output=True, text=True)
1717
+ assert "File staging conflict" in p.stderr
1718
+ assert p.returncode != 0
1487
1719
 
1488
1720
 
1489
1721
  @needs_cwl
1722
+ @pytest.mark.cwl
1490
1723
  @pytest.mark.cwl_small
1491
- def test_pick_value_with_one_null_value(caplog: pytest.LogCaptureFixture) -> None:
1724
+ def test_pick_value_with_one_null_value(
1725
+ caplog: pytest.LogCaptureFixture, tmp_path: Path
1726
+ ) -> None:
1492
1727
  """
1493
1728
  Make sure toil-cwl-runner does not false log a warning when pickValue is
1494
1729
  used but outputSource only contains one null value. See: #3991.
1495
1730
  """
1496
1731
  from toil.cwl import cwltoil
1497
1732
 
1498
- cwl_file = os.path.join(os.path.dirname(__file__), "conditional_wf.cwl")
1499
- job_file = os.path.join(os.path.dirname(__file__), "conditional_wf.yaml")
1500
- args = [cwl_file, job_file]
1501
-
1502
- with caplog.at_level(logging.WARNING, logger="toil.cwl.cwltoil"):
1503
- cwltoil.main(args)
1504
- for line in caplog.messages:
1505
- assert (
1506
- "You had a conditional step that did not run, but you did not use pickValue to handle the skipped input."
1507
- not in line
1508
- )
1733
+ with get_data("test/cwl/conditional_wf.cwl") as cwl_file:
1734
+ with get_data("test/cwl/conditional_wf.yaml") as job_file:
1735
+ with caplog.at_level(logging.WARNING, logger="toil.cwl.cwltoil"):
1736
+ cwltoil.main([f"--outdir={tmp_path}", str(cwl_file), str(job_file)])
1737
+ for line in caplog.messages:
1738
+ assert (
1739
+ "You had a conditional step that did not run, but you did not use pickValue to handle the skipped input."
1740
+ not in line
1741
+ )
1509
1742
 
1510
1743
 
1511
1744
  @needs_cwl
1745
+ @pytest.mark.cwl
1512
1746
  @pytest.mark.cwl_small
1513
- def test_workflow_echo_string() -> None:
1514
- toil = "toil-cwl-runner"
1515
- jobstore = f"--jobStore=file:explicit-local-jobstore-{uuid.uuid4()}"
1516
- option_1 = "--strict-memory-limit"
1517
- option_2 = "--force-docker-pull"
1518
- option_3 = "--clean=always"
1519
- cwl = os.path.join(os.path.dirname(__file__), "echo_string.cwl")
1520
- cmd = [toil, jobstore, option_1, option_2, option_3, cwl]
1521
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1522
- stdout, stderr = p.communicate()
1523
- stdout2 = stdout.decode("utf-8")
1524
- stderr2 = stderr.decode("utf-8")
1525
- assert (
1526
- stdout2.strip() == "{}"
1527
- ), f"Got wrong output: {stdout2}\nWith error: {stderr2}"
1528
- assert "Finished toil run successfully" in stderr2
1529
- assert p.returncode == 0
1747
+ def test_workflow_echo_string(tmp_path: Path) -> None:
1748
+ with get_data("test/cwl/echo_string.cwl") as cwl_file:
1749
+ cmd = [
1750
+ "toil-cwl-runner",
1751
+ f"--jobStore=file:{tmp_path / 'jobstore'}",
1752
+ "--strict-memory-limit",
1753
+ str(cwl_file),
1754
+ ]
1755
+ p = subprocess.run(cmd, capture_output=True, text=True)
1756
+ assert (
1757
+ p.stdout.strip() == "{}"
1758
+ ), f"Got wrong output: {p.stdout}\nWith error: {p.stderr}"
1759
+ assert "Finished toil run successfully" in p.stderr
1760
+ assert p.returncode == 0
1530
1761
 
1531
1762
 
1532
1763
  @needs_cwl
1764
+ @pytest.mark.cwl
1533
1765
  @pytest.mark.cwl_small
1534
- def test_workflow_echo_string_scatter_capture_stdout() -> None:
1535
- toil = "toil-cwl-runner"
1536
- jobstore = f"--jobStore=file:explicit-local-jobstore-{uuid.uuid4()}"
1537
- option_1 = "--strict-memory-limit"
1538
- option_2 = "--force-docker-pull"
1539
- option_3 = "--clean=always"
1540
- cwl = os.path.join(
1541
- os.path.dirname(__file__), "echo_string_scatter_capture_stdout.cwl"
1542
- )
1543
- cmd = [toil, jobstore, option_1, option_2, option_3, cwl]
1544
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1545
- stdout, stderr = p.communicate()
1546
- log.debug("Workflow standard output: %s", stdout)
1547
- assert len(stdout) > 0
1548
- outputs = json.loads(stdout)
1549
- out_list = outputs["list_out"]
1550
- assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
1551
- out_base = outputs["list_out"][0]
1552
- # This is a test on the scatter functionality and stdout.
1553
- # Each value of scatter should generate a separate file in the output.
1554
- for index, file in enumerate(out_list):
1555
- if index > 0:
1556
- new_file_loc = out_base["location"] + f"_{index + 1}"
1557
- else:
1558
- new_file_loc = out_base["location"]
1559
- assert (
1560
- new_file_loc == file["location"]
1561
- ), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
1766
+ def test_workflow_echo_string_scatter_capture_stdout(tmp_path: Path) -> None:
1767
+ with get_data("test/cwl/echo_string_scatter_capture_stdout.cwl") as cwl_file:
1768
+ cmd = [
1769
+ "toil-cwl-runner",
1770
+ f"--jobStore=file:{tmp_path / 'jobStore'}",
1771
+ "--strict-memory-limit",
1772
+ str(cwl_file),
1773
+ ]
1774
+ p = subprocess.run(cmd, capture_output=True, text=True)
1775
+ assert len(p.stdout) > 0
1776
+ outputs = json.loads(p.stdout)
1777
+ out_list = outputs["list_out"]
1778
+ assert len(out_list) == 2, f"outList shoud have two file elements {out_list}"
1779
+ out_base = outputs["list_out"][0]
1780
+ # This is a test on the scatter functionality and stdout.
1781
+ # Each value of scatter should generate a separate file in the output.
1782
+ for index, file in enumerate(out_list):
1783
+ if index > 0:
1784
+ new_file_loc = out_base["location"] + f"_{index + 1}"
1785
+ else:
1786
+ new_file_loc = out_base["location"]
1787
+ assert (
1788
+ new_file_loc == file["location"]
1789
+ ), f"Toil should have detected conflicts for these stdout files {new_file_loc} and {file}"
1562
1790
 
1563
- assert b"Finished toil run successfully" in stderr
1564
- assert p.returncode == 0
1791
+ assert "Finished toil run successfully" in p.stderr
1792
+ assert p.returncode == 0
1565
1793
 
1566
1794
 
1567
1795
  @needs_cwl
1796
+ @pytest.mark.cwl
1568
1797
  @pytest.mark.cwl_small
1569
1798
  def test_visit_top_cwl_class() -> None:
1570
1799
  structure = {
@@ -1616,6 +1845,7 @@ def test_visit_top_cwl_class() -> None:
1616
1845
 
1617
1846
 
1618
1847
  @needs_cwl
1848
+ @pytest.mark.cwl
1619
1849
  @pytest.mark.cwl_small
1620
1850
  def test_visit_cwl_class_and_reduce() -> None:
1621
1851
  structure = {
@@ -1677,6 +1907,7 @@ def test_visit_cwl_class_and_reduce() -> None:
1677
1907
 
1678
1908
 
1679
1909
  @needs_cwl
1910
+ @pytest.mark.cwl
1680
1911
  @pytest.mark.cwl_small
1681
1912
  def test_download_structure(tmp_path: Path) -> None:
1682
1913
  """
@@ -1701,7 +1932,7 @@ def test_download_structure(tmp_path: Path) -> None:
1701
1932
  }
1702
1933
 
1703
1934
  # Say where to put it on the filesystem
1704
- to_dir = str(tmp_path)
1935
+ to_dir = tmp_path
1705
1936
 
1706
1937
  # Make a fake file store
1707
1938
  file_store = Mock(AbstractFileStore)
@@ -1714,7 +1945,7 @@ def test_download_structure(tmp_path: Path) -> None:
1714
1945
  existing: dict[str, str] = {}
1715
1946
 
1716
1947
  # Do the download
1717
- download_structure(file_store, index, existing, structure, to_dir)
1948
+ download_structure(file_store, index, existing, structure, str(to_dir))
1718
1949
 
1719
1950
  # Check the results
1720
1951
  # 3 files should be made
@@ -1723,61 +1954,53 @@ def test_download_structure(tmp_path: Path) -> None:
1723
1954
  assert len(existing) == 2
1724
1955
 
1725
1956
  # Make sure that the index contents (path to URI) are correct
1726
- assert os.path.join(to_dir, "dir1/dir2/f1") in index
1727
- assert os.path.join(to_dir, "dir1/dir2/f1again") in index
1728
- assert os.path.join(to_dir, "anotherfile") in index
1957
+ assert str(to_dir / "dir1/dir2/f1") in index
1958
+ assert str(to_dir / "dir1/dir2/f1again") in index
1959
+ assert str(to_dir / "anotherfile") in index
1729
1960
  assert (
1730
- index[os.path.join(to_dir, "dir1/dir2/f1")]
1961
+ index[str(to_dir / "dir1/dir2/f1")]
1731
1962
  == cast(
1732
1963
  DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
1733
1964
  )["f1"]
1734
1965
  )
1735
1966
  assert (
1736
- index[os.path.join(to_dir, "dir1/dir2/f1again")]
1967
+ index[str(to_dir / "dir1/dir2/f1again")]
1737
1968
  == cast(
1738
1969
  DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
1739
1970
  )["f1again"]
1740
1971
  )
1741
- assert index[os.path.join(to_dir, "anotherfile")] == structure["anotherfile"]
1972
+ assert index[str(to_dir / "anotherfile")] == structure["anotherfile"]
1742
1973
 
1743
1974
  # And the existing contents (URI to path)
1744
1975
  assert "toilfile:" + fid1.pack() in existing
1745
1976
  assert "toilfile:" + fid2.pack() in existing
1746
1977
  assert existing["toilfile:" + fid1.pack()] in [
1747
- os.path.join(to_dir, "dir1/dir2/f1"),
1748
- os.path.join(to_dir, "dir1/dir2/f1again"),
1978
+ str(to_dir / "dir1/dir2/f1"),
1979
+ str(to_dir / "dir1/dir2/f1again"),
1749
1980
  ]
1750
- assert existing["toilfile:" + fid2.pack()] == os.path.join(to_dir, "anotherfile")
1981
+ assert existing["toilfile:" + fid2.pack()] == str(to_dir / "anotherfile")
1751
1982
 
1752
1983
  # The directory structure should be created for real
1753
- assert os.path.isdir(os.path.join(to_dir, "dir1")) is True
1754
- assert os.path.isdir(os.path.join(to_dir, "dir1/dir2")) is True
1755
- assert os.path.isdir(os.path.join(to_dir, "dir1/dir2/dir2sub")) is True
1756
- assert os.path.isdir(os.path.join(to_dir, "dir1/dir3")) is True
1984
+ assert (to_dir / "dir1").is_dir()
1985
+ assert (to_dir / "dir1/dir2").is_dir()
1986
+ assert (to_dir / "dir1/dir2/dir2sub").is_dir()
1987
+ assert (to_dir / "dir1/dir3").is_dir()
1757
1988
 
1758
1989
  # The file store should have been asked to do the download
1759
1990
  file_store.readGlobalFile.assert_has_calls(
1760
1991
  [
1761
- call(fid1, os.path.join(to_dir, "dir1/dir2/f1"), symlink=False),
1762
- call(fid1, os.path.join(to_dir, "dir1/dir2/f1again"), symlink=False),
1763
- call(fid2, os.path.join(to_dir, "anotherfile"), symlink=False),
1992
+ call(fid1, str(to_dir / "dir1/dir2/f1"), symlink=False),
1993
+ call(fid1, str(to_dir / "dir1/dir2/f1again"), symlink=False),
1994
+ call(fid2, str(to_dir / "anotherfile"), symlink=False),
1764
1995
  ],
1765
1996
  any_order=True,
1766
1997
  )
1767
1998
 
1768
1999
 
1769
2000
  @needs_cwl
2001
+ @pytest.mark.cwl
1770
2002
  @pytest.mark.timeout(300)
1771
2003
  def test_import_on_workers() -> None:
1772
- args = [
1773
- "src/toil/test/cwl/download.cwl",
1774
- "src/toil/test/cwl/download_file.json",
1775
- "--runImportsOnWorkers",
1776
- "--importWorkersDisk=10MiB",
1777
- "--realTimeLogging=True",
1778
- "--logLevel=INFO",
1779
- "--logColors=False",
1780
- ]
1781
2004
  from toil.cwl import cwltoil
1782
2005
 
1783
2006
  detector = ImportWorkersMessageHandler()
@@ -1785,9 +2008,21 @@ def test_import_on_workers() -> None:
1785
2008
  # Set up a log message detector to the root logger
1786
2009
  logging.getLogger().addHandler(detector)
1787
2010
 
1788
- cwltoil.main(args)
2011
+ with get_data("test/cwl/download.cwl") as cwl_file:
2012
+ with get_data("test/cwl/directory/directory/file.txt") as file_path:
2013
+ args = [
2014
+ "--runImportsOnWorkers",
2015
+ "--importWorkersDisk=10MiB",
2016
+ "--realTimeLogging=True",
2017
+ "--logLevel=INFO",
2018
+ "--logColors=False",
2019
+ str(cwl_file),
2020
+ "--input",
2021
+ str(file_path),
2022
+ ]
2023
+ cwltoil.main(args)
1789
2024
 
1790
- assert detector.detected is True
2025
+ assert detector.detected is True
1791
2026
 
1792
2027
 
1793
2028
  # StreamHandler is generic, _typeshed doesn't exist at runtime, do a bit of typing trickery, see https://github.com/python/typeshed/issues/5680