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

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