toil 5.12.0__py3-none-any.whl → 6.1.0a1__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 (157) hide show
  1. toil/__init__.py +18 -13
  2. toil/batchSystems/abstractBatchSystem.py +21 -10
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +2 -2
  4. toil/batchSystems/awsBatch.py +14 -14
  5. toil/batchSystems/contained_executor.py +3 -3
  6. toil/batchSystems/htcondor.py +0 -1
  7. toil/batchSystems/kubernetes.py +34 -31
  8. toil/batchSystems/local_support.py +3 -1
  9. toil/batchSystems/mesos/batchSystem.py +7 -7
  10. toil/batchSystems/options.py +32 -83
  11. toil/batchSystems/registry.py +104 -23
  12. toil/batchSystems/singleMachine.py +16 -13
  13. toil/batchSystems/slurm.py +3 -3
  14. toil/batchSystems/torque.py +0 -1
  15. toil/bus.py +6 -8
  16. toil/common.py +532 -743
  17. toil/cwl/__init__.py +28 -32
  18. toil/cwl/cwltoil.py +523 -520
  19. toil/cwl/utils.py +55 -10
  20. toil/fileStores/__init__.py +2 -2
  21. toil/fileStores/abstractFileStore.py +36 -11
  22. toil/fileStores/cachingFileStore.py +607 -530
  23. toil/fileStores/nonCachingFileStore.py +43 -10
  24. toil/job.py +140 -75
  25. toil/jobStores/abstractJobStore.py +147 -79
  26. toil/jobStores/aws/jobStore.py +23 -9
  27. toil/jobStores/aws/utils.py +1 -2
  28. toil/jobStores/fileJobStore.py +117 -19
  29. toil/jobStores/googleJobStore.py +16 -7
  30. toil/jobStores/utils.py +5 -6
  31. toil/leader.py +71 -43
  32. toil/lib/accelerators.py +10 -5
  33. toil/lib/aws/__init__.py +3 -14
  34. toil/lib/aws/ami.py +22 -9
  35. toil/lib/aws/iam.py +21 -13
  36. toil/lib/aws/session.py +2 -16
  37. toil/lib/aws/utils.py +4 -5
  38. toil/lib/compatibility.py +1 -1
  39. toil/lib/conversions.py +7 -3
  40. toil/lib/docker.py +22 -23
  41. toil/lib/ec2.py +10 -6
  42. toil/lib/ec2nodes.py +106 -100
  43. toil/lib/encryption/_nacl.py +2 -1
  44. toil/lib/generatedEC2Lists.py +325 -18
  45. toil/lib/io.py +21 -0
  46. toil/lib/misc.py +1 -1
  47. toil/lib/resources.py +1 -1
  48. toil/lib/threading.py +74 -26
  49. toil/options/common.py +738 -0
  50. toil/options/cwl.py +336 -0
  51. toil/options/wdl.py +32 -0
  52. toil/provisioners/abstractProvisioner.py +1 -4
  53. toil/provisioners/aws/__init__.py +3 -6
  54. toil/provisioners/aws/awsProvisioner.py +6 -0
  55. toil/provisioners/clusterScaler.py +3 -2
  56. toil/provisioners/gceProvisioner.py +2 -2
  57. toil/realtimeLogger.py +2 -1
  58. toil/resource.py +24 -18
  59. toil/server/app.py +2 -3
  60. toil/server/cli/wes_cwl_runner.py +4 -4
  61. toil/server/utils.py +1 -1
  62. toil/server/wes/abstract_backend.py +3 -2
  63. toil/server/wes/amazon_wes_utils.py +5 -4
  64. toil/server/wes/tasks.py +2 -3
  65. toil/server/wes/toil_backend.py +2 -10
  66. toil/server/wsgi_app.py +2 -0
  67. toil/serviceManager.py +12 -10
  68. toil/statsAndLogging.py +5 -1
  69. toil/test/__init__.py +29 -54
  70. toil/test/batchSystems/batchSystemTest.py +11 -111
  71. toil/test/batchSystems/test_slurm.py +3 -2
  72. toil/test/cwl/cwlTest.py +213 -90
  73. toil/test/cwl/glob_dir.cwl +15 -0
  74. toil/test/cwl/preemptible.cwl +21 -0
  75. toil/test/cwl/preemptible_expression.cwl +28 -0
  76. toil/test/cwl/revsort.cwl +1 -1
  77. toil/test/cwl/revsort2.cwl +1 -1
  78. toil/test/docs/scriptsTest.py +0 -1
  79. toil/test/jobStores/jobStoreTest.py +27 -16
  80. toil/test/lib/aws/test_iam.py +4 -14
  81. toil/test/lib/aws/test_utils.py +0 -3
  82. toil/test/lib/dockerTest.py +4 -4
  83. toil/test/lib/test_ec2.py +11 -16
  84. toil/test/mesos/helloWorld.py +4 -5
  85. toil/test/mesos/stress.py +1 -1
  86. toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
  87. toil/test/provisioners/clusterScalerTest.py +6 -4
  88. toil/test/provisioners/clusterTest.py +14 -3
  89. toil/test/provisioners/gceProvisionerTest.py +0 -6
  90. toil/test/provisioners/restartScript.py +3 -2
  91. toil/test/server/serverTest.py +1 -1
  92. toil/test/sort/restart_sort.py +2 -1
  93. toil/test/sort/sort.py +2 -1
  94. toil/test/sort/sortTest.py +2 -13
  95. toil/test/src/autoDeploymentTest.py +45 -45
  96. toil/test/src/busTest.py +5 -5
  97. toil/test/src/checkpointTest.py +2 -2
  98. toil/test/src/deferredFunctionTest.py +1 -1
  99. toil/test/src/fileStoreTest.py +32 -16
  100. toil/test/src/helloWorldTest.py +1 -1
  101. toil/test/src/importExportFileTest.py +1 -1
  102. toil/test/src/jobDescriptionTest.py +2 -1
  103. toil/test/src/jobServiceTest.py +1 -1
  104. toil/test/src/jobTest.py +18 -18
  105. toil/test/src/miscTests.py +5 -3
  106. toil/test/src/promisedRequirementTest.py +3 -3
  107. toil/test/src/realtimeLoggerTest.py +1 -1
  108. toil/test/src/resourceTest.py +2 -2
  109. toil/test/src/restartDAGTest.py +1 -1
  110. toil/test/src/resumabilityTest.py +36 -2
  111. toil/test/src/retainTempDirTest.py +1 -1
  112. toil/test/src/systemTest.py +2 -2
  113. toil/test/src/toilContextManagerTest.py +2 -2
  114. toil/test/src/userDefinedJobArgTypeTest.py +1 -1
  115. toil/test/utils/toilDebugTest.py +98 -32
  116. toil/test/utils/toilKillTest.py +2 -2
  117. toil/test/utils/utilsTest.py +20 -0
  118. toil/test/wdl/wdltoil_test.py +148 -45
  119. toil/toilState.py +7 -6
  120. toil/utils/toilClean.py +1 -1
  121. toil/utils/toilConfig.py +36 -0
  122. toil/utils/toilDebugFile.py +60 -33
  123. toil/utils/toilDebugJob.py +39 -12
  124. toil/utils/toilDestroyCluster.py +1 -1
  125. toil/utils/toilKill.py +1 -1
  126. toil/utils/toilLaunchCluster.py +13 -2
  127. toil/utils/toilMain.py +3 -2
  128. toil/utils/toilRsyncCluster.py +1 -1
  129. toil/utils/toilSshCluster.py +1 -1
  130. toil/utils/toilStats.py +240 -143
  131. toil/utils/toilStatus.py +1 -4
  132. toil/version.py +11 -11
  133. toil/wdl/utils.py +2 -122
  134. toil/wdl/wdltoil.py +999 -386
  135. toil/worker.py +25 -31
  136. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/METADATA +60 -53
  137. toil-6.1.0a1.dist-info/RECORD +237 -0
  138. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/WHEEL +1 -1
  139. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/entry_points.txt +0 -1
  140. toil/batchSystems/parasol.py +0 -379
  141. toil/batchSystems/tes.py +0 -459
  142. toil/test/batchSystems/parasolTestSupport.py +0 -117
  143. toil/test/wdl/builtinTest.py +0 -506
  144. toil/test/wdl/conftest.py +0 -23
  145. toil/test/wdl/toilwdlTest.py +0 -522
  146. toil/wdl/toilwdl.py +0 -141
  147. toil/wdl/versions/dev.py +0 -107
  148. toil/wdl/versions/draft2.py +0 -980
  149. toil/wdl/versions/v1.py +0 -794
  150. toil/wdl/wdl_analysis.py +0 -116
  151. toil/wdl/wdl_functions.py +0 -997
  152. toil/wdl/wdl_synthesis.py +0 -1011
  153. toil/wdl/wdl_types.py +0 -243
  154. toil-5.12.0.dist-info/RECORD +0 -244
  155. /toil/{wdl/versions → options}/__init__.py +0 -0
  156. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/LICENSE +0 -0
  157. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,3 @@
