toil 6.1.0a1__py3-none-any.whl → 8.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toil/__init__.py +122 -315
- toil/batchSystems/__init__.py +1 -0
- toil/batchSystems/abstractBatchSystem.py +173 -89
- toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
- toil/batchSystems/awsBatch.py +244 -135
- toil/batchSystems/cleanup_support.py +26 -16
- toil/batchSystems/contained_executor.py +31 -28
- toil/batchSystems/gridengine.py +86 -50
- toil/batchSystems/htcondor.py +166 -89
- toil/batchSystems/kubernetes.py +632 -382
- toil/batchSystems/local_support.py +20 -15
- toil/batchSystems/lsf.py +134 -81
- toil/batchSystems/lsfHelper.py +13 -11
- toil/batchSystems/mesos/__init__.py +41 -29
- toil/batchSystems/mesos/batchSystem.py +290 -151
- toil/batchSystems/mesos/executor.py +79 -50
- toil/batchSystems/mesos/test/__init__.py +31 -23
- toil/batchSystems/options.py +46 -28
- toil/batchSystems/registry.py +53 -19
- toil/batchSystems/singleMachine.py +296 -125
- toil/batchSystems/slurm.py +603 -138
- toil/batchSystems/torque.py +47 -33
- toil/bus.py +186 -76
- toil/common.py +664 -368
- toil/cwl/__init__.py +1 -1
- toil/cwl/cwltoil.py +1136 -483
- toil/cwl/utils.py +17 -22
- toil/deferred.py +63 -42
- toil/exceptions.py +5 -3
- toil/fileStores/__init__.py +5 -5
- toil/fileStores/abstractFileStore.py +140 -60
- toil/fileStores/cachingFileStore.py +717 -269
- toil/fileStores/nonCachingFileStore.py +116 -87
- toil/job.py +1225 -368
- toil/jobStores/abstractJobStore.py +416 -266
- toil/jobStores/aws/jobStore.py +863 -477
- toil/jobStores/aws/utils.py +201 -120
- toil/jobStores/conftest.py +3 -2
- toil/jobStores/fileJobStore.py +292 -154
- toil/jobStores/googleJobStore.py +140 -74
- toil/jobStores/utils.py +36 -15
- toil/leader.py +668 -272
- toil/lib/accelerators.py +115 -18
- toil/lib/aws/__init__.py +74 -31
- toil/lib/aws/ami.py +122 -87
- toil/lib/aws/iam.py +284 -108
- toil/lib/aws/s3.py +31 -0
- toil/lib/aws/session.py +214 -39
- toil/lib/aws/utils.py +287 -231
- toil/lib/bioio.py +13 -5
- toil/lib/compatibility.py +11 -6
- toil/lib/conversions.py +104 -47
- toil/lib/docker.py +131 -103
- toil/lib/ec2.py +361 -199
- toil/lib/ec2nodes.py +174 -106
- toil/lib/encryption/_dummy.py +5 -3
- toil/lib/encryption/_nacl.py +10 -6
- toil/lib/encryption/conftest.py +1 -0
- toil/lib/exceptions.py +26 -7
- toil/lib/expando.py +5 -3
- toil/lib/ftp_utils.py +217 -0
- toil/lib/generatedEC2Lists.py +127 -19
- toil/lib/humanize.py +6 -2
- toil/lib/integration.py +341 -0
- toil/lib/io.py +141 -15
- toil/lib/iterables.py +4 -2
- toil/lib/memoize.py +12 -8
- toil/lib/misc.py +66 -21
- toil/lib/objects.py +2 -2
- toil/lib/resources.py +68 -15
- toil/lib/retry.py +126 -81
- toil/lib/threading.py +299 -82
- toil/lib/throttle.py +16 -15
- toil/options/common.py +843 -409
- toil/options/cwl.py +175 -90
- toil/options/runner.py +50 -0
- toil/options/wdl.py +73 -17
- toil/provisioners/__init__.py +117 -46
- toil/provisioners/abstractProvisioner.py +332 -157
- toil/provisioners/aws/__init__.py +70 -33
- toil/provisioners/aws/awsProvisioner.py +1145 -715
- toil/provisioners/clusterScaler.py +541 -279
- toil/provisioners/gceProvisioner.py +282 -179
- toil/provisioners/node.py +155 -79
- toil/realtimeLogger.py +34 -22
- toil/resource.py +137 -75
- toil/server/app.py +128 -62
- toil/server/celery_app.py +3 -1
- toil/server/cli/wes_cwl_runner.py +82 -53
- toil/server/utils.py +54 -28
- toil/server/wes/abstract_backend.py +64 -26
- toil/server/wes/amazon_wes_utils.py +21 -15
- toil/server/wes/tasks.py +121 -63
- toil/server/wes/toil_backend.py +142 -107
- toil/server/wsgi_app.py +4 -3
- toil/serviceManager.py +58 -22
- toil/statsAndLogging.py +224 -70
- toil/test/__init__.py +282 -183
- toil/test/batchSystems/batchSystemTest.py +460 -210
- toil/test/batchSystems/batch_system_plugin_test.py +90 -0
- toil/test/batchSystems/test_gridengine.py +173 -0
- toil/test/batchSystems/test_lsf_helper.py +67 -58
- toil/test/batchSystems/test_slurm.py +110 -49
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +56 -0
- toil/test/cwl/cwlTest.py +496 -287
- toil/test/cwl/measure_default_memory.cwl +12 -0
- toil/test/cwl/not_run_required_input.cwl +29 -0
- toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
- toil/test/cwl/seqtk_seq.cwl +1 -1
- toil/test/docs/scriptsTest.py +69 -46
- toil/test/jobStores/jobStoreTest.py +427 -264
- toil/test/lib/aws/test_iam.py +118 -50
- toil/test/lib/aws/test_s3.py +16 -9
- toil/test/lib/aws/test_utils.py +5 -6
- toil/test/lib/dockerTest.py +118 -141
- toil/test/lib/test_conversions.py +113 -115
- toil/test/lib/test_ec2.py +58 -50
- toil/test/lib/test_integration.py +104 -0
- toil/test/lib/test_misc.py +12 -5
- toil/test/mesos/MesosDataStructuresTest.py +23 -10
- toil/test/mesos/helloWorld.py +7 -6
- toil/test/mesos/stress.py +25 -20
- toil/test/options/__init__.py +13 -0
- toil/test/options/options.py +42 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
- toil/test/provisioners/clusterScalerTest.py +440 -250
- toil/test/provisioners/clusterTest.py +166 -44
- toil/test/provisioners/gceProvisionerTest.py +174 -100
- toil/test/provisioners/provisionerTest.py +25 -13
- toil/test/provisioners/restartScript.py +5 -4
- toil/test/server/serverTest.py +188 -141
- toil/test/sort/restart_sort.py +137 -68
- toil/test/sort/sort.py +134 -66
- toil/test/sort/sortTest.py +91 -49
- toil/test/src/autoDeploymentTest.py +141 -101
- toil/test/src/busTest.py +20 -18
- toil/test/src/checkpointTest.py +8 -2
- toil/test/src/deferredFunctionTest.py +49 -35
- toil/test/src/dockerCheckTest.py +32 -24
- toil/test/src/environmentTest.py +135 -0
- toil/test/src/fileStoreTest.py +539 -272
- toil/test/src/helloWorldTest.py +7 -4
- toil/test/src/importExportFileTest.py +61 -31
- toil/test/src/jobDescriptionTest.py +46 -21
- toil/test/src/jobEncapsulationTest.py +2 -0
- toil/test/src/jobFileStoreTest.py +74 -50
- toil/test/src/jobServiceTest.py +187 -73
- toil/test/src/jobTest.py +121 -71
- toil/test/src/miscTests.py +19 -18
- toil/test/src/promisedRequirementTest.py +82 -36
- toil/test/src/promisesTest.py +7 -6
- toil/test/src/realtimeLoggerTest.py +10 -6
- toil/test/src/regularLogTest.py +71 -37
- toil/test/src/resourceTest.py +80 -49
- toil/test/src/restartDAGTest.py +36 -22
- toil/test/src/resumabilityTest.py +9 -2
- toil/test/src/retainTempDirTest.py +45 -14
- toil/test/src/systemTest.py +12 -8
- toil/test/src/threadingTest.py +44 -25
- toil/test/src/toilContextManagerTest.py +10 -7
- toil/test/src/userDefinedJobArgTypeTest.py +8 -5
- toil/test/src/workerTest.py +73 -23
- toil/test/utils/toilDebugTest.py +103 -33
- toil/test/utils/toilKillTest.py +4 -5
- toil/test/utils/utilsTest.py +245 -106
- toil/test/wdl/wdltoil_test.py +818 -149
- toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
- toil/toilState.py +120 -35
- toil/utils/toilConfig.py +13 -4
- toil/utils/toilDebugFile.py +44 -27
- toil/utils/toilDebugJob.py +214 -27
- toil/utils/toilDestroyCluster.py +11 -6
- toil/utils/toilKill.py +8 -3
- toil/utils/toilLaunchCluster.py +256 -140
- toil/utils/toilMain.py +37 -16
- toil/utils/toilRsyncCluster.py +32 -14
- toil/utils/toilSshCluster.py +49 -22
- toil/utils/toilStats.py +356 -273
- toil/utils/toilStatus.py +292 -139
- toil/utils/toilUpdateEC2Instances.py +3 -1
- toil/version.py +12 -12
- toil/wdl/utils.py +5 -5
- toil/wdl/wdltoil.py +3913 -1033
- toil/worker.py +367 -184
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
- toil-8.0.0.dist-info/METADATA +173 -0
- toil-8.0.0.dist-info/RECORD +253 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
- toil-6.1.0a1.dist-info/METADATA +0 -125
- toil-6.1.0a1.dist-info/RECORD +0 -237
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/test/src/resourceTest.py
CHANGED
|
@@ -30,7 +30,7 @@ from toil.version import exactPython
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@contextmanager
|
|
33
|
-
def tempFileContaining(content, suffix=
|
|
33
|
+
def tempFileContaining(content, suffix=""):
|
|
34
34
|
"""
|
|
35
35
|
Write a file with the given contents, and keep it on disk as long as the context is active.
|
|
36
36
|
:param str content: The contents of the file.
|
|
@@ -38,7 +38,7 @@ def tempFileContaining(content, suffix=''):
|
|
|
38
38
|
"""
|
|
39
39
|
fd, path = tempfile.mkstemp(suffix=suffix)
|
|
40
40
|
try:
|
|
41
|
-
encoded = content.encode(
|
|
41
|
+
encoded = content.encode("utf-8")
|
|
42
42
|
assert os.write(fd, encoded) == len(encoded)
|
|
43
43
|
except:
|
|
44
44
|
os.close(fd)
|
|
@@ -52,41 +52,55 @@ def tempFileContaining(content, suffix=''):
|
|
|
52
52
|
|
|
53
53
|
class ResourceTest(ToilTest):
|
|
54
54
|
"""Test module descriptors and resources derived from them."""
|
|
55
|
+
|
|
55
56
|
def testStandAlone(self):
|
|
56
|
-
self._testExternal(
|
|
57
|
+
self._testExternal(
|
|
58
|
+
moduleName="userScript", pyFiles=("userScript.py", "helper.py")
|
|
59
|
+
)
|
|
57
60
|
|
|
58
61
|
def testPackage(self):
|
|
59
|
-
self._testExternal(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
self._testExternal(
|
|
63
|
+
moduleName="foo.userScript",
|
|
64
|
+
pyFiles=(
|
|
65
|
+
"foo/__init__.py",
|
|
66
|
+
"foo/userScript.py",
|
|
67
|
+
"foo/bar/__init__.py",
|
|
68
|
+
"foo/bar/helper.py",
|
|
69
|
+
),
|
|
70
|
+
)
|
|
63
71
|
|
|
64
72
|
def testVirtualEnv(self):
|
|
65
|
-
self._testExternal(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
self._testExternal(
|
|
74
|
+
moduleName="foo.userScript",
|
|
75
|
+
virtualenv=True,
|
|
76
|
+
pyFiles=(
|
|
77
|
+
"foo/__init__.py",
|
|
78
|
+
"foo/userScript.py",
|
|
79
|
+
"foo/bar/__init__.py",
|
|
80
|
+
"foo/bar/helper.py",
|
|
81
|
+
"de/pen/dency.py",
|
|
82
|
+
"de/__init__.py",
|
|
83
|
+
"de/pen/__init__.py",
|
|
84
|
+
),
|
|
85
|
+
)
|
|
74
86
|
|
|
75
87
|
def testStandAloneInPackage(self):
|
|
76
|
-
self.assertRaises(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
self.assertRaises(
|
|
89
|
+
ResourceException,
|
|
90
|
+
self._testExternal,
|
|
91
|
+
moduleName="userScript",
|
|
92
|
+
pyFiles=("__init__.py", "userScript.py", "helper.py"),
|
|
93
|
+
)
|
|
80
94
|
|
|
81
95
|
def _testExternal(self, moduleName, pyFiles, virtualenv=False):
|
|
82
96
|
dirPath = self._createTempDir()
|
|
83
97
|
if virtualenv:
|
|
84
98
|
self.assertTrue(inVirtualEnv())
|
|
85
99
|
# --never-download prevents silent upgrades to pip, wheel and setuptools
|
|
86
|
-
subprocess.check_call(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
subprocess.check_call(
|
|
101
|
+
["virtualenv", "--never-download", "--python", exactPython, dirPath]
|
|
102
|
+
)
|
|
103
|
+
sitePackages = os.path.join(dirPath, "lib", exactPython, "site-packages")
|
|
90
104
|
# tuple assignment is necessary to make this line immediately precede the try:
|
|
91
105
|
oldPrefix, sys.prefix, dirPath = sys.prefix, dirPath, sitePackages
|
|
92
106
|
else:
|
|
@@ -95,21 +109,23 @@ class ResourceTest(ToilTest):
|
|
|
95
109
|
for relPath in pyFiles:
|
|
96
110
|
path = os.path.join(dirPath, relPath)
|
|
97
111
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
98
|
-
with open(path,
|
|
99
|
-
f.write(
|
|
112
|
+
with open(path, "w") as f:
|
|
113
|
+
f.write("pass\n")
|
|
100
114
|
sys.path.append(dirPath)
|
|
101
115
|
try:
|
|
102
116
|
userScript = importlib.import_module(moduleName)
|
|
103
117
|
try:
|
|
104
|
-
self._test(
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
self._test(
|
|
119
|
+
userScript.__name__,
|
|
120
|
+
expectedContents=pyFiles,
|
|
121
|
+
allowExtraContents=True,
|
|
122
|
+
)
|
|
107
123
|
finally:
|
|
108
124
|
del userScript
|
|
109
125
|
while moduleName:
|
|
110
126
|
del sys.modules[moduleName]
|
|
111
127
|
self.assertFalse(moduleName in sys.modules)
|
|
112
|
-
moduleName =
|
|
128
|
+
moduleName = ".".join(moduleName.split(".")[:-1])
|
|
113
129
|
|
|
114
130
|
finally:
|
|
115
131
|
sys.path.remove(dirPath)
|
|
@@ -120,17 +136,22 @@ class ResourceTest(ToilTest):
|
|
|
120
136
|
def testBuiltIn(self):
|
|
121
137
|
# Create a ModuleDescriptor for the module containing ModuleDescriptor, i.e. toil.resource
|
|
122
138
|
module_name = ModuleDescriptor.__module__
|
|
123
|
-
self.assertEqual(module_name,
|
|
139
|
+
self.assertEqual(module_name, "toil.resource")
|
|
124
140
|
self._test(module_name, shouldBelongToToil=True)
|
|
125
141
|
|
|
126
|
-
def _test(
|
|
127
|
-
|
|
142
|
+
def _test(
|
|
143
|
+
self,
|
|
144
|
+
module_name,
|
|
145
|
+
shouldBelongToToil=False,
|
|
146
|
+
expectedContents=None,
|
|
147
|
+
allowExtraContents=True,
|
|
148
|
+
):
|
|
128
149
|
module = ModuleDescriptor.forModule(module_name)
|
|
129
150
|
# Assert basic attributes and properties
|
|
130
151
|
self.assertEqual(module.belongsToToil, shouldBelongToToil)
|
|
131
152
|
self.assertEqual(module.name, module_name)
|
|
132
153
|
if shouldBelongToToil:
|
|
133
|
-
self.assertTrue(module.dirPath.endswith(
|
|
154
|
+
self.assertTrue(module.dirPath.endswith("/src"))
|
|
134
155
|
|
|
135
156
|
# Before the module is saved as a resource, localize() and globalize() are identity
|
|
136
157
|
# methods. This should log.warnings.
|
|
@@ -139,27 +160,32 @@ class ResourceTest(ToilTest):
|
|
|
139
160
|
# Create a mock job store ...
|
|
140
161
|
jobStore = MagicMock()
|
|
141
162
|
# ... to generate a fake URL for the resource ...
|
|
142
|
-
url =
|
|
163
|
+
url = "file://foo.zip"
|
|
143
164
|
jobStore.getSharedPublicUrl.return_value = url
|
|
144
165
|
# ... and save the resource to it.
|
|
145
166
|
resource = module.saveAsResourceTo(jobStore)
|
|
146
167
|
# Ensure that the URL generation method is actually called, ...
|
|
147
|
-
jobStore.getSharedPublicUrl.assert_called_once_with(
|
|
168
|
+
jobStore.getSharedPublicUrl.assert_called_once_with(
|
|
169
|
+
sharedFileName=resource.pathHash
|
|
170
|
+
)
|
|
148
171
|
# ... and that ensure that write_shared_file_stream is called.
|
|
149
|
-
jobStore.write_shared_file_stream.assert_called_once_with(
|
|
150
|
-
|
|
172
|
+
jobStore.write_shared_file_stream.assert_called_once_with(
|
|
173
|
+
shared_file_name=resource.pathHash, encrypted=False
|
|
174
|
+
)
|
|
151
175
|
# Now it gets a bit complicated: Ensure that the context manager returned by the
|
|
152
176
|
# jobStore's write_shared_file_stream() method is entered and that the file handle yielded
|
|
153
177
|
# by the context manager is written to once with the zipped source tree from which
|
|
154
178
|
# 'toil.resource' was originally imported. Keep the zipped tree around such that we can
|
|
155
179
|
# mock the download later.
|
|
156
|
-
file_handle =
|
|
180
|
+
file_handle = (
|
|
181
|
+
jobStore.write_shared_file_stream.return_value.__enter__.return_value
|
|
182
|
+
)
|
|
157
183
|
# The first 0 index selects the first call of write(), the second 0 selects positional
|
|
158
184
|
# instead of keyword arguments, and the third 0 selects the first positional, i.e. the
|
|
159
185
|
# contents. This is a bit brittle since it assumes that all the data is written in a
|
|
160
186
|
# single call to write(). If more calls are made we can easily concatenate them.
|
|
161
187
|
zipFile = file_handle.write.call_args_list[0][0][0]
|
|
162
|
-
self.assertTrue(zipFile.startswith(b
|
|
188
|
+
self.assertTrue(zipFile.startswith(b"PK")) # the magic header for ZIP files
|
|
163
189
|
|
|
164
190
|
# Check contents if requested
|
|
165
191
|
if expectedContents is not None:
|
|
@@ -186,7 +212,7 @@ class ResourceTest(ToilTest):
|
|
|
186
212
|
# urlopen() that yields the zipped tree ...
|
|
187
213
|
mock_urlopen = MagicMock()
|
|
188
214
|
mock_urlopen.return_value.read.return_value = zipFile
|
|
189
|
-
with patch(
|
|
215
|
+
with patch("toil.resource.urlopen", mock_urlopen):
|
|
190
216
|
# ... and use it to download and unpack the resource
|
|
191
217
|
localModule = module.localize()
|
|
192
218
|
# The name should be equal between original and localized resource ...
|
|
@@ -217,22 +243,27 @@ class ResourceTest(ToilTest):
|
|
|
217
243
|
def fn():
|
|
218
244
|
pass
|
|
219
245
|
|
|
220
|
-
if __name__ ==
|
|
246
|
+
if __name__ == "__main__":
|
|
221
247
|
parser = ArgumentParser()
|
|
222
248
|
Job.Runner.addToilOptions(parser)
|
|
223
249
|
options = parser.parse_args()
|
|
224
|
-
job = Job.wrapFn(fn, memory=
|
|
250
|
+
job = Job.wrapFn(fn, memory="10M", cores=0.1, disk="10M")
|
|
225
251
|
with Toil(options) as toil:
|
|
226
252
|
toil.start(job)
|
|
227
253
|
|
|
228
|
-
scriptBody = dedent(
|
|
229
|
-
shebang =
|
|
254
|
+
scriptBody = dedent("\n".join(getsource(script).split("\n")[1:]))
|
|
255
|
+
shebang = "#! %s\n" % sys.executable
|
|
230
256
|
with tempFileContaining(shebang + scriptBody) as scriptPath:
|
|
231
|
-
self.assertFalse(scriptPath.endswith((
|
|
257
|
+
self.assertFalse(scriptPath.endswith((".py", ".pyc")))
|
|
232
258
|
os.chmod(scriptPath, 0o755)
|
|
233
|
-
jobStorePath = scriptPath +
|
|
234
|
-
process = subprocess.Popen(
|
|
259
|
+
jobStorePath = scriptPath + ".jobStore"
|
|
260
|
+
process = subprocess.Popen(
|
|
261
|
+
[scriptPath, jobStorePath], stderr=subprocess.PIPE
|
|
262
|
+
)
|
|
235
263
|
stdout, stderr = process.communicate()
|
|
236
|
-
self.assertTrue(
|
|
264
|
+
self.assertTrue(
|
|
265
|
+
"The name of a user script/module must end in .py or .pyc."
|
|
266
|
+
in stderr.decode("utf-8")
|
|
267
|
+
)
|
|
237
268
|
self.assertNotEqual(0, process.returncode)
|
|
238
269
|
self.assertFalse(os.path.exists(jobStorePath))
|
toil/test/src/restartDAGTest.py
CHANGED
|
@@ -31,9 +31,10 @@ class RestartDAGTest(ToilTest):
|
|
|
31
31
|
Tests that restarted job DAGs don't run children of jobs that failed in the first run till the
|
|
32
32
|
parent completes successfully in the restart.
|
|
33
33
|
"""
|
|
34
|
+
|
|
34
35
|
def setUp(self):
|
|
35
36
|
super().setUp()
|
|
36
|
-
self.tempDir = self._createTempDir(purpose=
|
|
37
|
+
self.tempDir = self._createTempDir(purpose="tempDir")
|
|
37
38
|
self.testJobStore = self._getTestJobStorePath()
|
|
38
39
|
|
|
39
40
|
def tearDown(self):
|
|
@@ -42,11 +43,11 @@ class RestartDAGTest(ToilTest):
|
|
|
42
43
|
|
|
43
44
|
@slow
|
|
44
45
|
def testRestartedWorkflowSchedulesCorrectJobsOnFailedParent(self):
|
|
45
|
-
self._testRestartedWorkflowSchedulesCorrectJobs(
|
|
46
|
+
self._testRestartedWorkflowSchedulesCorrectJobs("raise")
|
|
46
47
|
|
|
47
48
|
@slow
|
|
48
49
|
def testRestartedWorkflowSchedulesCorrectJobsOnKilledParent(self):
|
|
49
|
-
self._testRestartedWorkflowSchedulesCorrectJobs(
|
|
50
|
+
self._testRestartedWorkflowSchedulesCorrectJobs("kill")
|
|
50
51
|
|
|
51
52
|
def _testRestartedWorkflowSchedulesCorrectJobs(self, failType):
|
|
52
53
|
"""
|
|
@@ -63,12 +64,12 @@ class RestartDAGTest(ToilTest):
|
|
|
63
64
|
"""
|
|
64
65
|
# Specify options
|
|
65
66
|
options = Job.Runner.getDefaultOptions(self.testJobStore)
|
|
66
|
-
options.logLevel =
|
|
67
|
+
options.logLevel = "DEBUG"
|
|
67
68
|
options.retryCount = 0
|
|
68
69
|
options.clean = "never"
|
|
69
70
|
|
|
70
|
-
parentFile = os.path.join(self.tempDir,
|
|
71
|
-
childFile = os.path.join(self.tempDir,
|
|
71
|
+
parentFile = os.path.join(self.tempDir, "parent")
|
|
72
|
+
childFile = os.path.join(self.tempDir, "child")
|
|
72
73
|
|
|
73
74
|
# Make the first job
|
|
74
75
|
root = Job.wrapJobFn(passingFn)
|
|
@@ -87,11 +88,11 @@ class RestartDAGTest(ToilTest):
|
|
|
87
88
|
assert not os.path.exists(childFile)
|
|
88
89
|
|
|
89
90
|
# Run the test
|
|
90
|
-
for runMode in
|
|
91
|
+
for runMode in "start", "restart":
|
|
91
92
|
self.errorRaised = None
|
|
92
93
|
try:
|
|
93
94
|
with Toil(options) as toil:
|
|
94
|
-
if runMode ==
|
|
95
|
+
if runMode == "start":
|
|
95
96
|
toil.start(root)
|
|
96
97
|
else:
|
|
97
98
|
toil.restart()
|
|
@@ -103,19 +104,27 @@ class RestartDAGTest(ToilTest):
|
|
|
103
104
|
# it together in this finally clause.
|
|
104
105
|
if self.errorRaised is not None:
|
|
105
106
|
if not os.path.exists(parentFile):
|
|
106
|
-
failReasons.append(
|
|
107
|
-
|
|
107
|
+
failReasons.append(
|
|
108
|
+
'The failing parent file did not exist on toil "%s".'
|
|
109
|
+
% runMode
|
|
110
|
+
)
|
|
108
111
|
if os.path.exists(childFile):
|
|
109
|
-
failReasons.append(
|
|
110
|
-
|
|
112
|
+
failReasons.append(
|
|
113
|
+
"The child file existed. i.e. the child was run on "
|
|
114
|
+
'toil "%s".' % runMode
|
|
115
|
+
)
|
|
111
116
|
if isinstance(self.errorRaised, FailedJobsException):
|
|
112
117
|
if self.errorRaised.numberOfFailedJobs != 3:
|
|
113
|
-
failReasons.append(
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
failReasons.append(
|
|
119
|
+
'FailedJobsException was raised on toil "%s" but '
|
|
120
|
+
"the number of failed jobs (%s) was not 3."
|
|
121
|
+
% (runMode, self.errorRaised.numberOfFailedJobs)
|
|
122
|
+
)
|
|
116
123
|
elif isinstance(self.errorRaised, AssertionError):
|
|
117
|
-
failReasons.append(
|
|
118
|
-
|
|
124
|
+
failReasons.append(
|
|
125
|
+
"Toil raised an AssertionError instead of a "
|
|
126
|
+
'FailedJobsException on toil "%s".' % runMode
|
|
127
|
+
)
|
|
119
128
|
else:
|
|
120
129
|
failReasons.append("Toil raised error: %s" % self.errorRaised)
|
|
121
130
|
self.errorRaised = None
|
|
@@ -123,8 +132,12 @@ class RestartDAGTest(ToilTest):
|
|
|
123
132
|
else:
|
|
124
133
|
self.fail('No errors were raised on toil "%s".' % runMode)
|
|
125
134
|
if failReasons:
|
|
126
|
-
self.fail(
|
|
127
|
-
|
|
135
|
+
self.fail(
|
|
136
|
+
"Test failed for ({}) reasons:\n\t{}".format(
|
|
137
|
+
len(failReasons), "\n\t".join(failReasons)
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
128
141
|
|
|
129
142
|
def passingFn(job, fileName=None):
|
|
130
143
|
"""
|
|
@@ -135,7 +148,8 @@ def passingFn(job, fileName=None):
|
|
|
135
148
|
"""
|
|
136
149
|
if fileName is not None:
|
|
137
150
|
# Emulates system touch.
|
|
138
|
-
open(fileName,
|
|
151
|
+
open(fileName, "w").close()
|
|
152
|
+
|
|
139
153
|
|
|
140
154
|
def failingFn(job, failType, fileName):
|
|
141
155
|
"""
|
|
@@ -145,11 +159,11 @@ def failingFn(job, failType, fileName):
|
|
|
145
159
|
:param str failType: 'raise' or 'kill
|
|
146
160
|
:param str fileName: The name of a file that must be created.
|
|
147
161
|
"""
|
|
148
|
-
assert failType in (
|
|
162
|
+
assert failType in ("raise", "kill")
|
|
149
163
|
# Use that function to avoid code redundancy
|
|
150
164
|
passingFn(job, fileName)
|
|
151
165
|
|
|
152
|
-
if failType ==
|
|
166
|
+
if failType == "raise":
|
|
153
167
|
assert False
|
|
154
168
|
else:
|
|
155
169
|
os.kill(os.getpid(), signal.SIGKILL)
|
|
@@ -24,6 +24,7 @@ class ResumabilityTest(ToilTest):
|
|
|
24
24
|
"""
|
|
25
25
|
https://github.com/BD2KGenomics/toil/issues/808
|
|
26
26
|
"""
|
|
27
|
+
|
|
27
28
|
@slow
|
|
28
29
|
def test(self):
|
|
29
30
|
"""
|
|
@@ -70,7 +71,7 @@ class ResumabilityTest(ToilTest):
|
|
|
70
71
|
# This one is intended to fail.
|
|
71
72
|
Job.Runner.startToil(root, options)
|
|
72
73
|
|
|
73
|
-
with open(options.logFile
|
|
74
|
+
with open(options.logFile) as f:
|
|
74
75
|
log_content = f.read()
|
|
75
76
|
# Make sure we actually did do chaining
|
|
76
77
|
assert "Chaining from" in log_content
|
|
@@ -81,6 +82,7 @@ class ResumabilityTest(ToilTest):
|
|
|
81
82
|
options.restart = True
|
|
82
83
|
Job.Runner.startToil(root, options)
|
|
83
84
|
|
|
85
|
+
|
|
84
86
|
def parent(job):
|
|
85
87
|
"""
|
|
86
88
|
Set up a bunch of dummy child jobs, and a bad job that needs to be
|
|
@@ -90,18 +92,21 @@ def parent(job):
|
|
|
90
92
|
job.addChildJobFn(goodChild)
|
|
91
93
|
job.addFollowOnJobFn(badChild)
|
|
92
94
|
|
|
95
|
+
|
|
93
96
|
def chaining_parent(job):
|
|
94
97
|
"""
|
|
95
98
|
Set up a failing job to chain to.
|
|
96
99
|
"""
|
|
97
100
|
job.addFollowOnJobFn(badChild)
|
|
98
101
|
|
|
102
|
+
|
|
99
103
|
def goodChild(job):
|
|
100
104
|
"""
|
|
101
105
|
Does nothing.
|
|
102
106
|
"""
|
|
103
107
|
return
|
|
104
108
|
|
|
109
|
+
|
|
105
110
|
def badChild(job):
|
|
106
111
|
"""
|
|
107
112
|
Fails the first time it's run, succeeds the second time.
|
|
@@ -110,6 +115,8 @@ def badChild(job):
|
|
|
110
115
|
with job.fileStore.jobStore.read_shared_file_stream("alreadyRun") as fileHandle:
|
|
111
116
|
fileHandle.read()
|
|
112
117
|
except NoSuchFileException as ex:
|
|
113
|
-
with job.fileStore.jobStore.write_shared_file_stream(
|
|
118
|
+
with job.fileStore.jobStore.write_shared_file_stream(
|
|
119
|
+
"alreadyRun", encrypted=False
|
|
120
|
+
) as fileHandle:
|
|
114
121
|
fileHandle.write(b"failed once\n")
|
|
115
122
|
raise RuntimeError(f"this is an expected error: {str(ex)}")
|
|
@@ -23,6 +23,7 @@ class CleanWorkDirTest(ToilTest):
|
|
|
23
23
|
"""
|
|
24
24
|
Tests testing :class:toil.fileStores.abstractFileStore.AbstractFileStore
|
|
25
25
|
"""
|
|
26
|
+
|
|
26
27
|
def setUp(self):
|
|
27
28
|
super().setUp()
|
|
28
29
|
self.testDir = self._createTempDir()
|
|
@@ -33,33 +34,61 @@ class CleanWorkDirTest(ToilTest):
|
|
|
33
34
|
|
|
34
35
|
def testNever(self):
|
|
35
36
|
retainedTempData = self._runAndReturnWorkDir("never", job=tempFileTestJob)
|
|
36
|
-
self.assertNotEqual(
|
|
37
|
-
|
|
37
|
+
self.assertNotEqual(
|
|
38
|
+
retainedTempData,
|
|
39
|
+
[],
|
|
40
|
+
"The worker's temporary workspace was deleted despite "
|
|
41
|
+
"cleanWorkDir being set to 'never'",
|
|
42
|
+
)
|
|
38
43
|
|
|
39
44
|
def testAlways(self):
|
|
40
45
|
retainedTempData = self._runAndReturnWorkDir("always", job=tempFileTestJob)
|
|
41
|
-
self.assertEqual(
|
|
42
|
-
|
|
46
|
+
self.assertEqual(
|
|
47
|
+
retainedTempData,
|
|
48
|
+
[],
|
|
49
|
+
"The worker's temporary workspace was not deleted despite "
|
|
50
|
+
"cleanWorkDir being set to 'always'",
|
|
51
|
+
)
|
|
43
52
|
|
|
44
53
|
def testOnErrorWithError(self):
|
|
45
|
-
retainedTempData = self._runAndReturnWorkDir(
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
retainedTempData = self._runAndReturnWorkDir(
|
|
55
|
+
"onError", job=tempFileTestErrorJob, expectError=True
|
|
56
|
+
)
|
|
57
|
+
self.assertEqual(
|
|
58
|
+
retainedTempData,
|
|
59
|
+
[],
|
|
60
|
+
"The worker's temporary workspace was not deleted despite "
|
|
61
|
+
"an error occurring and cleanWorkDir being set to 'onError'",
|
|
62
|
+
)
|
|
48
63
|
|
|
49
64
|
def testOnErrorWithNoError(self):
|
|
50
65
|
retainedTempData = self._runAndReturnWorkDir("onError", job=tempFileTestJob)
|
|
51
|
-
self.assertNotEqual(
|
|
52
|
-
|
|
66
|
+
self.assertNotEqual(
|
|
67
|
+
retainedTempData,
|
|
68
|
+
[],
|
|
69
|
+
"The worker's temporary workspace was deleted despite "
|
|
70
|
+
"no error occurring and cleanWorkDir being set to 'onError'",
|
|
71
|
+
)
|
|
53
72
|
|
|
54
73
|
def testOnSuccessWithError(self):
|
|
55
|
-
retainedTempData = self._runAndReturnWorkDir(
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
retainedTempData = self._runAndReturnWorkDir(
|
|
75
|
+
"onSuccess", job=tempFileTestErrorJob, expectError=True
|
|
76
|
+
)
|
|
77
|
+
self.assertNotEqual(
|
|
78
|
+
retainedTempData,
|
|
79
|
+
[],
|
|
80
|
+
"The worker's temporary workspace was deleted despite "
|
|
81
|
+
"an error occurring and cleanWorkDir being set to 'onSuccesss'",
|
|
82
|
+
)
|
|
58
83
|
|
|
59
84
|
def testOnSuccessWithSuccess(self):
|
|
60
85
|
retainedTempData = self._runAndReturnWorkDir("onSuccess", job=tempFileTestJob)
|
|
61
|
-
self.assertEqual(
|
|
62
|
-
|
|
86
|
+
self.assertEqual(
|
|
87
|
+
retainedTempData,
|
|
88
|
+
[],
|
|
89
|
+
"The worker's temporary workspace was not deleted despite "
|
|
90
|
+
"a successful job execution and cleanWorkDir being set to 'onSuccesss'",
|
|
91
|
+
)
|
|
63
92
|
|
|
64
93
|
def _runAndReturnWorkDir(self, cleanWorkDir, job, expectError=False):
|
|
65
94
|
"""
|
|
@@ -89,10 +118,12 @@ class CleanWorkDirTest(ToilTest):
|
|
|
89
118
|
else:
|
|
90
119
|
self.fail("Toil run succeeded unexpectedly")
|
|
91
120
|
|
|
121
|
+
|
|
92
122
|
def tempFileTestJob(job):
|
|
93
123
|
with open(job.fileStore.getLocalTempFile(), "w") as f:
|
|
94
124
|
f.write("test file retention")
|
|
95
125
|
|
|
126
|
+
|
|
96
127
|
def tempFileTestErrorJob(job):
|
|
97
128
|
with open(job.fileStore.getLocalTempFile(), "w") as f:
|
|
98
129
|
f.write("test file retention")
|
toil/test/src/systemTest.py
CHANGED
|
@@ -10,17 +10,21 @@ from toil.test import ToilTest
|
|
|
10
10
|
|
|
11
11
|
class SystemTest(ToilTest):
|
|
12
12
|
"""Test various assumptions about the operating system's behavior."""
|
|
13
|
+
|
|
13
14
|
def testAtomicityOfNonEmptyDirectoryRenames(self):
|
|
14
15
|
for _ in range(100):
|
|
15
|
-
parent = self._createTempDir(purpose=
|
|
16
|
-
child = os.path.join(parent,
|
|
16
|
+
parent = self._createTempDir(purpose="parent")
|
|
17
|
+
child = os.path.join(parent, "child")
|
|
17
18
|
# Use processes (as opposed to threads) to prevent GIL from ordering things artificially
|
|
18
19
|
pool = multiprocessing.Pool(processes=cpu_count())
|
|
19
20
|
try:
|
|
20
21
|
numTasks = cpu_count() * 10
|
|
21
22
|
grandChildIds = pool.map_async(
|
|
22
|
-
func=partial(
|
|
23
|
-
|
|
23
|
+
func=partial(
|
|
24
|
+
_testAtomicityOfNonEmptyDirectoryRenamesTask, parent, child
|
|
25
|
+
),
|
|
26
|
+
iterable=list(range(numTasks)),
|
|
27
|
+
)
|
|
24
28
|
grandChildIds = grandChildIds.get()
|
|
25
29
|
finally:
|
|
26
30
|
pool.close()
|
|
@@ -31,15 +35,15 @@ class SystemTest(ToilTest):
|
|
|
31
35
|
self.assertEqual(len(grandChildIds), 1)
|
|
32
36
|
# Assert that the winner's grandChild wasn't silently overwritten by a looser
|
|
33
37
|
expectedGrandChildId = grandChildIds[0]
|
|
34
|
-
actualGrandChild = os.path.join(child,
|
|
38
|
+
actualGrandChild = os.path.join(child, "grandChild")
|
|
35
39
|
actualGrandChildId = os.stat(actualGrandChild).st_ino
|
|
36
40
|
self.assertEqual(actualGrandChildId, expectedGrandChildId)
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
def _testAtomicityOfNonEmptyDirectoryRenamesTask(parent, child, _):
|
|
40
|
-
tmpChildDir = mkdtemp(dir=parent, prefix=
|
|
41
|
-
grandChild = os.path.join(tmpChildDir,
|
|
42
|
-
open(grandChild,
|
|
44
|
+
tmpChildDir = mkdtemp(dir=parent, prefix="child", suffix=".tmp")
|
|
45
|
+
grandChild = os.path.join(tmpChildDir, "grandChild")
|
|
46
|
+
open(grandChild, "w").close()
|
|
43
47
|
grandChildId = os.stat(grandChild).st_ino
|
|
44
48
|
try:
|
|
45
49
|
os.rename(tmpChildDir, child)
|