toil 8.1.0b1__py3-none-any.whl → 9.0.0__py3-none-any.whl

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