1
- """A set of test cases for toilwdl.py"""
2
1
  # Copyright (C) 2015-2021 Regents of the University of California
3
2
  #
4
3
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,32 +14,33 @@
15
14
  import logging
16
15
  import os
17
16
  import subprocess
18
- from pathlib import Path
17
+ import tempfile
18
+
19
+ import pytest
20
+
21
+ from toil.test import ToilTest
19
22
 
20
23
  from toil.lib.resources import glob
21
24
  from toil.test import slow
22
25
  from toil.version import python
23
26
 
24
- import pytest
25
-
26
27
  logger = logging.getLogger(__name__)
27
28
 
28
29
 
29
- @pytest.fixture
30
- def workflow_debug_jobstore(tmp_path: Path) -> str:
31
- jobStorePath = str(tmp_path / "toilWorkflowRun")
30
+ def workflow_debug_jobstore() -> str:
31
+ job_store_path = os.path.join(tempfile.mkdtemp(), "toilWorkflowRun")
32
32
  subprocess.check_call(
33
33
  [
34
34
  python,
35
35
  os.path.abspath("src/toil/test/utils/ABCWorkflowDebug/debugWorkflow.py"),
36
- jobStorePath,
36
+ job_store_path,
37
37
  ]
38
38
  )
39
- return jobStorePath
39
+ return job_store_path
40
40
 
