toil 5.12.0__py3-none-any.whl → 6.1.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 (164) hide show
  1. toil/__init__.py +18 -13
  2. toil/batchSystems/abstractBatchSystem.py +39 -13
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +24 -24
  4. toil/batchSystems/awsBatch.py +14 -14
  5. toil/batchSystems/cleanup_support.py +7 -3
  6. toil/batchSystems/contained_executor.py +3 -3
  7. toil/batchSystems/htcondor.py +0 -1
  8. toil/batchSystems/kubernetes.py +34 -31
  9. toil/batchSystems/local_support.py +3 -1
  10. toil/batchSystems/lsf.py +7 -7
  11. toil/batchSystems/mesos/batchSystem.py +7 -7
  12. toil/batchSystems/options.py +32 -83
  13. toil/batchSystems/registry.py +104 -23
  14. toil/batchSystems/singleMachine.py +16 -13
  15. toil/batchSystems/slurm.py +87 -16
  16. toil/batchSystems/torque.py +0 -1
  17. toil/bus.py +44 -8
  18. toil/common.py +544 -753
  19. toil/cwl/__init__.py +28 -32
  20. toil/cwl/cwltoil.py +595 -574
  21. toil/cwl/utils.py +55 -10
  22. toil/exceptions.py +1 -1
  23. toil/fileStores/__init__.py +2 -2
  24. toil/fileStores/abstractFileStore.py +88 -14
  25. toil/fileStores/cachingFileStore.py +610 -549
  26. toil/fileStores/nonCachingFileStore.py +46 -22
  27. toil/job.py +182 -101
  28. toil/jobStores/abstractJobStore.py +161 -95
  29. toil/jobStores/aws/jobStore.py +23 -9
  30. toil/jobStores/aws/utils.py +6 -6
  31. toil/jobStores/fileJobStore.py +116 -18
  32. toil/jobStores/googleJobStore.py +16 -7
  33. toil/jobStores/utils.py +5 -6
  34. toil/leader.py +87 -56
  35. toil/lib/accelerators.py +10 -5
  36. toil/lib/aws/__init__.py +3 -14
  37. toil/lib/aws/ami.py +22 -9
  38. toil/lib/aws/iam.py +21 -13
  39. toil/lib/aws/session.py +2 -16
  40. toil/lib/aws/utils.py +4 -5
  41. toil/lib/compatibility.py +1 -1
  42. toil/lib/conversions.py +26 -3
  43. toil/lib/docker.py +22 -23
  44. toil/lib/ec2.py +10 -6
  45. toil/lib/ec2nodes.py +106 -100
  46. toil/lib/encryption/_nacl.py +2 -1
  47. toil/lib/generatedEC2Lists.py +325 -18
  48. toil/lib/io.py +49 -2
  49. toil/lib/misc.py +1 -1
  50. toil/lib/resources.py +9 -2
  51. toil/lib/threading.py +101 -38
  52. toil/options/common.py +736 -0
  53. toil/options/cwl.py +336 -0
  54. toil/options/wdl.py +37 -0
  55. toil/provisioners/abstractProvisioner.py +9 -4
  56. toil/provisioners/aws/__init__.py +3 -6
  57. toil/provisioners/aws/awsProvisioner.py +6 -0
  58. toil/provisioners/clusterScaler.py +3 -2
  59. toil/provisioners/gceProvisioner.py +2 -2
  60. toil/realtimeLogger.py +2 -1
  61. toil/resource.py +24 -18
  62. toil/server/app.py +2 -3
  63. toil/server/cli/wes_cwl_runner.py +4 -4
  64. toil/server/utils.py +1 -1
  65. toil/server/wes/abstract_backend.py +3 -2
  66. toil/server/wes/amazon_wes_utils.py +5 -4
  67. toil/server/wes/tasks.py +2 -3
  68. toil/server/wes/toil_backend.py +2 -10
  69. toil/server/wsgi_app.py +2 -0
  70. toil/serviceManager.py +12 -10
  71. toil/statsAndLogging.py +41 -9
  72. toil/test/__init__.py +29 -54
  73. toil/test/batchSystems/batchSystemTest.py +11 -111
  74. toil/test/batchSystems/test_slurm.py +24 -8
  75. toil/test/cactus/__init__.py +0 -0
  76. toil/test/cactus/test_cactus_integration.py +58 -0
  77. toil/test/cwl/cwlTest.py +438 -223
  78. toil/test/cwl/glob_dir.cwl +15 -0
  79. toil/test/cwl/preemptible.cwl +21 -0
  80. toil/test/cwl/preemptible_expression.cwl +28 -0
  81. toil/test/cwl/revsort.cwl +1 -1
  82. toil/test/cwl/revsort2.cwl +1 -1
  83. toil/test/docs/scriptsTest.py +2 -3
  84. toil/test/jobStores/jobStoreTest.py +34 -21
  85. toil/test/lib/aws/test_iam.py +4 -14
  86. toil/test/lib/aws/test_utils.py +0 -3
  87. toil/test/lib/dockerTest.py +4 -4
  88. toil/test/lib/test_ec2.py +12 -17
  89. toil/test/mesos/helloWorld.py +4 -5
  90. toil/test/mesos/stress.py +1 -1
  91. toil/test/{wdl/conftest.py → options/__init__.py} +0 -10
  92. toil/test/options/options.py +37 -0
  93. toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
  94. toil/test/provisioners/clusterScalerTest.py +6 -4
  95. toil/test/provisioners/clusterTest.py +23 -11
  96. toil/test/provisioners/gceProvisionerTest.py +0 -6
  97. toil/test/provisioners/restartScript.py +3 -2
  98. toil/test/server/serverTest.py +1 -1
  99. toil/test/sort/restart_sort.py +2 -1
  100. toil/test/sort/sort.py +2 -1
  101. toil/test/sort/sortTest.py +2 -13
  102. toil/test/src/autoDeploymentTest.py +45 -45
  103. toil/test/src/busTest.py +5 -5
  104. toil/test/src/checkpointTest.py +2 -2
  105. toil/test/src/deferredFunctionTest.py +1 -1
  106. toil/test/src/fileStoreTest.py +32 -16
  107. toil/test/src/helloWorldTest.py +1 -1
  108. toil/test/src/importExportFileTest.py +1 -1
  109. toil/test/src/jobDescriptionTest.py +2 -1
  110. toil/test/src/jobServiceTest.py +1 -1
  111. toil/test/src/jobTest.py +18 -18
  112. toil/test/src/miscTests.py +5 -3
  113. toil/test/src/promisedRequirementTest.py +3 -3
  114. toil/test/src/realtimeLoggerTest.py +1 -1
  115. toil/test/src/resourceTest.py +2 -2
  116. toil/test/src/restartDAGTest.py +1 -1
  117. toil/test/src/resumabilityTest.py +36 -2
  118. toil/test/src/retainTempDirTest.py +1 -1
  119. toil/test/src/systemTest.py +2 -2
  120. toil/test/src/toilContextManagerTest.py +2 -2
  121. toil/test/src/userDefinedJobArgTypeTest.py +1 -1
  122. toil/test/utils/toilDebugTest.py +98 -32
  123. toil/test/utils/toilKillTest.py +2 -2
  124. toil/test/utils/utilsTest.py +23 -3
  125. toil/test/wdl/wdltoil_test.py +223 -45
  126. toil/toilState.py +7 -6
  127. toil/utils/toilClean.py +1 -1
  128. toil/utils/toilConfig.py +36 -0
  129. toil/utils/toilDebugFile.py +60 -33
  130. toil/utils/toilDebugJob.py +39 -12
  131. toil/utils/toilDestroyCluster.py +1 -1
  132. toil/utils/toilKill.py +1 -1
  133. toil/utils/toilLaunchCluster.py +13 -2
  134. toil/utils/toilMain.py +3 -2
  135. toil/utils/toilRsyncCluster.py +1 -1
  136. toil/utils/toilSshCluster.py +1 -1
  137. toil/utils/toilStats.py +445 -305
  138. toil/utils/toilStatus.py +2 -5
  139. toil/version.py +10 -10
  140. toil/wdl/utils.py +2 -122
  141. toil/wdl/wdltoil.py +1257 -492
  142. toil/worker.py +55 -46
  143. toil-6.1.0.dist-info/METADATA +124 -0
  144. toil-6.1.0.dist-info/RECORD +241 -0
  145. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/WHEEL +1 -1
  146. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/entry_points.txt +0 -1
  147. toil/batchSystems/parasol.py +0 -379
  148. toil/batchSystems/tes.py +0 -459
  149. toil/test/batchSystems/parasolTestSupport.py +0 -117
  150. toil/test/wdl/builtinTest.py +0 -506
  151. toil/test/wdl/toilwdlTest.py +0 -522
  152. toil/wdl/toilwdl.py +0 -141
  153. toil/wdl/versions/dev.py +0 -107
  154. toil/wdl/versions/draft2.py +0 -980
  155. toil/wdl/versions/v1.py +0 -794
  156. toil/wdl/wdl_analysis.py +0 -116
  157. toil/wdl/wdl_functions.py +0 -997
  158. toil/wdl/wdl_synthesis.py +0 -1011
  159. toil/wdl/wdl_types.py +0 -243
  160. toil-5.12.0.dist-info/METADATA +0 -118
  161. toil-5.12.0.dist-info/RECORD +0 -244
  162. /toil/{wdl/versions → options}/__init__.py +0 -0
  163. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/LICENSE +0 -0
  164. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/top_level.txt +0 -0