41
41
 
42
42
  @slow
43
- def testJobStoreContents(workflow_debug_jobstore: str):
43
+ def testJobStoreContents():
44
44
  """
45
45
  Test toilDebugFile.printContentsOfJobStore().
46
46
 
@@ -48,14 +48,13 @@ def testJobStoreContents(workflow_debug_jobstore: str):
48
48
  jobStore. 'A.txt', 'C.txt', 'ABC.txt' are then created. This checks to
49
49
  make sure these contents are found in the jobStore and printed.
50
50
  """
51
- jobStoreDir = workflow_debug_jobstore
52
51
  contents = ["A.txt", "B.txt", "C.txt", "ABC.txt", "mkFile.py"]
53
52
 
54
53
  subprocess.check_call(
55
54
  [
56
55
  python,
57
56
  os.path.abspath("src/toil/utils/toilDebugFile.py"),
58
- jobStoreDir,
57
+ workflow_debug_jobstore(),
59
58
  "--logDebug",
60
59
  "--listFilesInJobStore=True",
61
60
  ]
@@ -78,7 +77,7 @@ def testJobStoreContents(workflow_debug_jobstore: str):
78
77
  os.remove(jobstoreFileContents)
79
78
 
80
79
 
81
- def fetchFiles(symLink, jobStoreDir: str, outputDir):
80
+ def fetchFiles(symLink: bool, jobStoreDir: str, outputDir: str):
82
81
  """
83
82
  Fn for testFetchJobStoreFiles() and testFetchJobStoreFilesWSymlinks().
84
83
 
@@ -99,8 +98,8 @@ def fetchFiles(symLink, jobStoreDir: str, outputDir):
99
98
  "*C.txt",
100
99
  "*ABC.txt",
101
100
  "*mkFile.py",
102
- "--localFilePath=" + outputDir,
103
- "--useSymlinks=" + str(symLink),
101
+ f"--localFilePath={outputDir}",
102
+ f"--useSymlinks={symLink}",
104
103
  ]
105
104
  print(cmd)
106
105
  subprocess.check_call(cmd)
@@ -114,22 +113,89 @@ def fetchFiles(symLink, jobStoreDir: str, outputDir):
114
113
 
115
114
 
116
115
  # expected run time = 4s
117
- def testFetchJobStoreFiles(tmp_path: Path, workflow_debug_jobstore: str) -> None:
118
- """Test toilDebugFile.fetchJobStoreFiles() without using symlinks."""
119
- outputDir = tmp_path / "testoutput"
120
- outputDir.mkdir()
121
- fetchFiles(
122
- symLink=False, jobStoreDir=workflow_debug_jobstore, outputDir=str(outputDir)
123
- )
116
+ def testFetchJobStoreFiles() -> None:
117
+ """Test toilDebugFile.fetchJobStoreFiles() symlinks."""
118
+ job_store_dir = workflow_debug_jobstore()
119
+ output_dir = os.path.join(os.path.dirname(job_store_dir), "testoutput")
120
+ os.makedirs(output_dir, exist_ok=True)
121
+ for symlink in (True, False):
122
+ fetchFiles(symLink=symlink, jobStoreDir=job_store_dir, outputDir=output_dir)
123
+
124
+ class DebugJobTest(ToilTest):
125
+ """
126
+ Test the toil debug-job command.
127
+ """
128
+
129
+ def _get_job_store_and_job_id(self):
130
+ """
131
+ Get a job store and the ID of a failing job within it.
132
+ """
133
+
134
+ # First make a job store.
135
+ job_store = os.path.join(self._createTempDir(), "tree")
136
+
137
+ logger.info("Running workflow that always fails")
138
+ try:
139
+ # Run an always-failign workflow
140
+ subprocess.check_call([
141
+ python,
142
+ os.path.abspath("src/toil/test/docs/scripts/example_alwaysfail.py"),
143
+ "--retryCount=0",
144
+ "--logCritical",
145
+ "--disableProgress=True",
146
+ job_store
147
+ ], stderr=subprocess.DEVNULL)
148
+ raise RuntimeError("Failing workflow succeeded!")
149
+ except subprocess.CalledProcessError:
150
+ # Should fail to run
151
+ logger.info("Task failed successfully")
152
+ pass
153
+
154
+ # Get the job ID.
155
+ # TODO: This assumes a lot about the FileJobStore. Use the MessageBus instead?
156
+ job_id = "kind-explode/" + os.listdir(os.path.join(job_store, "jobs/kind-explode"))[0]
157
+
158
+ return job_store, job_id
159
+
160
+ def test_run_job(self):
161
+ """
162
+ Make sure that we can use toil debug-job to try and run a job in-process.
163
+ """
164
+
165
+ job_store, job_id = self._get_job_store_and_job_id()
166
+
167
+ logger.info("Trying to rerun job %s", job_id)
168
+
169
+ # Rerun the job, which should fail again
170
+ output = subprocess.check_output([
171
+ "toil",
172
+ "debug-job",
173
+ "--logDebug",
174
+ job_store,
175
+ job_id
176
+ ], stderr=subprocess.STDOUT)
177
+ # Even if the job fails, the attempt to run it will succeed.
178
+ log = output.decode('utf-8')
179
+ assert "Boom!" in log, f"Did not find the expected exception message in: {log}"
180
+
181
+
182
+ def test_print_job_info(self):
183
+ """
184
+ Make sure that we can use --printJobInfo to get information on a job from a job store.
185
+ """
186
+
187
+ job_store, job_id = self._get_job_store_and_job_id()
188
+
189
+ logger.info("Trying to print job info for job %s", job_id)
190
+
191
+ # Print the job info and make sure that doesn't crash.
192
+ subprocess.check_call([
193
+ "toil",
194
+ "debug-job",
195
+ "--logDebug",
196
+ job_store,
197
+ "--printJobInfo",
198
+ job_id
199
+ ])
124
200
 
125
201
 
126
- # expected run time = 4s
127
- def testFetchJobStoreFilesWSymlinks(
128
- tmp_path: Path, workflow_debug_jobstore: str
129
- ) -> None:
130
- """Test toilDebugFile.fetchJobStoreFiles() using symlinks."""
131
- outputDir = tmp_path / "testoutput"
132
- outputDir.mkdir()
133
- fetchFiles(
134
- symLink=True, jobStoreDir=workflow_debug_jobstore, outputDir=str(outputDir)
135
- )
@@ -21,7 +21,8 @@ import time
21
21
  import unittest
22
22
 
23
23
  from toil.common import Toil
24
- from toil.jobStores.abstractJobStore import NoSuchFileException, NoSuchJobStoreException
24
+ from toil.jobStores.abstractJobStore import (NoSuchFileException,
25
+ NoSuchJobStoreException)
25
26
  from toil.jobStores.utils import generate_locator
26
27
  from toil.test import ToilTest, needs_aws_s3, needs_cwl
27
28
 
@@ -77,7 +78,6 @@ class ToilKillTest(ToilTest):
77
78
  logger.info('Waiting for kill flag...')
78
79
  except (NoSuchJobStoreException, NoSuchFileException):
79
80
  logger.info('Waiting for job store to be openable...')
80
- pass
81
81
  time.sleep(2)
82
82
 
83
83
  # run toil kill
@@ -115,6 +115,26 @@ class UtilsTest(ToilTest):
115
115
  commandTokens.append('--failIfNotComplete')
116
116
  return commandTokens
117
117
 
118
+ def test_config_functionality(self):
119
+ """Ensure that creating and reading back the config file works"""
120
+ config_file = os.path.abspath("config.yaml")
121
+ config_command = [self.toilMain, 'config', config_file]
122
+ # make sure the command `toil config file_path` works
123
+ try:
124
+ subprocess.check_call(config_command)
125
+ except subprocess.CalledProcessError:
126
+ self.fail("The toil config utility failed!")
127
+
128
+ parser = Job.Runner.getDefaultArgumentParser()
129
+ # make sure that toil can read from the generated config file
130
+ try:
131
+ parser.parse_args(["random_jobstore", "--config", config_file])
132
+ except SystemExit:
133
+ self.fail("Failed to parse the default generated config file!")
134
+ finally:
135
+ os.remove(config_file)
136
+
137
+
118
138
  @needs_rsync3
119
139
  @pytest.mark.timeout(1200)
120
140
  @needs_aws_ec2
@@ -4,20 +4,36 @@ import shutil
4
4
  import subprocess
5
5
  import unittest
6
6
  import uuid
7
- import zipfile
8
- from urllib.request import urlretrieve
7
+ from typing import Any, Dict, List, Optional, Set
8
+ from unittest.mock import patch
9
9
 
10
- import pytest
10
+ from unittest.mock import patch
11
+ from typing import Any, Dict, List, Set
11
12
 
12
- from toil.test import ToilTest, needs_docker, needs_docker_cuda, needs_google_storage, needs_java, needs_singularity_or_docker, slow
13
+ from toil.test import (ToilTest,
14
+ needs_docker_cuda,
15
+ needs_google_storage,
16
+ needs_singularity_or_docker,
17
+ slow)
13
18
  from toil.version import exactPython
14
- # Don't import the test case directly or pytest will test it again.
15
- import toil.test.wdl.toilwdlTest
19
+ from toil.wdl.wdltoil import WDLSectionJob, WDLWorkflowGraph
16
20
 
17
21
 
18
- class ToilConformanceTests(toil.test.wdl.toilwdlTest.BaseToilWdlTest):
22
+ class BaseWDLTest(ToilTest):
23
+ """Base test class for WDL tests."""
24
+ def setUp(self) -> None:
25
+ """Runs anew before each test to create farm fresh temp dirs."""
26
+ self.output_dir = os.path.join('/tmp/', 'toil-wdl-test-' + str(uuid.uuid4()))
27
+ os.makedirs(self.output_dir)
28
+
29
+ def tearDown(self) -> None:
30
+ if os.path.exists(self.output_dir):
31
+ shutil.rmtree(self.output_dir)
32
+
33
+
34
+ class WDLConformanceTests(BaseWDLTest):
19
35
  """
20
- New WDL conformance tests for Toil
36
+ WDL conformance tests for Toil.
21
37
  """
22
38
  wdl_dir = "wdl-conformance-tests"
23
39
  @classmethod
@@ -47,7 +63,7 @@ class ToilConformanceTests(toil.test.wdl.toilwdlTest.BaseToilWdlTest):
47
63
  p = subprocess.run(self.base_command + ["-v", "1.0", "-n", tests_to_run], capture_output=True)
48
64
 
49
65
  if p.returncode != 0:
50
- print(p.stdout)
66
+ print(p.stdout.decode('utf-8', errors='replace'))
51
67
 
52
68
  p.check_returncode()
53
69
 
@@ -58,7 +74,7 @@ class ToilConformanceTests(toil.test.wdl.toilwdlTest.BaseToilWdlTest):
58
74
  p = subprocess.run(self.base_command + ["-v", "1.1", "-n", tests_to_run], capture_output=True)
59
75
 
60
76
  if p.returncode != 0:
61
- print(p.stdout)
77
+ print(p.stdout.decode('utf-8', errors='replace'))
62
78
 
63
79
  p.check_returncode()
64
80
 
@@ -69,26 +85,23 @@ class ToilConformanceTests(toil.test.wdl.toilwdlTest.BaseToilWdlTest):
69
85
  shutil.rmtree("wdl-conformance-tests")
70
86
 
71
87
 
72
- class WdlToilTest(toil.test.wdl.toilwdlTest.ToilWdlTest):
73
- """
74
- Version of the old Toil WDL tests that tests the new MiniWDL-based implementation.
75
- """
76
-
88
+ class WDLTests(BaseWDLTest):
89
+ """Tests for Toil's MiniWDL-based implementation."""
77
90
  @classmethod
78
91
  def setUpClass(cls) -> None:
79
92
  """Runs once for all tests."""
80
93
  cls.base_command = [exactPython, '-m', 'toil.wdl.wdltoil']
81
94
 
82
- # We inherit a testMD5sum but it is going to need Singularity and not
83
- # Docker now. And also needs to have a WDL 1.0+ WDL file. So we replace it.
95
+ # We inherit a testMD5sum but it is going to need Singularity or Docker
96
+ # now. And also needs to have a WDL 1.0+ WDL file. So we replace it.
84
97
  @needs_singularity_or_docker
85
- def testMD5sum(self):
98
+ def test_MD5sum(self):
86
99
  """Test if Toil produces the same outputs as known good outputs for WDL's
87
100
  GATK tutorial #1."""
88
101
  wdl = os.path.abspath('src/toil/test/wdl/md5sum/md5sum.1.0.wdl')
89
102
  json_file = os.path.abspath('src/toil/test/wdl/md5sum/md5sum.json')
90
103
 
91
- result_json = subprocess.check_output(self.base_command + [wdl, json_file, '-o', self.output_dir, '--logDebug'])
104
+ result_json = subprocess.check_output(self.base_command + [wdl, json_file, '-o', self.output_dir, '--logDebug', '--retryCount=0'])
92
105
  result = json.loads(result_json)
93
106
 
94
107
  assert 'ga4ghMd5.value' in result
@@ -96,25 +109,13 @@ class WdlToilTest(toil.test.wdl.toilwdlTest.ToilWdlTest):
96
109
  assert os.path.exists(result['ga4ghMd5.value'])
97
110
  assert os.path.basename(result['ga4ghMd5.value']) == 'md5sum.txt'
98
111
 
99
- def test_empty_file_path(self):
100
- """Test if empty File type inputs are protected against"""
101
- wdl = os.path.abspath('src/toil/test/wdl/md5sum/md5sum.1.0.wdl')
102
- json_file = os.path.abspath('src/toil/test/wdl/md5sum/empty_file.json')
103
-
104
- p = subprocess.Popen(self.base_command + [wdl, json_file, '-o', self.output_dir, '--logDebug'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
105
- stdout, stderr = p.communicate()
106
- retval = p.wait()
107
-
108
- assert retval != 0
109
- assert b'Could not find' in stderr
110
-
111
112
  @needs_singularity_or_docker
112
- def test_miniwdl_self_test(self):
113
+ def test_miniwdl_self_test(self, extra_args: Optional[List[str]] = None) -> None:
113
114
  """Test if the MiniWDL self test runs and produces the expected output."""
114
115
  wdl_file = os.path.abspath('src/toil/test/wdl/miniwdl_self_test/self_test.wdl')
115
116
  json_file = os.path.abspath('src/toil/test/wdl/miniwdl_self_test/inputs.json')
116
117
 
117
- result_json = subprocess.check_output(self.base_command + [wdl_file, json_file, '-o', self.output_dir, '--outputDialect', 'miniwdl'])
118
+ result_json = subprocess.check_output(self.base_command + [wdl_file, json_file, '--logDebug', '-o', self.output_dir, '--outputDialect', 'miniwdl'] + (extra_args or []))
118
119
  result = json.loads(result_json)
119
120
 
120
121
  # Expect MiniWDL-style output with a designated "dir"
@@ -138,10 +139,17 @@ class WdlToilTest(toil.test.wdl.toilwdlTest.ToilWdlTest):
138
139
  assert 'hello_caller.messages' in outputs
139
140
  assert outputs['hello_caller.messages'] == ["Hello, Alyssa P. Hacker!", "Hello, Ben Bitdiddle!"]
140
141
 
142
+ @needs_singularity_or_docker
143
+ def test_miniwdl_self_test_by_reference(self) -> None:
144
+ """
145
+ Test if the MiniWDL self test works when passing input files by URL reference.
146
+ """
147
+ self.test_miniwdl_self_test(extra_args=["--referenceInputs=True"])
148
+
141
149
  @slow
142
150
  @needs_docker_cuda
143
151
  def test_giraffe_deepvariant(self):
144
- """Test if Giraffe and CPU DeepVariant run. This could take 25 minutes."""
152
+ """Test if Giraffe and GPU DeepVariant run. This could take 25 minutes."""
145
153
  # TODO: enable test if nvidia-container-runtime and Singularity are installed but Docker isn't.
146
154
 
147
155
  json_dir = self._createTempDir()
@@ -184,7 +192,7 @@ class WdlToilTest(toil.test.wdl.toilwdlTest.ToilWdlTest):
184
192
  @slow
185
193
  @needs_singularity_or_docker
186
194
  def test_giraffe(self):
187
- """Test if Giraffe runs. This could take 12 minutes. Also we scale it down."""
195
+ """Test if Giraffe runs. This could take 12 minutes. Also we scale it down but it still demands lots of memory."""
188
196
  # TODO: enable test if nvidia-container-runtime and Singularity are installed but Docker isn't.
189
197
 
190
198
  json_dir = self._createTempDir()
@@ -224,17 +232,112 @@ class WdlToilTest(toil.test.wdl.toilwdlTest.ToilWdlTest):
224
232
  assert os.path.exists(result['ga4ghMd5.value'])
225
233
  assert os.path.basename(result['ga4ghMd5.value']) == 'md5sum.txt'
226
234
 
227
- def test_empty_file_path(self):
228
- """Test if empty File type inputs are protected against"""
229
- wdl = os.path.abspath('src/toil/test/wdl/md5sum/md5sum.1.0.wdl')
230
- json_file = os.path.abspath('src/toil/test/wdl/md5sum/empty_file.json')
231
-
232
- p = subprocess.Popen(self.base_command + [wdl, json_file, '-o', self.output_dir, '--logDebug'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
233
- stdout, stderr = p.communicate()
234
- retval = p.wait()
235
+ def test_coalesce(self):
236
+ """
237
+ Test if WDLSectionJob can coalesce WDL decls.
238
+
239
+ White box test; will need to be changed or removed if the WDL interpreter changes.
240
+ """
241
+
242
+ # Set up data structures for our fake workflow graph to pull from.
243
+ # This has all decl-type nodes
244
+ all_decls: Set[str] = set()
245
+ # And this has all transitive dependencies for all nodes.
246
+ all_deps: Dict[str, Set[str]] = {}
247
+
248
+ def mock_is_decl(self: Any, node_id: str) -> bool:
249
+ """
250
+ Replacement function to determine if a node is a decl or not.
251
+ """
252
+ return node_id in all_decls
253
+
254
+ def mock_get_transitive_dependencies(self: Any, node_id: str) -> Set[str]:
255
+ """
256
+ Replacement function to get all the transitive dependencies of a node.
257
+ """
258
+ return all_deps[node_id]
259
+
260
+ # These are the only two methods coalesce_nodes calls, so we can
261
+ # replace them to ensure our graph is used without actually needing to
262
+ # make any WDL objects for it.
263
+ #
264
+ # If that changes, the test will need to change! Maybe then it will be
265
+ # worth extracting a base type for this interface.
266
+ with patch.object(WDLWorkflowGraph, 'is_decl', mock_is_decl):
267
+ with patch.object(WDLWorkflowGraph, 'get_transitive_dependencies', mock_get_transitive_dependencies):
268
+
269
+ with self.subTest(msg="Two unrelated decls can coalesce"):
270
+ # Set up two unrelated decls
271
+ all_decls = {"decl1", "decl2"}
272
+ all_deps = {
273
+ "decl1": set(),
274
+ "decl2": set()
275
+ }
276
+
277
+ result = WDLSectionJob.coalesce_nodes(["decl1", "decl2"], WDLWorkflowGraph([]))
278
+
279
+ # Make sure they coalesced
280
+ assert len(result) == 1
281
+ assert "decl1" in result[0]
282
+ assert "decl2" in result[0]
283
+
284
+ with self.subTest(msg="A decl will not coalesce with a non-decl"):
285
+ all_decls = {"decl"}
286
+ all_deps = {
287
+ "decl": set(),
288
+ "nondecl": set()
289
+ }
290
+
291
+ result = WDLSectionJob.coalesce_nodes(["decl", "nondecl"], WDLWorkflowGraph([]))
292
+
293
+ assert len(result) == 2
294
+ assert len(result[0]) == 1
295
+ assert len(result[1]) == 1
296
+
297
+
298
+ with self.subTest(msg="Two adjacent decls with a common dependency can coalesce"):
299
+ all_decls = {"decl1", "decl2"}
300
+ all_deps = {
301
+ "decl1": {"base"},
302
+ "decl2": {"base"},
303
+ "base": set()
304
+ }
305
+
306
+ result = WDLSectionJob.coalesce_nodes(["base", "decl1", "decl2"], WDLWorkflowGraph([]))
307
+
308
+ assert len(result) == 2
309
+ assert "base" in result[0]
310
+ assert "decl1" in result[1]
311
+ assert "decl2" in result[1]
312
+
313
+ with self.subTest(msg="Two adjacent decls with different dependencies will not coalesce"):
314
+ all_decls = {"decl1", "decl2"}
315
+ all_deps = {
316
+ "decl1": {"base"},
317
+ "decl2": set(),
318
+ "base": set()
319
+ }
320
+
321
+ result = WDLSectionJob.coalesce_nodes(["base", "decl1", "decl2"], WDLWorkflowGraph([]))
322
+
323
+ assert len(result) == 3
324
+ assert "base" in result[0]
325
+
326
+ with self.subTest(msg="Two adjacent decls with different successors will coalesce"):
327
+ all_decls = {"decl1", "decl2"}
328
+ all_deps = {
329
+ "decl1": set(),
330
+ "decl2": set(),
331
+ "successor": {"decl2"}
332
+ }
333
+
334
+ result = WDLSectionJob.coalesce_nodes(["decl1", "decl2", "successor"], WDLWorkflowGraph([]))
335
+
336
+ assert len(result) == 2
337
+ assert "decl1" in result[0]
338
+ assert "decl2" in result[0]
339
+ assert "successor" in result[1]
235
340
 
236
- assert retval != 0
237
- assert b'Could not find' in stderr
238
341
 
239
342
  if __name__ == "__main__":
240
343
  unittest.main() # run all tests
toil/toilState.py CHANGED
@@ -183,6 +183,7 @@ class ToilState:
183
183
  if job_id in self.__job_database:
184
184
  # Update the one true copy in place
185
185
  old_truth = self.__job_database[job_id]
186
+ old_truth.check_new_version(new_truth)
186
187
  old_truth.__dict__.update(new_truth.__dict__)
187
188
  else:
188
189
  # Just keep the new one
@@ -293,7 +294,8 @@ class ToilState:
293
294
  successor.predecessorsFinished.add(jobDesc.jobStoreID)
294
295
 
295
296
  # If the successor has no predecessors to finish
296
- assert len(successor.predecessorsFinished) <= successor.predecessorNumber
297
+ if len(successor.predecessorsFinished) > successor.predecessorNumber:
298
+ raise RuntimeError("There are more finished predecessors than possible.")
297
299
  if len(successor.predecessorsFinished) == successor.predecessorNumber:
298
300
 
299
301
  # It is ready to be run, so remove it from the set of waiting jobs
@@ -322,7 +324,8 @@ class ToilState:
322
324
 
323
325
  # We put the successor job in the set of waiting successor
324
326
  # jobs with multiple predecessors
325
- assert successorJobStoreID not in self.jobsToBeScheduledWithMultiplePredecessors
327
+ if successorJobStoreID in self.jobsToBeScheduledWithMultiplePredecessors:
328
+ raise RuntimeError("Failed to schedule the successor job. The successor job is already scheduled.")
326
329
  self.jobsToBeScheduledWithMultiplePredecessors.add(successorJobStoreID)
327
330
 
328
331
  # Process successor
@@ -337,10 +340,8 @@ class ToilState:
337
340
  # We've already seen the successor
338
341
 
339
342
  # Add the job as a predecessor
340
- assert (
341
- jobDesc.jobStoreID
342
- not in self.successor_to_predecessors[successorJobStoreID]
343
- )
343
+ if jobDesc.jobStoreID in self.successor_to_predecessors[successorJobStoreID]:
344
+ raise RuntimeError("Failed to add the job as a predecessor. The job is already added as a predecessor.")
344
345
  self.successor_to_predecessors[successorJobStoreID].add(
345
346
  str(jobDesc.jobStoreID)
346
347
  )
toil/utils/toilClean.py CHANGED
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
22
22
 
23
23
 
24
24
  def main() -> None:
25
- parser = parser_with_common_options(jobstore_option=True)
25
+ parser = parser_with_common_options(jobstore_option=True, prog="toil clean")
26
26
 
27
27
  options = parser.parse_args()
28
28
  set_logging_from_options(options)
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2015-2021 Regents of the University of California
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Create a config file with all default Toil options."""
15
+ import logging
16
+ import os
17
+
18
+ from configargparse import ArgParser
19
+
20
+ from toil.common import generate_config
21
+ from toil.statsAndLogging import add_logging_options, set_logging_from_options
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ def main() -> None:
27
+ parser = ArgParser()
28
+
29
+ parser.add_argument("output", default="config.yaml", help="Filepath to write the config file too. Default=%("
30
+ "default)s")
31
+ add_logging_options(parser)
32
+ options = parser.parse_args()
33
+ set_logging_from_options(options)
34
+ logger.debug("Attempting to write a default config file to %s.", os.path.abspath(options.output))
35
+ generate_config(os.path.abspath(options.output))
36
+ logger.info("Successfully wrote a default config file to %s.", os.path.abspath(options.output))