toil/test/cwl/cwlTest.py CHANGED
@@ -17,6 +17,7 @@ import logging
17
17
  import os
18
18
  import re
19
19
  import shutil
20
+ import stat
20
21
  import subprocess
21
22
  import sys
22
23
  import unittest
@@ -25,25 +26,36 @@ import zipfile
25
26
  from functools import partial
26
27
  from io import StringIO
27
28
  from pathlib import Path
28
- from typing import Dict, List, MutableMapping, Optional, Union
29
+ from typing import (TYPE_CHECKING,
30
+ Any,
31
+ Callable,
32
+ Dict,
33
+ List,
34
+ MutableMapping,
35
+ Optional,
36
+ cast)
29
37
  from unittest.mock import Mock, call
30
38
  from urllib.request import urlretrieve
31
39
 
40
+ if TYPE_CHECKING:
41
+ from cwltool.utils import CWLObjectType
42
+
32
43
  import pytest
33
44
 
34
45
  pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) # noqa
35
46
  sys.path.insert(0, pkg_root) # noqa
36
47
 
37
- from toil.cwl.utils import (download_structure,
48
+ from schema_salad.exceptions import ValidationException
49
+
50
+ from toil.cwl.utils import (DirectoryStructure,
51
+ download_structure,
38
52
  visit_cwl_class_and_reduce,
39
53
  visit_top_cwl_class)
40
54
  from toil.exceptions import FailedJobsException
41
55
  from toil.fileStores import FileID
42
56
  from toil.fileStores.abstractFileStore import AbstractFileStore
43
- from toil.lib.aws import zone_to_region
44
57
  from toil.lib.threading import cpu_count
45
58
  from toil.provisioners import cluster_factory
46
- from toil.provisioners.aws import get_best_aws_zone
47
59
  from toil.test import (ToilTest,
48
60
  needs_aws_ec2,
49
61
  needs_aws_s3,
@@ -57,17 +69,15 @@ from toil.test import (ToilTest,
57
69
  needs_local_cuda,
58
70
  needs_lsf,
59
71
  needs_mesos,
60
- needs_parasol,
72
+ needs_online,
61
73
  needs_slurm,
62
74
  needs_torque,
63
75
  needs_wes_server,
64
76
  slow)
65
- from toil.test.provisioners.aws.awsProvisionerTest import \
66
- AbstractAWSAutoscaleTest
67
77
  from toil.test.provisioners.clusterTest import AbstractClusterTest
68
78
 
69
79
  log = logging.getLogger(__name__)
70
- CONFORMANCE_TEST_TIMEOUT = 3600
80
+ CONFORMANCE_TEST_TIMEOUT = 10000
71
81
 
72
82
 
73
83
  def run_conformance_tests(
@@ -75,14 +85,14 @@ def run_conformance_tests(
75
85
  yml: str,
76
86
  runner: Optional[str] = None,
77
87
  caching: bool = False,
78
- batchSystem: str = None,
79
- selected_tests: str = None,
80
- selected_tags: str = None,
81
- skipped_tests: str = None,
88
+ batchSystem: Optional[str] = None,
89
+ selected_tests: Optional[str] = None,
90
+ selected_tags: Optional[str] = None,
91
+ skipped_tests: Optional[str] = None,
82
92
  extra_args: Optional[List[str]] = None,
83
93
  must_support_all_features: bool = False,
84
94
  junit_file: Optional[str] = None,
85
- ):
95
+ ) -> None:
86
96
  """
87
97
  Run the CWL conformance tests.
88
98
 
@@ -137,10 +147,12 @@ def run_conformance_tests(
137
147
  "--logDebug",
138
148
  "--statusWait=10",
139
149
  "--retryCount=2",
150
+ "--relax-path-checks",
151
+ # Defaults to 20s but we can't start hundreds of nodejs processes that fast on our CI potatoes
152
+ "--eval-timeout=600",
153
+ f"--caching={caching}"
140
154
  ]
141
155
 
142
- args_passed_directly_to_runner.append(f"--caching={caching}")
143
-
144
156
  if extra_args:
145
157
  args_passed_directly_to_runner += extra_args
146
158
 
@@ -149,6 +161,10 @@ def run_conformance_tests(
149
161
  "--setEnv=SINGULARITY_DOCKER_HUB_MIRROR"
150
162
  )
151
163
 
164
+ if batchSystem is None or batchSystem == "single_machine":
165
+ # Make sure we can run on small machines
166
+ args_passed_directly_to_runner.append("--scale=0.1")
167
+
152
168
  job_store_override = None
153
169
 
154
170
  if batchSystem == "kubernetes":
@@ -159,7 +175,8 @@ def run_conformance_tests(
159
175
  else:
160
176
  # Run tests in parallel on the local machine. Don't run too many
161
177
  # tests at once; we want at least a couple cores for each.
162
- parallel_tests = max(int(cpu_count() / 2), 1)
178
+ # But we need to have at least a few going in parallel or we risk hitting our timeout.
179
+ parallel_tests = max(int(cpu_count() / 2), 4)
163
180
  cmd.append(f"-j{parallel_tests}")
164
181
 
165
182
  if batchSystem:
@@ -193,6 +210,8 @@ def run_conformance_tests(
193
210
  raise e
194
211
 
195
212
 
213
+ TesterFuncType = Callable[[str, str, "CWLObjectType"], None]
214
+
196
215
  @needs_cwl
197
216
  class CWLWorkflowTest(ToilTest):
198
217
  """
@@ -201,27 +220,53 @@ class CWLWorkflowTest(ToilTest):
201
220
  inputs.
202
221
  """
203
222
 
204
- def setUp(self):
223
+ def setUp(self) -> None:
205
224
  """Runs anew before each test to create farm fresh temp dirs."""
206
225
  self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
207
226
  os.makedirs(self.outDir)
208
227
  self.rootDir = self._projectRootPath()
209
228
 
210
- def tearDown(self):
229
+ def tearDown(self) -> None:
211
230
  """Clean up outputs."""
212
231
  if os.path.exists(self.outDir):
213
232
  shutil.rmtree(self.outDir)
214
233
  unittest.TestCase.tearDown(self)
215
234
 
216
- def _tester(self, cwlfile, jobfile, expect, main_args=[], out_name="output"):
235
+ def test_cwl_cmdline_input(self) -> None:
236
+ """
237
+ Test that running a CWL workflow with inputs specified on the command line passes.
238
+ """
239
+ from toil.cwl import cwltoil
240
+ cwlfile = "src/toil/test/cwl/conditional_wf.cwl"
241
+ args = [cwlfile, "--message", "str", "--sleep", "2"]
242
+ st = StringIO()
243
+ # If the workflow runs, it must have had options
244
+ cwltoil.main(args, stdout=st)
245
+
246
+ def _tester(
247
+ self,
248
+ cwlfile: str,
249
+ jobfile: str,
250
+ expect: "CWLObjectType",
251
+ main_args: List[str] = [],
252
+ out_name: str = "output",
253
+ output_here: bool = False,
254
+ ) -> None:
217
255
  from toil.cwl import cwltoil
218
256
 
219
257
  st = StringIO()
220
258
  main_args = main_args[:]
259
+ if not output_here:
260
+ # Don't just dump output in the working directory.
261
+ main_args.extend(
262
+ [
263
+ "--logDebug",
264
+ "--outdir",
265
+ self.outDir
266
+ ]
267
+ )
221
268
  main_args.extend(
222
269
  [
223
- "--outdir",
224
- self.outDir,
225
270
  os.path.join(self.rootDir, cwlfile),
226
271
  os.path.join(self.rootDir, jobfile),
227
272
  ]
@@ -233,7 +278,16 @@ class CWLWorkflowTest(ToilTest):
233
278
  out.get(out_name, {}).pop("nameroot", None)
234
279
  self.assertEqual(out, expect)
235
280
 
236
- def _debug_worker_tester(self, cwlfile, jobfile, expect):
281
+ for k, v in expect.items():
282
+ if isinstance(v, dict) and "class" in v and v["class"] == "File" and "path" in v:
283
+ # This is a top-level output file.
284
+ # None of our output files should be executable.
285
+ self.assertTrue(os.path.exists(v["path"]))
286
+ self.assertFalse(os.stat(v["path"]).st_mode & stat.S_IXUSR)
287
+
288
+ def _debug_worker_tester(
289
+ self, cwlfile: str, jobfile: str, expect: "CWLObjectType"
290
+ ) -> None:
237
291
  from toil.cwl import cwltoil
238
292
 
239
293
  st = StringIO()
@@ -253,21 +307,21 @@ class CWLWorkflowTest(ToilTest):
253
307
  out["output"].pop("nameroot", None)
254
308
  self.assertEqual(out, expect)
255
309
 
256
- def revsort(self, cwl_filename, tester_fn):
310
+ def revsort(self, cwl_filename: str, tester_fn: TesterFuncType) -> None:
257
311
  tester_fn(
258
312
  "src/toil/test/cwl/" + cwl_filename,
259
313
  "src/toil/test/cwl/revsort-job.json",
260
314
  self._expected_revsort_output(self.outDir),
261
315
  )
262
316
 
263
- def revsort_no_checksum(self, cwl_filename, tester_fn):
317
+ def revsort_no_checksum(self, cwl_filename: str, tester_fn: TesterFuncType) -> None:
264
318
  tester_fn(
265
319
  "src/toil/test/cwl/" + cwl_filename,
266
320
  "src/toil/test/cwl/revsort-job.json",
267
321
  self._expected_revsort_nochecksum_output(self.outDir),
268
322
  )
269
323
 
270
- def download(self, inputs, tester_fn):
324
+ def download(self, inputs: str, tester_fn: TesterFuncType) -> None:
271
325
  input_location = os.path.join("src/toil/test/cwl", inputs)
272
326
  tester_fn(
273
327
  "src/toil/test/cwl/download.cwl",
@@ -275,7 +329,7 @@ class CWLWorkflowTest(ToilTest):
275
329
  self._expected_download_output(self.outDir),
276
330
  )
277
331
 
278
- def load_contents(self, inputs, tester_fn):
332
+ def load_contents(self, inputs: str, tester_fn: TesterFuncType) -> None:
279
333
  input_location = os.path.join("src/toil/test/cwl", inputs)
280
334
  tester_fn(
281
335
  "src/toil/test/cwl/load_contents.cwl",
@@ -283,7 +337,7 @@ class CWLWorkflowTest(ToilTest):
283
337
  self._expected_load_contents_output(self.outDir),
284
338
  )
285
339
 
286
- def download_directory(self, inputs, tester_fn):
340
+ def download_directory(self, inputs: str, tester_fn: TesterFuncType) -> None:
287
341
  input_location = os.path.join("src/toil/test/cwl", inputs)
288
342
  tester_fn(
289
343
  "src/toil/test/cwl/download_directory.cwl",
@@ -291,7 +345,7 @@ class CWLWorkflowTest(ToilTest):
291
345
  self._expected_download_output(self.outDir),
292
346
  )
293
347
 
294
- def download_subdirectory(self, inputs, tester_fn):
348
+ def download_subdirectory(self, inputs: str, tester_fn: TesterFuncType) -> None:
295
349
  input_location = os.path.join("src/toil/test/cwl", inputs)
296
350
  tester_fn(
297
351
  "src/toil/test/cwl/download_subdirectory.cwl",
@@ -299,7 +353,7 @@ class CWLWorkflowTest(ToilTest):
299
353
  self._expected_download_output(self.outDir),
300
354
  )
301
355
 
302
- def test_mpi(self):
356
+ def test_mpi(self) -> None:
303
357
  from toil.cwl import cwltoil
304
358
 
305
359
  stdout = StringIO()
@@ -324,7 +378,7 @@ class CWLWorkflowTest(ToilTest):
324
378
  self.assertTrue(isinstance(two_pids[1], int))
325
379
 
326
380
  @needs_aws_s3
327
- def test_s3_as_secondary_file(self):
381
+ def test_s3_as_secondary_file(self) -> None:
328
382
  from toil.cwl import cwltoil
329
383
 
330
384
  stdout = StringIO()
@@ -343,21 +397,21 @@ class CWLWorkflowTest(ToilTest):
343
397
  with open(out["output"]["location"][len("file://") :]) as f:
344
398
  self.assertEqual(f.read().strip(), "When is s4 coming out?")
345
399
 
346
- def test_run_revsort(self):
400
+ def test_run_revsort(self) -> None:
347
401
  self.revsort("revsort.cwl", self._tester)
348
402
 
349
- def test_run_revsort_nochecksum(self):
403
+ def test_run_revsort_nochecksum(self) -> None:
350
404
  self.revsort_no_checksum(
351
405
  "revsort.cwl", partial(self._tester, main_args=["--no-compute-checksum"])
352
406
  )
353
407
 
354
- def test_run_revsort2(self):
408
+ def test_run_revsort2(self) -> None:
355
409
  self.revsort("revsort2.cwl", self._tester)
356
410
 
357
- def test_run_revsort_debug_worker(self):
411
+ def test_run_revsort_debug_worker(self) -> None:
358
412
  self.revsort("revsort.cwl", self._debug_worker_tester)
359
413
 
360
- def test_run_colon_output(self):
414
+ def test_run_colon_output(self) -> None:
361
415
  self._tester(
362
416
  "src/toil/test/cwl/colon_test_output.cwl",
363
417
  "src/toil/test/cwl/colon_test_output_job.yaml",
@@ -365,50 +419,78 @@ class CWLWorkflowTest(ToilTest):
365
419
  out_name="result",
366
420
  )
367
421
 
422
+ def test_glob_dir_bypass_file_store(self) -> None:
423
+ self.maxDiff = 1000
424
+ try:
425
+ # We need to output to the current directory to make sure that
426
+ # works.
427
+ self._tester(
428
+ "src/toil/test/cwl/glob_dir.cwl",
429
+ "src/toil/test/cwl/empty.json",
430
+ self._expected_glob_dir_output(os.getcwd()),
431
+ main_args=["--bypass-file-store"],
432
+ output_here=True
433
+ )
434
+ finally:
435
+ # Clean up anything we made in the current directory.
436
+ try:
437
+ shutil.rmtree(os.path.join(os.getcwd(), "shouldmake"))
438
+ except FileNotFoundError:
439
+ pass
440
+
368
441
  @needs_aws_s3
369
- def test_download_s3(self):
442
+ def test_download_s3(self) -> None:
370
443
  self.download("download_s3.json", self._tester)
371
444
 
372
- def test_download_http(self):
445
+ def test_download_http(self) -> None:
373
446
  self.download("download_http.json", self._tester)
374
447
 
375
- def test_download_https(self):
448
+ def test_download_https(self) -> None:
376
449
  self.download("download_https.json", self._tester)
377
450
 
378
- def test_download_file(self):
451
+ def test_download_https_reference(self) -> None:
452
+ self.download("download_https.json", partial(self._tester, main_args=["--reference-inputs"]))
453
+
454
+ def test_download_file(self) -> None:
379
455
  self.download("download_file.json", self._tester)
380
456
 
381
457
  @needs_aws_s3
382
- def test_download_directory_s3(self):
458
+ def test_download_directory_s3(self) -> None:
383
459
  self.download_directory("download_directory_s3.json", self._tester)
384
460
 
385
- def test_download_directory_file(self):
461
+ @needs_aws_s3
462
+ def test_download_directory_s3_reference(self) -> None:
463
+ self.download_directory("download_directory_s3.json", partial(self._tester, main_args=["--reference-inputs"]))
464
+
465
+ def test_download_directory_file(self) -> None:
386
466
  self.download_directory("download_directory_file.json", self._tester)
387
467
 
388
468
  @needs_aws_s3
389
- def test_download_subdirectory_s3(self):
469
+ def test_download_subdirectory_s3(self) -> None:
390
470
  self.download_subdirectory("download_subdirectory_s3.json", self._tester)
391
471
 
392
- def test_download_subdirectory_file(self):
472
+ def test_download_subdirectory_file(self) -> None:
393
473
  self.download_subdirectory("download_subdirectory_file.json", self._tester)
394
474
 
395
475
  # 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.
396
476
 
397
477
  @needs_aws_s3
398
- def test_load_contents_s3(self):
478
+ def test_load_contents_s3(self) -> None:
399
479
  self.load_contents("download_s3.json", self._tester)
400
480
 
401
- def test_load_contents_http(self):
481
+ def test_load_contents_http(self) -> None:
402
482
  self.load_contents("download_http.json", self._tester)
403
483
 
404
- def test_load_contents_https(self):
484
+ def test_load_contents_https(self) -> None:
405
485
  self.load_contents("download_https.json", self._tester)
406
486
 
407
- def test_load_contents_file(self):
487
+ def test_load_contents_file(self) -> None:
408
488
  self.load_contents("download_file.json", self._tester)
409
489
 
410
490
  @slow
411
- def test_bioconda(self):
491
+ @pytest.mark.integrative
492
+ @unittest.skip("Fails too often due to remote service")
493
+ def test_bioconda(self) -> None:
412
494
  self._tester(
413
495
  "src/toil/test/cwl/seqtk_seq.cwl",
414
496
  "src/toil/test/cwl/seqtk_seq_job.json",
@@ -418,7 +500,19 @@ class CWLWorkflowTest(ToilTest):
418
500
  )
419
501
 
420
502
  @needs_docker
421
- def test_biocontainers(self):
503
+ def test_default_args(self) -> None:
504
+ self._tester(
505
+ "src/toil/test/cwl/seqtk_seq.cwl",
506
+ "src/toil/test/cwl/seqtk_seq_job.json",
507
+ self._expected_seqtk_output(self.outDir),
508
+ main_args=["--default-container", "quay.io/biocontainers/seqtk:r93--0"],
509
+ out_name="output1",
510
+ )
511
+
512
+ @needs_docker
513
+ @pytest.mark.integrative
514
+ @unittest.skip("Fails too often due to remote service")
515
+ def test_biocontainers(self) -> None:
422
516
  self._tester(
423
517
  "src/toil/test/cwl/seqtk_seq.cwl",
424
518
  "src/toil/test/cwl/seqtk_seq_job.json",
@@ -430,7 +524,7 @@ class CWLWorkflowTest(ToilTest):
430
524
  @needs_docker
431
525
  @needs_docker_cuda
432
526
  @needs_local_cuda
433
- def test_cuda(self):
527
+ def test_cuda(self) -> None:
434
528
  self._tester(
435
529
  "src/toil/test/cwl/nvidia_smi.cwl",
436
530
  "src/toil/test/cwl/empty.json",
@@ -439,7 +533,7 @@ class CWLWorkflowTest(ToilTest):
439
533
  )
440
534
 
441
535
  @slow
442
- def test_restart(self):
536
+ def test_restart(self) -> None:
443
537
  """
444
538
  Enable restarts with toil-cwl-runner -- run failing test, re-run correct test.
445
539
  Only implemented for single machine.
@@ -468,7 +562,7 @@ class CWLWorkflowTest(ToilTest):
468
562
  ][-1]
469
563
  os.symlink(os.path.join(cal_path, "date"), f'{os.path.join(outDir, "rev")}')
470
564
 
471
- def path_with_bogus_rev():
565
+ def path_with_bogus_rev() -> str:
472
566
  # append to the front of the PATH so that we check there first
473
567
  return f"{outDir}:" + os.environ["PATH"]
474
568
 
@@ -491,7 +585,7 @@ class CWLWorkflowTest(ToilTest):
491
585
  pass
492
586
 
493
587
  @needs_aws_s3
494
- def test_streamable(self):
588
+ def test_streamable(self, extra_args: Optional[List[str]] = None) -> None:
495
589
  """
496
590
  Test that a file with 'streamable'=True is a named pipe.
497
591
  This is a CWL1.2 feature.
@@ -504,12 +598,16 @@ class CWLWorkflowTest(ToilTest):
504
598
 
505
599
  st = StringIO()
506
600
  args = [
601
+ "--logDebug",
507
602
  "--outdir",
508
603
  self.outDir,
509
604
  jobstore,
510
605
  os.path.join(self.rootDir, cwlfile),
511
606
  os.path.join(self.rootDir, jobfile),
512
607
  ]
608
+ if extra_args:
609
+ args = extra_args + args
610
+ log.info("Run CWL run: %s", " ".join(args))
513
611
  cwltoil.main(args, stdout=st)
514
612
  out = json.loads(st.getvalue())
515
613
  out[out_name].pop("http://commonwl.org/cwltool#generation", None)
@@ -519,12 +617,68 @@ class CWLWorkflowTest(ToilTest):
519
617
  with open(out[out_name]["location"][len("file://") :]) as f:
520
618
  self.assertEqual(f.read().strip(), "When is s4 coming out?")
521
619
 
620
+ @needs_aws_s3
621
+ def test_streamable_reference(self) -> None:
622
+ """
623
+ Test that a streamable file is a stream even when passed around by URI.
624
+ """
625
+ self.test_streamable(extra_args=["--reference-inputs"])
626
+
627
+ def test_preemptible(self) -> None:
628
+ """
629
+ Tests that the http://arvados.org/cwl#UsePreemptible extension is supported.
630
+ """
631
+ cwlfile = "src/toil/test/cwl/preemptible.cwl"
632
+ jobfile = "src/toil/test/cwl/empty.json"
633
+ out_name = "output"
634
+ from toil.cwl import cwltoil
635
+
636
+ st = StringIO()
637
+ args = [
638
+ "--outdir",
639
+ self.outDir,
640
+ os.path.join(self.rootDir, cwlfile),
641
+ os.path.join(self.rootDir, jobfile),
642
+ ]
643
+ cwltoil.main(args, stdout=st)
644
+ out = json.loads(st.getvalue())
645
+ out[out_name].pop("http://commonwl.org/cwltool#generation", None)
646
+ out[out_name].pop("nameext", None)
647
+ out[out_name].pop("nameroot", None)
648
+ with open(out[out_name]["location"][len("file://") :]) as f:
649
+ self.assertEqual(f.read().strip(), "hello")
650
+
651
+ def test_preemptible_expression(self) -> None:
652
+ """
653
+ Tests that the http://arvados.org/cwl#UsePreemptible extension is validated.
654
+ """
655
+ cwlfile = "src/toil/test/cwl/preemptible_expression.cwl"
656
+ jobfile = "src/toil/test/cwl/preemptible_expression.json"
657
+ from toil.cwl import cwltoil
658
+
659
+ st = StringIO()
660
+ args = [
661
+ "--outdir",
662
+ self.outDir,
663
+ os.path.join(self.rootDir, cwlfile),
664
+ os.path.join(self.rootDir, jobfile),
665
+ ]
666
+ try:
667
+ cwltoil.main(args, stdout=st)
668
+ raise RuntimeError("Did not raise correct exception")
669
+ except ValidationException as e:
670
+ # Make sure we chastise the user appropriately.
671
+ assert "expressions are not allowed" in str(e)
672
+
673
+
522
674
  @staticmethod
523
- def _expected_seqtk_output(outDir):
524
- loc = "file://" + os.path.join(outDir, "out")
675
+ def _expected_seqtk_output(outDir: str) -> "CWLObjectType":
676
+ path = os.path.join(outDir, "out")
677
+ loc = "file://" + path
525
678
  return {
526
679
  "output1": {
527
680
  "location": loc,
681
+ "path": path,
528
682
  "checksum": "sha1$322e001e5a99f19abdce9f02ad0f02a17b5066c2",
529
683
  "basename": "out",
530
684
  "class": "File",
@@ -533,11 +687,13 @@ class CWLWorkflowTest(ToilTest):
533
687
  }
534
688
 
535
689
  @staticmethod
536
- def _expected_revsort_output(outDir):
537
- loc = "file://" + os.path.join(outDir, "output.txt")
690
+ def _expected_revsort_output(outDir: str) -> "CWLObjectType":
691
+ path = os.path.join(outDir, "output.txt")
692
+ loc = "file://" + path
538
693
  return {
539
694
  "output": {
540
695
  "location": loc,
696
+ "path": path,
541
697
  "basename": "output.txt",
542
698
  "size": 1111,
543
699
  "class": "File",
@@ -546,11 +702,13 @@ class CWLWorkflowTest(ToilTest):
546
702
  }
547
703
 
548
704
  @staticmethod
549
- def _expected_revsort_nochecksum_output(outDir):
550
- loc = "file://" + os.path.join(outDir, "output.txt")
705
+ def _expected_revsort_nochecksum_output(outDir: str) -> "CWLObjectType":
706
+ path = os.path.join(outDir, "output.txt")
707
+ loc = "file://" + path
551
708
  return {
552
709
  "output": {
553
710
  "location": loc,
711
+ "path": path,
554
712
  "basename": "output.txt",
555
713
  "size": 1111,
556
714
  "class": "File",
@@ -558,8 +716,9 @@ class CWLWorkflowTest(ToilTest):
558
716
  }
559
717
 
560
718
  @staticmethod
561
- def _expected_download_output(outDir):
562
- loc = "file://" + os.path.join(outDir, "output.txt")
719
+ def _expected_download_output(outDir: str) -> "CWLObjectType":
720
+ path = os.path.join(outDir, "output.txt")
721
+ loc = "file://" + path
563
722
  return {
564
723
  "output": {
565
724
  "location": loc,
@@ -567,11 +726,41 @@ class CWLWorkflowTest(ToilTest):
567
726
  "size": 0,
568
727
  "class": "File",
569
728
  "checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
729
+ "path": path
730
+ }
731
+ }
732
+
733
+ @staticmethod
734
+ def _expected_glob_dir_output(out_dir: str) -> "CWLObjectType":
735
+ dir_path = os.path.join(out_dir, "shouldmake")
736
+ dir_loc = "file://" + dir_path
737
+ file_path = os.path.join(dir_path, "test.txt")
738
+ file_loc = os.path.join(dir_loc, "test.txt")
739
+ return {
740
+ "shouldmake": {
741
+ "location": dir_loc,
742
+ "path": dir_path,
743
+ "basename": "shouldmake",
744
+ "nameroot": "shouldmake",
745
+ "nameext": "",
746
+ "class": "Directory",
747
+ "listing": [
748
+ {
749
+ "class": "File",
750
+ "location": file_loc,
751
+ "path": file_path,
752
+ "basename": "test.txt",
753
+ "checksum": "sha1$da39a3ee5e6b4b0d3255bfef95601890afd80709",
754
+ "size": 0,
755
+ "nameroot": "test",
756
+ "nameext": ".txt"
757
+ }
758
+ ]
570
759
  }
571
760
  }
572
761
 
573
762
  @classmethod
574
- def _expected_load_contents_output(cls, out_dir):
763
+ def _expected_load_contents_output(cls, out_dir: str) -> "CWLObjectType":
575
764
  """
576
765
  Generate the putput we expect from load_contents.cwl, when sending
577
766
  output files to the given directory.
@@ -581,11 +770,13 @@ class CWLWorkflowTest(ToilTest):
581
770
  return expected
582
771
 
583
772
  @staticmethod
584
- def _expected_colon_output(outDir):
773
+ def _expected_colon_output(outDir: str) -> "CWLObjectType":
774
+ path = os.path.join(outDir, "A:Gln2Cys_result")
585
775
  loc = "file://" + os.path.join(outDir, "A%3AGln2Cys_result")
586
776
  return {
587
777
  "result": {
588
778
  "location": loc,
779
+ "path": path,
589
780
  "basename": "A:Gln2Cys_result",
590
781
  "class": "Directory",
591
782
  "listing": [
@@ -597,16 +788,19 @@ class CWLWorkflowTest(ToilTest):
597
788
  "size": 1111,
598
789
  "nameroot": "whale",
599
790
  "nameext": ".txt",
791
+ "path": f"{path}/whale.txt"
600
792
  }
601
793
  ],
602
794
  }
603
795
  }
604
796
 
605
- def _expected_streaming_output(self, outDir):
606
- loc = "file://" + os.path.join(outDir, "output.txt")
797
+ def _expected_streaming_output(self, outDir: str) -> "CWLObjectType":
798
+ path = os.path.join(outDir, "output.txt")
799
+ loc = "file://" + path
607
800
  return {
608
801
  "output": {
609
802
  "location": loc,
803
+ "path": path,
610
804
  "basename": "output.txt",
611
805
  "size": 24,
612
806
  "class": "File",
@@ -616,12 +810,13 @@ class CWLWorkflowTest(ToilTest):
616
810
 
617
811
 
618
812
  @needs_cwl
813
+ @needs_online
619
814
  class CWLv10Test(ToilTest):
620
815
  """
621
816
  Run the CWL 1.0 conformance tests in various environments.
622
817
  """
623
818
 
624
- def setUp(self):
819
+ def setUp(self) -> None:
625
820
  """Runs anew before each test to create farm fresh temp dirs."""
626
821
  self.outDir = f"/tmp/toil-cwl-test-{str(uuid.uuid4())}"
627
822
  os.makedirs(self.outDir)
@@ -642,7 +837,7 @@ class CWLv10Test(ToilTest):
642
837
  shutil.move("common-workflow-language-%s" % testhash, self.cwlSpec)
643
838
  os.remove("spec.zip")
644
839
 
645
- def tearDown(self):
840
+ def tearDown(self) -> None:
646
841
  """Clean up outputs."""
647
842
  if os.path.exists(self.outDir):
648
843
  shutil.rmtree(self.outDir)
@@ -650,121 +845,121 @@ class CWLv10Test(ToilTest):
650
845
 
651
846
  @slow
652
847
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
653
- def test_run_conformance_with_caching(self):
848
+ def test_run_conformance_with_caching(self) -> None:
654
849
  self.test_run_conformance(caching=True)
655
850
 
656
851
  @slow
657
852
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
658
853
  def test_run_conformance(
659
- self, batchSystem=None, caching=False, selected_tests=None
660
- ):
854
+ self,
855
+ batchSystem: Optional[str] = None,
856
+ caching: bool = False,
857
+ selected_tests: Optional[str] = None,
858
+ skipped_tests: Optional[str] = None,
859
+ extra_args: Optional[List[str]] = None,
860
+ ) -> None:
661
861
  run_conformance_tests(
662
862
  workDir=self.workDir,
663
863
  yml="conformance_test_v1.0.yaml",
664
864
  caching=caching,
665
865
  batchSystem=batchSystem,
666
866
  selected_tests=selected_tests,
867
+ skipped_tests=skipped_tests,
868
+ extra_args=extra_args,
667
869
  )
668
870
 
669
871
  @slow
670
872
  @needs_lsf
671
- @unittest.skip
672
- def test_lsf_cwl_conformance(self, **kwargs):
673
- return self.test_run_conformance(batchSystem="lsf", **kwargs)
873
+ @unittest.skip("Not run")
874
+ def test_lsf_cwl_conformance(self, caching: bool = False) -> None:
875
+ self.test_run_conformance(batchSystem="lsf", caching=caching)
674
876
 
675
877
  @slow
676
878
  @needs_slurm
677
- @unittest.skip
678
- def test_slurm_cwl_conformance(self, **kwargs):
679
- return self.test_run_conformance(batchSystem="slurm", **kwargs)
879
+ @unittest.skip("Not run")
880
+ def test_slurm_cwl_conformance(self, caching: bool = False) -> None:
881
+ self.test_run_conformance(batchSystem="slurm", caching=caching)
680
882
 
681
883
  @slow
682
884
  @needs_torque
683
- @unittest.skip
684
- def test_torque_cwl_conformance(self, **kwargs):
685
- return self.test_run_conformance(batchSystem="torque", **kwargs)
885
+ @unittest.skip("Not run")
886
+ def test_torque_cwl_conformance(self, caching: bool = False) -> None:
887
+ self.test_run_conformance(batchSystem="torque", caching=caching)
686
888
 
687
889
  @slow
688
890
  @needs_gridengine
689
- @unittest.skip
690
- def test_gridengine_cwl_conformance(self, **kwargs):
691
- return self.test_run_conformance(batchSystem="grid_engine", **kwargs)
891
+ @unittest.skip("Not run")
892
+ def test_gridengine_cwl_conformance(self, caching: bool = False) -> None:
893
+ self.test_run_conformance(batchSystem="grid_engine", caching=caching)
692
894
 
693
895
  @slow
694
896
  @needs_mesos
695
- @unittest.skip
696
- def test_mesos_cwl_conformance(self, **kwargs):
697
- return self.test_run_conformance(batchSystem="mesos", **kwargs)
698
-
699
- @slow
700
- @needs_parasol
701
- @unittest.skip
702
- def test_parasol_cwl_conformance(self, **kwargs):
703
- return self.test_run_conformance(batchSystem="parasol", **kwargs)
897
+ @unittest.skip("Not run")
898
+ def test_mesos_cwl_conformance(self, caching: bool = False) -> None:
899
+ self.test_run_conformance(batchSystem="mesos", caching=caching)
704
900
 
705
901
  @slow
706
902
  @needs_kubernetes
707
- def test_kubernetes_cwl_conformance(self, **kwargs):
708
- return self.test_run_conformance(
903
+ def test_kubernetes_cwl_conformance(self, caching: bool = False) -> None:
904
+ self.test_run_conformance(
905
+ caching=caching,
709
906
  batchSystem="kubernetes",
710
907
  extra_args=["--retryCount=3"],
711
908
  # This test doesn't work with
712
909
  # Singularity; see
713
910
  # https://github.com/common-workflow-language/cwltool/blob/7094ede917c2d5b16d11f9231fe0c05260b51be6/conformance-test.sh#L99-L117
714
911
  skipped_tests="docker_entrypoint",
715
- **kwargs,
716
912
  )
717
913
 
718
914
  @slow
719
915
  @needs_lsf
720
- @unittest.skip
721
- def test_lsf_cwl_conformance_with_caching(self):
722
- return self.test_lsf_cwl_conformance(caching=True)
916
+ @unittest.skip("Not run")
917
+ def test_lsf_cwl_conformance_with_caching(self) -> None:
918
+ self.test_lsf_cwl_conformance(caching=True)
723
919
 
724
920
  @slow
725
921
  @needs_slurm
726
- @unittest.skip
727
- def test_slurm_cwl_conformance_with_caching(self):
728
- return self.test_slurm_cwl_conformance(caching=True)
922
+ @unittest.skip("Not run")
923
+ def test_slurm_cwl_conformance_with_caching(self) -> None:
924
+ self.test_slurm_cwl_conformance(caching=True)
729
925
 
730
926
  @slow
731
927
  @needs_torque
732
- @unittest.skip
733
- def test_torque_cwl_conformance_with_caching(self):
734
- return self.test_torque_cwl_conformance(caching=True)
928
+ @unittest.skip("Not run")
929
+ def test_torque_cwl_conformance_with_caching(self) -> None:
930
+ self.test_torque_cwl_conformance(caching=True)
735
931
 
736
932
  @slow
737
933
  @needs_gridengine
738
- @unittest.skip
739
- def test_gridengine_cwl_conformance_with_caching(self):
740
- return self.test_gridengine_cwl_conformance(caching=True)
934
+ @unittest.skip("Not run")
935
+ def test_gridengine_cwl_conformance_with_caching(self) -> None:
936
+ self.test_gridengine_cwl_conformance(caching=True)
741
937
 
742
938
  @slow
743
939
  @needs_mesos
744
- @unittest.skip
745
- def test_mesos_cwl_conformance_with_caching(self):
746
- return self.test_mesos_cwl_conformance(caching=True)
747
-
748
- @slow
749
- @needs_parasol
750
- @unittest.skip
751
- def test_parasol_cwl_conformance_with_caching(self):
752
- return self.test_parasol_cwl_conformance(caching=True)
940
+ @unittest.skip("Not run")
941
+ def test_mesos_cwl_conformance_with_caching(self) -> None:
942
+ self.test_mesos_cwl_conformance(caching=True)
753
943
 
754
944
  @slow
755
945
  @needs_kubernetes
756
- def test_kubernetes_cwl_conformance_with_caching(self):
757
- return self.test_kubernetes_cwl_conformance(caching=True)
946
+ def test_kubernetes_cwl_conformance_with_caching(self) -> None:
947
+ self.test_kubernetes_cwl_conformance(caching=True)
758
948
 
759
949
 
760
950
  @needs_cwl
951
+ @needs_online
761
952
  class CWLv11Test(ToilTest):
762
953
  """
763
954
  Run the CWL 1.1 conformance tests in various environments.
764
955
  """
765
956
 
957
+ rootDir: str
958
+ cwlSpec: str
959
+ test_yaml: str
960
+
766
961
  @classmethod
767
- def setUpClass(cls):
962
+ def setUpClass(cls) -> None:
768
963
  """Runs anew before each test."""
769
964
  cls.rootDir = cls._projectRootPath()
770
965
  cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v11")
@@ -778,94 +973,146 @@ class CWLv11Test(ToilTest):
778
973
  )
779
974
  p.communicate()
780
975
 
781
- def tearDown(self):
976
+ def tearDown(self) -> None:
782
977
  """Clean up outputs."""
783
978
  unittest.TestCase.tearDown(self)
784
979
 
785
980
  @slow
786
981
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
787
- def test_run_conformance(self, **kwargs):
788
- run_conformance_tests(workDir=self.cwlSpec, yml=self.test_yaml, **kwargs)
982
+ def test_run_conformance(
983
+ self,
984
+ caching: bool = False,
985
+ batchSystem: Optional[str] = None,
986
+ skipped_tests: Optional[str] = None,
987
+ extra_args: Optional[List[str]] = None,
988
+ ) -> None:
989
+ run_conformance_tests(
990
+ workDir=self.cwlSpec,
991
+ yml=self.test_yaml,
992
+ caching=caching,
993
+ batchSystem=batchSystem,
994
+ skipped_tests=skipped_tests,
995
+ extra_args=extra_args,
996
+ )
789
997
 
790
998
  @slow
791
999
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
792
- def test_run_conformance_with_caching(self):
1000
+ def test_run_conformance_with_caching(self) -> None:
793
1001
  self.test_run_conformance(caching=True)
794
1002
 
795
1003
  @slow
796
1004
  @needs_kubernetes
797
- def test_kubernetes_cwl_conformance(self, **kwargs):
798
- return self.test_run_conformance(
1005
+ def test_kubernetes_cwl_conformance(self, caching: bool = False) -> None:
1006
+ self.test_run_conformance(
799
1007
  batchSystem="kubernetes",
800
1008
  extra_args=["--retryCount=3"],
801
1009
  # These tests don't work with
802
1010
  # Singularity; see
803
1011
  # https://github.com/common-workflow-language/cwltool/blob/7094ede917c2d5b16d11f9231fe0c05260b51be6/conformance-test.sh#L99-L117
804
1012
  skipped_tests="docker_entrypoint,stdin_shorcut",
805
- **kwargs,
1013
+ caching=caching,
806
1014
  )
807
1015
 
808
1016
  @slow
809
1017
  @needs_kubernetes
810
- def test_kubernetes_cwl_conformance_with_caching(self):
811
- return self.test_kubernetes_cwl_conformance(caching=True)
1018
+ def test_kubernetes_cwl_conformance_with_caching(self) -> None:
1019
+ self.test_kubernetes_cwl_conformance(caching=True)
812
1020
 
813
1021
 
814
1022
  @needs_cwl
1023
+ @needs_online
815
1024
  class CWLv12Test(ToilTest):
816
1025
  """
817
1026
  Run the CWL 1.2 conformance tests in various environments.
818
1027
  """
819
1028
 
1029
+ rootDir: str
1030
+ cwlSpec: str
1031
+ test_yaml: str
1032
+
820
1033
  @classmethod
821
- def setUpClass(cls):
1034
+ def setUpClass(cls) -> None:
822
1035
  """Runs anew before each test."""
823
1036
  cls.rootDir = cls._projectRootPath()
824
1037
  cls.cwlSpec = os.path.join(cls.rootDir, "src/toil/test/cwl/spec_v12")
825
1038
  cls.test_yaml = os.path.join(cls.cwlSpec, "conformance_tests.yaml")
826
1039
  # TODO: Use a commit zip in case someone decides to rewrite master's history?
827
1040
  url = "https://github.com/common-workflow-language/cwl-v1.2.git"
828
- commit = "8c3fd9d9f0209a51c5efacb1c7bc02a1164688d6"
1041
+ commit = "0d538a0dbc5518f3c6083ce4571926f65cb84f76"
829
1042
  p = subprocess.Popen(
830
1043
  f"git clone {url} {cls.cwlSpec} && cd {cls.cwlSpec} && git checkout {commit}",
831
1044
  shell=True,
832
1045
  )
833
1046
  p.communicate()
834
1047
 
835
- def tearDown(self):
1048
+ def tearDown(self) -> None:
836
1049
  """Clean up outputs."""
837
1050
  unittest.TestCase.tearDown(self)
838
1051
 
839
1052
  @slow
840
1053
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
841
- def test_run_conformance(self, **kwargs):
842
- run_conformance_tests(workDir=self.cwlSpec, yml=self.test_yaml, **kwargs)
1054
+ def test_run_conformance(
1055
+ self,
1056
+ runner: Optional[str] = None,
1057
+ caching: bool = False,
1058
+ batchSystem: Optional[str] = None,
1059
+ selected_tests: Optional[str] = None,
1060
+ skipped_tests: Optional[str] = None,
1061
+ extra_args: Optional[List[str]] = None,
1062
+ must_support_all_features: bool = False,
1063
+ junit_file: Optional[str] = None,
1064
+ ) -> None:
1065
+ if junit_file is None:
1066
+ junit_file = os.path.join(self.rootDir, "conformance-1.2.junit.xml")
1067
+ run_conformance_tests(
1068
+ workDir=self.cwlSpec,
1069
+ yml=self.test_yaml,
1070
+ runner=runner,
1071
+ caching=caching,
1072
+ batchSystem=batchSystem,
1073
+ selected_tests=selected_tests,
1074
+ skipped_tests=skipped_tests,
1075
+ extra_args=extra_args,
1076
+ must_support_all_features=must_support_all_features,
1077
+ junit_file=junit_file,
1078
+ )
843
1079
 
844
1080
  @slow
845
1081
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
846
- def test_run_conformance_with_caching(self):
847
- self.test_run_conformance(caching=True)
1082
+ def test_run_conformance_with_caching(self) -> None:
1083
+ self.test_run_conformance(
1084
+ caching=True,
1085
+ junit_file = os.path.join(
1086
+ self.rootDir, "caching-conformance-1.2.junit.xml"
1087
+ )
1088
+ )
848
1089
 
849
1090
  @slow
850
1091
  @pytest.mark.timeout(CONFORMANCE_TEST_TIMEOUT)
851
- def test_run_conformance_with_in_place_update(self):
1092
+ def test_run_conformance_with_in_place_update(self) -> None:
852
1093
  """
853
1094
  Make sure that with --bypass-file-store we properly support in place
854
1095
  update on a single node, and that this doesn't break any other
855
1096
  features.
856
1097
  """
857
1098
  self.test_run_conformance(
858
- extra_args=["--bypass-file-store"], must_support_all_features=True
1099
+ extra_args=["--bypass-file-store"], must_support_all_features=True,
1100
+ junit_file = os.path.join(
1101
+ self.rootDir, "in-place-update-conformance-1.2.junit.xml"
1102
+ )
859
1103
  )
860
1104
 
861
1105
  @slow
862
1106
  @needs_kubernetes
863
- def test_kubernetes_cwl_conformance(self, **kwargs):
864
- if "junit_file" not in kwargs:
865
- kwargs["junit_file"] = os.path.join(
866
- self.rootDir, "kubernetes-conformance.junit.xml"
1107
+ def test_kubernetes_cwl_conformance(
1108
+ self, caching: bool = False, junit_file: Optional[str] = None
1109
+ ) -> None:
1110
+ if junit_file is None:
1111
+ junit_file = os.path.join(
1112
+ self.rootDir, "kubernetes-conformance-1.2.junit.xml"
867
1113
  )
868
- return self.test_run_conformance(
1114
+ self.test_run_conformance(
1115
+ caching=caching,
869
1116
  batchSystem="kubernetes",
870
1117
  extra_args=["--retryCount=3"],
871
1118
  # This test doesn't work with
@@ -874,22 +1121,22 @@ class CWLv12Test(ToilTest):
874
1121
  # and
875
1122
  # https://github.com/common-workflow-language/cwltool/issues/1441#issuecomment-826747975
876
1123
  skipped_tests="docker_entrypoint",
877
- **kwargs,
1124
+ junit_file=junit_file,
878
1125
  )
879
1126
 
880
1127
  @slow
881
1128
  @needs_kubernetes
882
- def test_kubernetes_cwl_conformance_with_caching(self):
883
- return self.test_kubernetes_cwl_conformance(
1129
+ def test_kubernetes_cwl_conformance_with_caching(self) -> None:
1130
+ self.test_kubernetes_cwl_conformance(
884
1131
  caching=True,
885
1132
  junit_file=os.path.join(
886
- self.rootDir, "kubernetes-caching-conformance.junit.xml"
1133
+ self.rootDir, "kubernetes-caching-conformance-1.2.junit.xml"
887
1134
  ),
888
1135
  )
889
1136
 
890
1137
  @slow
891
1138
  @needs_wes_server
892
- def test_wes_server_cwl_conformance(self):
1139
+ def test_wes_server_cwl_conformance(self) -> None:
893
1140
  """
894
1141
  Run the CWL conformance tests via WES. TOIL_WES_ENDPOINT must be
895
1142
  specified. If the WES server requires authentication, set TOIL_WES_USER
@@ -914,7 +1161,7 @@ class CWLv12Test(ToilTest):
914
1161
  # 1. `cwltool --print-deps` doesn't seem to include secondary files from the default
915
1162
  # e.g.: https://github.com/common-workflow-language/cwl-v1.2/blob/1.2.1_proposed/tests/mixed-versions/wf-v10.cwl#L4-L10
916
1163
 
917
- return self.test_run_conformance(
1164
+ self.test_run_conformance(
918
1165
  runner="toil-wes-cwl-runner",
919
1166
  selected_tests="1-309,313-337",
920
1167
  extra_args=extra_args,
@@ -929,7 +1176,7 @@ class CWLOnARMTest(AbstractClusterTest):
929
1176
  Run the CWL 1.2 conformance tests on ARM specifically.
930
1177
  """
931
1178
 
932
- def __init__(self, methodName):
1179
+ def __init__(self, methodName: str) -> None:
933
1180
  super().__init__(methodName=methodName)
934
1181
  self.clusterName = "cwl-test-" + str(uuid.uuid4())
935
1182
  self.leaderNodeType = "t4g.2xlarge"
@@ -937,12 +1184,12 @@ class CWLOnARMTest(AbstractClusterTest):
937
1184
  # We need to be running in a directory which Flatcar and the Toil Appliance both have
938
1185
  self.cwl_test_dir = "/tmp/toil/cwlTests"
939
1186
 
940
- def setUp(self):
1187
+ def setUp(self) -> None:
941
1188
  super().setUp()
942
1189
  self.jobStore = f"aws:{self.awsRegion()}:cluster-{uuid.uuid4()}"
943
1190
 
944
1191
  @needs_env_var("CI_COMMIT_SHA", "a git commit sha")
945
- def test_cwl_on_arm(self):
1192
+ def test_cwl_on_arm(self) -> None:
946
1193
  # Make a cluster
947
1194
  self.launchCluster()
948
1195
  # get the leader so we know the IP address - we don't need to wait since create cluster
@@ -991,10 +1238,19 @@ class CWLOnARMTest(AbstractClusterTest):
991
1238
  ]
992
1239
  )
993
1240
 
1241
+ # We know if it succeeds it should save a junit XML for us to read.
1242
+ # Bring it back to be an artifact.
1243
+ self.rsync_util(
1244
+ f":{self.cwl_test_dir}/toil/conformance-1.2.junit.xml",
1245
+ os.path.join(
1246
+ self._projectRootPath(),
1247
+ "arm-conformance-1.2.junit.xml"
1248
+ )
1249
+ )
994
1250
 
995
1251
  @needs_cwl
996
1252
  @pytest.mark.cwl_small_log_dir
997
- def test_workflow_echo_string_scatter_stderr_log_dir(tmp_path: Path):
1253
+ def test_workflow_echo_string_scatter_stderr_log_dir(tmp_path: Path) -> None:
998
1254
  log_dir = tmp_path / "cwl-logs"
999
1255
  job_store = "test_workflow_echo_string_scatter_stderr_log_dir"
1000
1256
  toil = "toil-cwl-runner"
@@ -1101,7 +1357,7 @@ def test_log_dir_echo_stderr(tmp_path: Path) -> None:
1101
1357
 
1102
1358
  @needs_cwl
1103
1359
  @pytest.mark.cwl_small_log_dir
1104
- def test_filename_conflict_resolution(tmp_path: Path):
1360
+ def test_filename_conflict_resolution(tmp_path: Path) -> None:
1105
1361
  out_dir = tmp_path / "cwl-out-dir"
1106
1362
  toil = "toil-cwl-runner"
1107
1363
  options = [
@@ -1124,7 +1380,7 @@ def test_filename_conflict_resolution(tmp_path: Path):
1124
1380
  @needs_cwl
1125
1381
  @needs_docker
1126
1382
  @pytest.mark.cwl_small_log_dir
1127
- def test_filename_conflict_detection(tmp_path: Path):
1383
+ def test_filename_conflict_detection(tmp_path: Path) -> None:
1128
1384
  """
1129
1385
  Make sure we don't just stage files over each other when using a container.
1130
1386
  """
@@ -1146,7 +1402,7 @@ def test_filename_conflict_detection(tmp_path: Path):
1146
1402
  @needs_cwl
1147
1403
  @needs_docker
1148
1404
  @pytest.mark.cwl_small_log_dir
1149
- def test_filename_conflict_detection_at_root(tmp_path: Path):
1405
+ def test_filename_conflict_detection_at_root(tmp_path: Path) -> None:
1150
1406
  """
1151
1407
  Make sure we don't just stage files over each other.
1152
1408
 
@@ -1170,7 +1426,7 @@ def test_filename_conflict_detection_at_root(tmp_path: Path):
1170
1426
 
1171
1427
  @needs_cwl
1172
1428
  @pytest.mark.cwl_small
1173
- def test_pick_value_with_one_null_value(caplog):
1429
+ def test_pick_value_with_one_null_value(caplog: pytest.LogCaptureFixture) -> None:
1174
1430
  """
1175
1431
  Make sure toil-cwl-runner does not false log a warning when pickValue is
1176
1432
  used but outputSource only contains one null value. See: #3991.
@@ -1189,57 +1445,7 @@ def test_pick_value_with_one_null_value(caplog):
1189
1445
 
1190
1446
  @needs_cwl
1191
1447
  @pytest.mark.cwl_small
1192
- def test_usage_message():
1193
- """
1194
- This is purely to ensure a (more) helpful error message is printed if a user does
1195
- not order their positional args correctly [cwl, cwl-job (json/yml/yaml), jobstore].
1196
- """
1197
- toil = "toil-cwl-runner"
1198
- cwl = "test/cwl/revsort.cwl"
1199
- cwl_job_json = "test/cwl/revsort-job.json"
1200
- jobstore = "delete-test-toil"
1201
- random_option_1 = "--logInfo"
1202
- random_option_2 = "--disableChaining"
1203
- cmd_wrong_ordering_1 = [
1204
- toil,
1205
- cwl,
1206
- cwl_job_json,
1207
- jobstore,
1208
- random_option_1,
1209
- random_option_2,
1210
- ]
1211
- cmd_wrong_ordering_2 = [
1212
- toil,
1213
- cwl,
1214
- jobstore,
1215
- random_option_1,
1216
- random_option_2,
1217
- cwl_job_json,
1218
- ]
1219
- cmd_wrong_ordering_3 = [
1220
- toil,
1221
- jobstore,
1222
- random_option_1,
1223
- random_option_2,
1224
- cwl,
1225
- cwl_job_json,
1226
- ]
1227
-
1228
- for cmd in [cmd_wrong_ordering_1, cmd_wrong_ordering_2, cmd_wrong_ordering_3]:
1229
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1230
- stdout, stderr = p.communicate()
1231
- assert (
1232
- b"Usage: toil-cwl-runner [options] example.cwl example-job.yaml" in stderr
1233
- )
1234
- assert (
1235
- b"All positional arguments [cwl, yml_or_json] "
1236
- b"must always be specified last for toil-cwl-runner." in stderr
1237
- )
1238
-
1239
-
1240
- @needs_cwl
1241
- @pytest.mark.cwl_small
1242
- def test_workflow_echo_string():
1448
+ def test_workflow_echo_string() -> None:
1243
1449
  toil = "toil-cwl-runner"
1244
1450
  jobstore = f"--jobStore=file:explicit-local-jobstore-{uuid.uuid4()}"
1245
1451
  option_1 = "--strict-memory-limit"
@@ -1249,14 +1455,18 @@ def test_workflow_echo_string():
1249
1455
  cmd = [toil, jobstore, option_1, option_2, option_3, cwl]
1250
1456
  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1251
1457
  stdout, stderr = p.communicate()
1252
- assert stdout == b"{}", f"Got wrong output: {stdout}\nWith error: {stderr}"
1253
- assert b"Finished toil run successfully" in stderr
1458
+ stdout2 = stdout.decode("utf-8")
1459
+ stderr2 = stderr.decode("utf-8")
1460
+ assert (
1461
+ stdout2.strip() == "{}"
1462
+ ), f"Got wrong output: {stdout2}\nWith error: {stderr2}"
1463
+ assert "Finished toil run successfully" in stderr2
1254
1464
  assert p.returncode == 0
1255
1465
 
1256
1466
 
1257
1467
  @needs_cwl
1258
1468
  @pytest.mark.cwl_small
1259
- def test_workflow_echo_string_scatter_capture_stdout():
1469
+ def test_workflow_echo_string_scatter_capture_stdout() -> None:
1260
1470
  toil = "toil-cwl-runner"
1261
1471
  jobstore = f"--jobStore=file:explicit-local-jobstore-{uuid.uuid4()}"
1262
1472
  option_1 = "--strict-memory-limit"
@@ -1289,7 +1499,7 @@ def test_workflow_echo_string_scatter_capture_stdout():
1289
1499
 
1290
1500
  @needs_cwl
1291
1501
  @pytest.mark.cwl_small
1292
- def test_visit_top_cwl_class():
1502
+ def test_visit_top_cwl_class() -> None:
1293
1503
  structure = {
1294
1504
  "class": "Directory",
1295
1505
  "listing": [
@@ -1315,7 +1525,7 @@ def test_visit_top_cwl_class():
1315
1525
 
1316
1526
  counter = 0
1317
1527
 
1318
- def increment(thing: Dict) -> None:
1528
+ def increment(thing: "CWLObjectType") -> None:
1319
1529
  """
1320
1530
  Make sure we are at something CWL object like, and count it.
1321
1531
  """
@@ -1340,7 +1550,7 @@ def test_visit_top_cwl_class():
1340
1550
 
1341
1551
  @needs_cwl
1342
1552
  @pytest.mark.cwl_small
1343
- def test_visit_cwl_class_and_reduce():
1553
+ def test_visit_cwl_class_and_reduce() -> None:
1344
1554
  structure = {
1345
1555
  "class": "Directory",
1346
1556
  "listing": [
@@ -1366,7 +1576,7 @@ def test_visit_cwl_class_and_reduce():
1366
1576
 
1367
1577
  down_count = 0
1368
1578
 
1369
- def op_down(thing: MutableMapping) -> int:
1579
+ def op_down(thing: "CWLObjectType") -> int:
1370
1580
  """
1371
1581
  Grab the ID of the thing we are at, and count what we visit going
1372
1582
  down.
@@ -1378,7 +1588,7 @@ def test_visit_cwl_class_and_reduce():
1378
1588
  up_count = 0
1379
1589
  up_child_count = 0
1380
1590
 
1381
- def op_up(thing: MutableMapping, down_value: int, child_results: List[str]) -> str:
1591
+ def op_up(thing: "CWLObjectType", down_value: int, child_results: List[str]) -> str:
1382
1592
  """
1383
1593
  Check the down return value and the up return values, and count
1384
1594
  what we visit going up and what child relationships we have.
@@ -1401,7 +1611,7 @@ def test_visit_cwl_class_and_reduce():
1401
1611
 
1402
1612
  @needs_cwl
1403
1613
  @pytest.mark.cwl_small
1404
- def test_download_structure(tmp_path) -> None:
1614
+ def test_download_structure(tmp_path: Path) -> None:
1405
1615
  """
1406
1616
  Make sure that download_structure makes the right calls to what it thinks is the file store.
1407
1617
  """
@@ -1411,7 +1621,7 @@ def test_download_structure(tmp_path) -> None:
1411
1621
  fid2 = FileID("adifferentfile", 1000, True)
1412
1622
 
1413
1623
  # And what directory structure it would be in
1414
- structure = {
1624
+ structure: DirectoryStructure = {
1415
1625
  "dir1": {
1416
1626
  "dir2": {
1417
1627
  "f1": "toilfile:" + fid1.pack(),
@@ -1432,9 +1642,9 @@ def test_download_structure(tmp_path) -> None:
1432
1642
  # These will be populated.
1433
1643
  # TODO: This cache seems unused. Remove it?
1434
1644
  # This maps filesystem path to CWL URI
1435
- index = {}
1645
+ index: Dict[str, str] = {}
1436
1646
  # This maps CWL URI to filesystem path
1437
- existing = {}
1647
+ existing: Dict[str, str] = {}
1438
1648
 
1439
1649
  # Do the download
1440
1650
  download_structure(file_store, index, existing, structure, to_dir)
@@ -1450,11 +1660,16 @@ def test_download_structure(tmp_path) -> None:
1450
1660
  assert os.path.join(to_dir, "dir1/dir2/f1again") in index
1451
1661
  assert os.path.join(to_dir, "anotherfile") in index
1452
1662
  assert (
1453
- index[os.path.join(to_dir, "dir1/dir2/f1")] == structure["dir1"]["dir2"]["f1"]
1663
+ index[os.path.join(to_dir, "dir1/dir2/f1")]
1664
+ == cast(
1665
+ DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
1666
+ )["f1"]
1454
1667
  )
1455
1668
  assert (
1456
1669
  index[os.path.join(to_dir, "dir1/dir2/f1again")]
1457
- == structure["dir1"]["dir2"]["f1again"]
1670
+ == cast(
1671
+ DirectoryStructure, cast(DirectoryStructure, structure["dir1"])["dir2"]
1672
+ )["f1again"]
1458
1673
  )
1459
1674
  assert index[os.path.join(to_dir, "anotherfile")] == structure["anotherfile"]
1460
1675
 
@@ -1476,9 +1691,9 @@ def test_download_structure(tmp_path) -> None:
1476
1691
  # The file store should have been asked to do the download
1477
1692
  file_store.readGlobalFile.assert_has_calls(
1478
1693
  [
1479
- call(fid1, os.path.join(to_dir, "dir1/dir2/f1"), symlink=True),
1480
- call(fid1, os.path.join(to_dir, "dir1/dir2/f1again"), symlink=True),
1481
- call(fid2, os.path.join(to_dir, "anotherfile"), symlink=True),
1694
+ call(fid1, os.path.join(to_dir, "dir1/dir2/f1"), symlink=False),
1695
+ call(fid1, os.path.join(to_dir, "dir1/dir2/f1again"), symlink=False),
1696
+ call(fid2, os.path.join(to_dir, "anotherfile"), symlink=False),
1482
1697
  ],
1483
1698
  any_order=True,
1484
1699
  )