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/threadingTest.py
CHANGED
|
@@ -6,28 +6,29 @@ import time
|
|
|
6
6
|
import traceback
|
|
7
7
|
from functools import partial
|
|
8
8
|
|
|
9
|
-
from toil.lib.threading import
|
|
10
|
-
cpu_count,
|
|
11
|
-
global_mutex)
|
|
9
|
+
from toil.lib.threading import LastProcessStandingArena, cpu_count, global_mutex
|
|
12
10
|
from toil.test import ToilTest
|
|
13
11
|
|
|
14
12
|
log = logging.getLogger(__name__)
|
|
15
13
|
|
|
14
|
+
|
|
16
15
|
class ThreadingTest(ToilTest):
|
|
17
16
|
"""Test Toil threading/synchronization tools."""
|
|
17
|
+
|
|
18
18
|
def testGlobalMutexOrdering(self):
|
|
19
19
|
for it in range(10):
|
|
20
|
-
log.info(
|
|
20
|
+
log.info("Iteration %d", it)
|
|
21
21
|
|
|
22
22
|
scope = self._createTempDir()
|
|
23
|
-
mutex =
|
|
23
|
+
mutex = "mutex"
|
|
24
24
|
# Use processes (as opposed to threads) to prevent GIL from ordering things artificially
|
|
25
25
|
pool = multiprocessing.Pool(processes=cpu_count())
|
|
26
26
|
try:
|
|
27
27
|
numTasks = 100
|
|
28
28
|
results = pool.map_async(
|
|
29
29
|
func=partial(_testGlobalMutexOrderingTask, scope, mutex),
|
|
30
|
-
iterable=list(range(numTasks))
|
|
30
|
+
iterable=list(range(numTasks)),
|
|
31
|
+
)
|
|
31
32
|
results = results.get()
|
|
32
33
|
finally:
|
|
33
34
|
pool.close()
|
|
@@ -40,17 +41,18 @@ class ThreadingTest(ToilTest):
|
|
|
40
41
|
|
|
41
42
|
def testLastProcessStanding(self):
|
|
42
43
|
for it in range(10):
|
|
43
|
-
log.info(
|
|
44
|
+
log.info("Iteration %d", it)
|
|
44
45
|
|
|
45
46
|
scope = self._createTempDir()
|
|
46
|
-
arena_name =
|
|
47
|
+
arena_name = "thunderdome"
|
|
47
48
|
# Use processes (as opposed to threads) to prevent GIL from ordering things artificially
|
|
48
49
|
pool = multiprocessing.Pool(processes=cpu_count())
|
|
49
50
|
try:
|
|
50
51
|
numTasks = 100
|
|
51
52
|
results = pool.map_async(
|
|
52
53
|
func=partial(_testLastProcessStandingTask, scope, arena_name),
|
|
53
|
-
iterable=list(range(numTasks))
|
|
54
|
+
iterable=list(range(numTasks)),
|
|
55
|
+
)
|
|
54
56
|
results = results.get()
|
|
55
57
|
finally:
|
|
56
58
|
pool.close()
|
|
@@ -61,19 +63,24 @@ class ThreadingTest(ToilTest):
|
|
|
61
63
|
# Make sure all workers say they succeeded
|
|
62
64
|
self.assertEqual(item, True)
|
|
63
65
|
for filename in os.listdir(scope):
|
|
64
|
-
assert not filename.startswith(
|
|
66
|
+
assert not filename.startswith(
|
|
67
|
+
"precious"
|
|
68
|
+
), f"File {filename} still exists"
|
|
69
|
+
|
|
65
70
|
|
|
66
71
|
def _testGlobalMutexOrderingTask(scope, mutex, number):
|
|
67
72
|
try:
|
|
68
73
|
# We will all fight over the potato
|
|
69
|
-
potato = os.path.join(scope,
|
|
74
|
+
potato = os.path.join(scope, "potato")
|
|
70
75
|
|
|
71
76
|
with global_mutex(scope, mutex):
|
|
72
|
-
log.info(
|
|
73
|
-
assert not os.path.exists(
|
|
77
|
+
log.info("PID %d = num %d running", os.getpid(), number)
|
|
78
|
+
assert not os.path.exists(
|
|
79
|
+
potato
|
|
80
|
+
), "We see someone else holding the potato file"
|
|
74
81
|
|
|
75
82
|
# Put our name there
|
|
76
|
-
with open(potato,
|
|
83
|
+
with open(potato, "w") as out_stream:
|
|
77
84
|
out_stream.write(str(number))
|
|
78
85
|
|
|
79
86
|
# Wait
|
|
@@ -82,51 +89,63 @@ def _testGlobalMutexOrderingTask(scope, mutex, number):
|
|
|
82
89
|
# Make sure our name is still there
|
|
83
90
|
with open(potato) as in_stream:
|
|
84
91
|
seen = in_stream.read().rstrip()
|
|
85
|
-
assert seen == str(
|
|
92
|
+
assert seen == str(
|
|
93
|
+
number
|
|
94
|
+
), f"We are {number} but {seen} stole our potato!"
|
|
86
95
|
|
|
87
96
|
os.unlink(potato)
|
|
88
97
|
assert not os.path.exists(potato), "We left the potato behind"
|
|
89
|
-
log.info(
|
|
98
|
+
log.info("PID %d = num %d dropped potato", os.getpid(), number)
|
|
90
99
|
return True
|
|
91
100
|
except:
|
|
92
101
|
traceback.print_exc()
|
|
93
102
|
return False
|
|
94
103
|
|
|
104
|
+
|
|
95
105
|
def _testLastProcessStandingTask(scope, arena_name, number):
|
|
96
106
|
try:
|
|
97
107
|
arena = LastProcessStandingArena(scope, arena_name)
|
|
98
108
|
|
|
99
109
|
arena.enter()
|
|
100
|
-
log.info(
|
|
110
|
+
log.info("PID %d = num %d entered arena", os.getpid(), number)
|
|
101
111
|
try:
|
|
102
112
|
# We all make files
|
|
103
|
-
my_precious = os.path.join(scope,
|
|
113
|
+
my_precious = os.path.join(scope, "precious" + str(number))
|
|
104
114
|
|
|
105
115
|
# Put our name there
|
|
106
|
-
with open(my_precious,
|
|
116
|
+
with open(my_precious, "w") as out_stream:
|
|
107
117
|
out_stream.write(str(number))
|
|
108
118
|
|
|
109
119
|
# Wait
|
|
110
120
|
time.sleep(random.random() * 0.01)
|
|
111
121
|
|
|
112
122
|
# Make sure our file is still there unmodified
|
|
113
|
-
assert os.path.exists(
|
|
123
|
+
assert os.path.exists(
|
|
124
|
+
my_precious
|
|
125
|
+
), f"Precious file {my_precious} has been stolen!"
|
|
114
126
|
with open(my_precious) as in_stream:
|
|
115
127
|
seen = in_stream.read().rstrip()
|
|
116
|
-
assert seen == str(
|
|
128
|
+
assert seen == str(
|
|
129
|
+
number
|
|
130
|
+
), f"We are {number} but saw {seen} in our precious file!"
|
|
117
131
|
finally:
|
|
118
132
|
was_last = False
|
|
119
133
|
for _ in arena.leave():
|
|
120
134
|
was_last = True
|
|
121
|
-
log.info(
|
|
135
|
+
log.info("PID %d = num %d is last standing", os.getpid(), number)
|
|
122
136
|
|
|
123
137
|
# Clean up all the files
|
|
124
138
|
for filename in os.listdir(scope):
|
|
125
|
-
if filename.startswith(
|
|
126
|
-
log.info(
|
|
139
|
+
if filename.startswith("precious"):
|
|
140
|
+
log.info(
|
|
141
|
+
"PID %d = num %d cleaning up %s",
|
|
142
|
+
os.getpid(),
|
|
143
|
+
number,
|
|
144
|
+
filename,
|
|
145
|
+
)
|
|
127
146
|
os.unlink(os.path.join(scope, filename))
|
|
128
147
|
|
|
129
|
-
log.info(
|
|
148
|
+
log.info("PID %d = num %d left arena", os.getpid(), number)
|
|
130
149
|
|
|
131
150
|
return True
|
|
132
151
|
except:
|
|
@@ -29,13 +29,13 @@ class ToilContextManagerTest(ToilTest):
|
|
|
29
29
|
|
|
30
30
|
def testContextManger(self):
|
|
31
31
|
options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
|
|
32
|
-
options.logLevel =
|
|
32
|
+
options.logLevel = "INFO"
|
|
33
33
|
with Toil(options) as toil:
|
|
34
34
|
toil.start(HelloWorld())
|
|
35
35
|
|
|
36
36
|
def testNoContextManger(self):
|
|
37
37
|
options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
|
|
38
|
-
options.logLevel =
|
|
38
|
+
options.logLevel = "INFO"
|
|
39
39
|
toil = Toil(options)
|
|
40
40
|
self.assertRaises(ToilContextManagerException, toil.start, HelloWorld())
|
|
41
41
|
|
|
@@ -45,7 +45,9 @@ class ToilContextManagerTest(ToilTest):
|
|
|
45
45
|
with Toil(options) as toil:
|
|
46
46
|
_ = toil.start(HelloWorld())
|
|
47
47
|
# oh no, an error! :(
|
|
48
|
-
raise RuntimeError(
|
|
48
|
+
raise RuntimeError(
|
|
49
|
+
"we died after workflow completion but before our export finished"
|
|
50
|
+
)
|
|
49
51
|
except RuntimeError:
|
|
50
52
|
pass
|
|
51
53
|
|
|
@@ -54,17 +56,18 @@ class ToilContextManagerTest(ToilTest):
|
|
|
54
56
|
fileID = toil.restart()
|
|
55
57
|
print(fileID)
|
|
56
58
|
# Hopefully the error didn't cause us to lose all our work!
|
|
57
|
-
toil.exportFile(fileID,
|
|
59
|
+
toil.exportFile(fileID, "file://" + self.exportPath)
|
|
58
60
|
with open(self.exportPath) as f:
|
|
59
61
|
# The file should have all our content
|
|
60
62
|
self.assertEqual(f.read(), "Hello, World!")
|
|
61
63
|
|
|
64
|
+
|
|
62
65
|
class HelloWorld(Job):
|
|
63
66
|
def __init__(self):
|
|
64
|
-
Job.__init__(self, memory=100000, disk=
|
|
67
|
+
Job.__init__(self, memory=100000, disk="1M")
|
|
65
68
|
|
|
66
69
|
def run(self, fileStore):
|
|
67
|
-
fileID = self.addChildJobFn(childFn, memory=
|
|
70
|
+
fileID = self.addChildJobFn(childFn, memory="1M", disk="1M").rv()
|
|
68
71
|
return self.addFollowOn(FollowOn(fileID)).rv()
|
|
69
72
|
|
|
70
73
|
|
|
@@ -81,7 +84,7 @@ class FollowOn(Job):
|
|
|
81
84
|
|
|
82
85
|
def run(self, fileStore):
|
|
83
86
|
tempDir = fileStore.getLocalTempDir()
|
|
84
|
-
tempFilePath = "/".join([tempDir,
|
|
87
|
+
tempFilePath = "/".join([tempDir, "LocalCopy"])
|
|
85
88
|
with fileStore.readGlobalFileStream(self.fileId) as globalFile:
|
|
86
89
|
with open(tempFilePath, "wb") as localFile:
|
|
87
90
|
localFile.write(globalFile.read())
|
|
@@ -52,9 +52,11 @@ class UserDefinedJobArgTypeTest(ToilTest):
|
|
|
52
52
|
self._testFromMain()
|
|
53
53
|
|
|
54
54
|
def _testFromMain(self):
|
|
55
|
-
testMethodName = self.id().split(
|
|
56
|
-
self.assertTrue(testMethodName.endswith(
|
|
57
|
-
subprocess.check_call(
|
|
55
|
+
testMethodName = self.id().split(".")[-1]
|
|
56
|
+
self.assertTrue(testMethodName.endswith("FromMain"))
|
|
57
|
+
subprocess.check_call(
|
|
58
|
+
[sys.executable, "-m", self.__module__, testMethodName[:-8]]
|
|
59
|
+
)
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
class JobClass(Job):
|
|
@@ -66,8 +68,9 @@ class JobClass(Job):
|
|
|
66
68
|
def run(self, fileStore):
|
|
67
69
|
self.foo.assertIsCopy()
|
|
68
70
|
if self.level < 2:
|
|
69
|
-
self.addChildJobFn(
|
|
70
|
-
|
|
71
|
+
self.addChildJobFn(
|
|
72
|
+
jobFunction, self.level + 1, Foo(), cores=1, memory="1M", disk="300M"
|
|
73
|
+
)
|
|
71
74
|
|
|
72
75
|
|
|
73
76
|
def jobFunction(job, level, foo):
|
toil/test/src/workerTest.py
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
15
17
|
from toil.common import Config
|
|
16
18
|
from toil.job import CheckpointJobDescription, JobDescription
|
|
17
19
|
from toil.jobStores.fileJobStore import FileJobStore
|
|
@@ -21,27 +23,45 @@ from toil.worker import nextChainable
|
|
|
21
23
|
|
|
22
24
|
class WorkerTests(ToilTest):
|
|
23
25
|
"""Test miscellaneous units of the worker."""
|
|
26
|
+
|
|
24
27
|
def setUp(self):
|
|
25
28
|
super().setUp()
|
|
26
29
|
path = self._getTestJobStorePath()
|
|
27
30
|
self.jobStore = FileJobStore(path)
|
|
28
31
|
self.config = Config()
|
|
29
|
-
self.config.jobStore =
|
|
32
|
+
self.config.jobStore = "file:%s" % path
|
|
30
33
|
self.jobStore.initialize(self.config)
|
|
31
34
|
self.jobNumber = 0
|
|
32
35
|
|
|
33
36
|
def testNextChainable(self):
|
|
34
37
|
"""Make sure chainable/non-chainable jobs are identified correctly."""
|
|
35
|
-
|
|
38
|
+
|
|
39
|
+
def createTestJobDesc(
|
|
40
|
+
memory,
|
|
41
|
+
cores,
|
|
42
|
+
disk,
|
|
43
|
+
preemptible: bool = True,
|
|
44
|
+
checkpoint: bool = False,
|
|
45
|
+
local: Optional[bool] = None,
|
|
46
|
+
):
|
|
36
47
|
"""
|
|
37
48
|
Create a JobDescription with no command (representing a Job that
|
|
38
49
|
has already run) and return the JobDescription.
|
|
39
50
|
"""
|
|
40
|
-
name =
|
|
51
|
+
name = "job%d" % self.jobNumber
|
|
41
52
|
self.jobNumber += 1
|
|
42
53
|
|
|
43
54
|
descClass = CheckpointJobDescription if checkpoint else JobDescription
|
|
44
|
-
jobDesc = descClass(
|
|
55
|
+
jobDesc = descClass(
|
|
56
|
+
requirements={
|
|
57
|
+
"memory": memory,
|
|
58
|
+
"cores": cores,
|
|
59
|
+
"disk": disk,
|
|
60
|
+
"preemptible": preemptible,
|
|
61
|
+
},
|
|
62
|
+
jobName=name,
|
|
63
|
+
local=local,
|
|
64
|
+
)
|
|
45
65
|
|
|
46
66
|
# Assign an ID
|
|
47
67
|
self.jobStore.assign_job_id(jobDesc)
|
|
@@ -49,46 +69,76 @@ class WorkerTests(ToilTest):
|
|
|
49
69
|
# Save and return the JobDescription
|
|
50
70
|
return self.jobStore.create_job(jobDesc)
|
|
51
71
|
|
|
52
|
-
for successorType in [
|
|
72
|
+
for successorType in ["addChild", "addFollowOn"]:
|
|
53
73
|
# Try with the branch point at both child and follow-on stages
|
|
54
74
|
|
|
55
75
|
# Identical non-checkpoint jobs should be chainable.
|
|
56
|
-
jobDesc1 = createTestJobDesc(1, 2, 3
|
|
57
|
-
jobDesc2 = createTestJobDesc(1, 2, 3
|
|
76
|
+
jobDesc1 = createTestJobDesc(1, 2, 3)
|
|
77
|
+
jobDesc2 = createTestJobDesc(1, 2, 3)
|
|
58
78
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
59
79
|
chainable = nextChainable(jobDesc1, self.jobStore, self.config)
|
|
60
80
|
self.assertNotEqual(chainable, None)
|
|
61
|
-
self.assertEqual(
|
|
81
|
+
self.assertEqual(chainable.jobStoreID, jobDesc2.jobStoreID)
|
|
62
82
|
|
|
63
83
|
# Identical checkpoint jobs should not be chainable.
|
|
64
|
-
jobDesc1 = createTestJobDesc(1, 2, 3, True
|
|
65
|
-
jobDesc2 = createTestJobDesc(1, 2, 3, True
|
|
84
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, checkpoint=True)
|
|
85
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, checkpoint=True)
|
|
66
86
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
67
|
-
self.assertEqual(
|
|
87
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
88
|
+
|
|
89
|
+
# Changing checkpoint from false to true should make it not chainable.
|
|
90
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, checkpoint=False)
|
|
91
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, checkpoint=True)
|
|
92
|
+
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
93
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
68
94
|
|
|
69
95
|
# If there is no child we should get nothing to chain.
|
|
70
|
-
jobDesc1 = createTestJobDesc(1, 2, 3
|
|
71
|
-
self.assertEqual(
|
|
96
|
+
jobDesc1 = createTestJobDesc(1, 2, 3)
|
|
97
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
72
98
|
|
|
73
99
|
# If there are 2 or more children we should get nothing to chain.
|
|
74
|
-
jobDesc1 = createTestJobDesc(1, 2, 3
|
|
75
|
-
jobDesc2 = createTestJobDesc(1, 2, 3
|
|
76
|
-
jobDesc3 = createTestJobDesc(1, 2, 3
|
|
100
|
+
jobDesc1 = createTestJobDesc(1, 2, 3)
|
|
101
|
+
jobDesc2 = createTestJobDesc(1, 2, 3)
|
|
102
|
+
jobDesc3 = createTestJobDesc(1, 2, 3)
|
|
77
103
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
78
104
|
getattr(jobDesc1, successorType)(jobDesc3.jobStoreID)
|
|
79
|
-
self.assertEqual(
|
|
105
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
80
106
|
|
|
81
107
|
# If there is an increase in resource requirements we should get nothing to chain.
|
|
82
|
-
|
|
83
|
-
|
|
108
|
+
base_reqs = {
|
|
109
|
+
"memory": 1,
|
|
110
|
+
"cores": 2,
|
|
111
|
+
"disk": 3,
|
|
112
|
+
"preemptible": True,
|
|
113
|
+
"checkpoint": False,
|
|
114
|
+
}
|
|
115
|
+
for increased_attribute in ("memory", "cores", "disk"):
|
|
116
|
+
reqs = dict(base_reqs)
|
|
84
117
|
jobDesc1 = createTestJobDesc(**reqs)
|
|
85
118
|
reqs[increased_attribute] += 1
|
|
86
119
|
jobDesc2 = createTestJobDesc(**reqs)
|
|
87
120
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
88
|
-
self.assertEqual(
|
|
121
|
+
self.assertEqual(
|
|
122
|
+
nextChainable(jobDesc1, self.jobStore, self.config), None
|
|
123
|
+
)
|
|
89
124
|
|
|
90
125
|
# A change in preemptability from True to False should be disallowed.
|
|
91
|
-
jobDesc1 = createTestJobDesc(1, 2, 3, True
|
|
92
|
-
jobDesc2 = createTestJobDesc(1, 2, 3, False
|
|
126
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, preemptible=True)
|
|
127
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, preemptible=False)
|
|
93
128
|
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
94
|
-
self.assertEqual(
|
|
129
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
130
|
+
|
|
131
|
+
# A change in local-ness from True to False should be disallowed.
|
|
132
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, local=True)
|
|
133
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, local=False)
|
|
134
|
+
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
135
|
+
self.assertEqual(nextChainable(jobDesc1, self.jobStore, self.config), None)
|
|
136
|
+
|
|
137
|
+
# A change in local-ness from False to True should be allowed,
|
|
138
|
+
# since running locally is an optional optimization.
|
|
139
|
+
jobDesc1 = createTestJobDesc(1, 2, 3, local=False)
|
|
140
|
+
jobDesc2 = createTestJobDesc(1, 2, 3, local=True)
|
|
141
|
+
getattr(jobDesc1, successorType)(jobDesc2.jobStoreID)
|
|
142
|
+
chainable = nextChainable(jobDesc1, self.jobStore, self.config)
|
|
143
|
+
self.assertNotEqual(chainable, None)
|
|
144
|
+
self.assertEqual(chainable.jobStoreID, jobDesc2.jobStoreID)
|
toil/test/utils/toilDebugTest.py
CHANGED
|
@@ -16,12 +16,8 @@ import os
|
|
|
16
16
|
import subprocess
|
|
17
17
|
import tempfile
|
|
18
18
|
|
|
19
|
-
import pytest
|
|
20
|
-
|
|
21
|
-
from toil.test import ToilTest
|
|
22
|
-
|
|
23
19
|
from toil.lib.resources import glob
|
|
24
|
-
from toil.test import slow
|
|
20
|
+
from toil.test import ToilTest, needs_wdl, slow
|
|
25
21
|
from toil.version import python
|
|
26
22
|
|
|
27
23
|
logger = logging.getLogger(__name__)
|
|
@@ -121,6 +117,7 @@ def testFetchJobStoreFiles() -> None:
|
|
|
121
117
|
for symlink in (True, False):
|
|
122
118
|
fetchFiles(symLink=symlink, jobStoreDir=job_store_dir, outputDir=output_dir)
|
|
123
119
|
|
|
120
|
+
|
|
124
121
|
class DebugJobTest(ToilTest):
|
|
125
122
|
"""
|
|
126
123
|
Test the toil debug-job command.
|
|
@@ -136,27 +133,77 @@ class DebugJobTest(ToilTest):
|
|
|
136
133
|
|
|
137
134
|
logger.info("Running workflow that always fails")
|
|
138
135
|
try:
|
|
139
|
-
# Run an always-
|
|
140
|
-
subprocess.check_call(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
# Run an always-failing workflow
|
|
137
|
+
subprocess.check_call(
|
|
138
|
+
[
|
|
139
|
+
python,
|
|
140
|
+
os.path.abspath("src/toil/test/docs/scripts/example_alwaysfail.py"),
|
|
141
|
+
"--retryCount=0",
|
|
142
|
+
"--logCritical",
|
|
143
|
+
"--disableProgress",
|
|
144
|
+
job_store,
|
|
145
|
+
],
|
|
146
|
+
stderr=subprocess.DEVNULL,
|
|
147
|
+
)
|
|
148
148
|
raise RuntimeError("Failing workflow succeeded!")
|
|
149
149
|
except subprocess.CalledProcessError:
|
|
150
150
|
# Should fail to run
|
|
151
151
|
logger.info("Task failed successfully")
|
|
152
|
-
pass
|
|
153
152
|
|
|
154
153
|
# Get the job ID.
|
|
155
154
|
# TODO: This assumes a lot about the FileJobStore. Use the MessageBus instead?
|
|
156
|
-
job_id =
|
|
155
|
+
job_id = (
|
|
156
|
+
"kind-explode/"
|
|
157
|
+
+ os.listdir(os.path.join(job_store, "jobs/kind-explode"))[0]
|
|
158
|
+
)
|
|
157
159
|
|
|
158
160
|
return job_store, job_id
|
|
159
161
|
|
|
162
|
+
def _get_wdl_job_store_and_job_name(self):
|
|
163
|
+
"""
|
|
164
|
+
Get a job store and the name of a failed job in it that actually wanted to use some files.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
# First make a job store.
|
|
168
|
+
job_store = os.path.join(self._createTempDir(), "tree")
|
|
169
|
+
|
|
170
|
+
logger.info("Running workflow that always fails")
|
|
171
|
+
# Run an always-failing workflow
|
|
172
|
+
wf_result = subprocess.run(
|
|
173
|
+
[
|
|
174
|
+
"toil-wdl-runner",
|
|
175
|
+
os.path.abspath(
|
|
176
|
+
"src/toil/test/docs/scripts/example_alwaysfail_with_files.wdl"
|
|
177
|
+
),
|
|
178
|
+
"--retryCount=0",
|
|
179
|
+
"--logDebug",
|
|
180
|
+
"--disableProgress",
|
|
181
|
+
"--jobStore",
|
|
182
|
+
job_store,
|
|
183
|
+
],
|
|
184
|
+
stdout=subprocess.PIPE,
|
|
185
|
+
stderr=subprocess.STDOUT,
|
|
186
|
+
encoding="utf-8",
|
|
187
|
+
errors="replace",
|
|
188
|
+
)
|
|
189
|
+
logger.debug("Always-failing workflow output: %s", wf_result.stdout)
|
|
190
|
+
if wf_result.returncode == 0:
|
|
191
|
+
raise RuntimeError("Failing workflow succeeded!")
|
|
192
|
+
else:
|
|
193
|
+
logger.info("Task failed successfully")
|
|
194
|
+
|
|
195
|
+
# Make sure that the job store we created actually has its job store
|
|
196
|
+
# root job ID file. If it doesn't, we failed during workflow setup and
|
|
197
|
+
# not because of a real failing job.
|
|
198
|
+
assert os.path.exists(
|
|
199
|
+
os.path.join(job_store, "files/shared/rootJobStoreID")
|
|
200
|
+
), "Failed workflow still needs a root job"
|
|
201
|
+
|
|
202
|
+
# Get a job name for a job that fails
|
|
203
|
+
job_name = "WDLTaskJob"
|
|
204
|
+
|
|
205
|
+
return job_store, job_name
|
|
206
|
+
|
|
160
207
|
def test_run_job(self):
|
|
161
208
|
"""
|
|
162
209
|
Make sure that we can use toil debug-job to try and run a job in-process.
|
|
@@ -167,18 +214,14 @@ class DebugJobTest(ToilTest):
|
|
|
167
214
|
logger.info("Trying to rerun job %s", job_id)
|
|
168
215
|
|
|
169
216
|
# Rerun the job, which should fail again
|
|
170
|
-
output = subprocess.check_output(
|
|
171
|
-
"toil",
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
job_store,
|
|
175
|
-
job_id
|
|
176
|
-
], stderr=subprocess.STDOUT)
|
|
217
|
+
output = subprocess.check_output(
|
|
218
|
+
["toil", "debug-job", "--logDebug", job_store, job_id],
|
|
219
|
+
stderr=subprocess.STDOUT,
|
|
220
|
+
)
|
|
177
221
|
# Even if the job fails, the attempt to run it will succeed.
|
|
178
|
-
log = output.decode(
|
|
222
|
+
log = output.decode("utf-8")
|
|
179
223
|
assert "Boom!" in log, f"Did not find the expected exception message in: {log}"
|
|
180
224
|
|
|
181
|
-
|
|
182
225
|
def test_print_job_info(self):
|
|
183
226
|
"""
|
|
184
227
|
Make sure that we can use --printJobInfo to get information on a job from a job store.
|
|
@@ -189,13 +232,40 @@ class DebugJobTest(ToilTest):
|
|
|
189
232
|
logger.info("Trying to print job info for job %s", job_id)
|
|
190
233
|
|
|
191
234
|
# Print the job info and make sure that doesn't crash.
|
|
192
|
-
subprocess.check_call(
|
|
193
|
-
"toil",
|
|
194
|
-
|
|
195
|
-
"--logDebug",
|
|
196
|
-
job_store,
|
|
197
|
-
"--printJobInfo",
|
|
198
|
-
job_id
|
|
199
|
-
])
|
|
235
|
+
subprocess.check_call(
|
|
236
|
+
["toil", "debug-job", "--logDebug", job_store, "--printJobInfo", job_id]
|
|
237
|
+
)
|
|
200
238
|
|
|
239
|
+
@needs_wdl
|
|
240
|
+
def test_retrieve_task_directory(self):
|
|
241
|
+
"""
|
|
242
|
+
Make sure that we can use --retrieveTaskDirectory to get the input files for a job.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
job_store, job_name = self._get_wdl_job_store_and_job_name()
|
|
201
246
|
|
|
247
|
+
logger.info("Trying to retrieve task dorectory for job %s", job_name)
|
|
248
|
+
|
|
249
|
+
dest_dir = os.path.join(self._createTempDir(), "dump")
|
|
250
|
+
|
|
251
|
+
# Print the job info and make sure that doesn't crash.
|
|
252
|
+
subprocess.check_call(
|
|
253
|
+
[
|
|
254
|
+
"toil",
|
|
255
|
+
"debug-job",
|
|
256
|
+
"--logDebug",
|
|
257
|
+
job_store,
|
|
258
|
+
job_name,
|
|
259
|
+
"--retrieveTaskDirectory",
|
|
260
|
+
dest_dir,
|
|
261
|
+
]
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
first_file = os.path.join(
|
|
265
|
+
dest_dir,
|
|
266
|
+
"inside/mnt/miniwdl_task_container/work/_miniwdl_inputs/0/test.txt",
|
|
267
|
+
)
|
|
268
|
+
assert os.path.exists(
|
|
269
|
+
first_file
|
|
270
|
+
), "Input file not found in fake container environment"
|
|
271
|
+
self.assertEqual(open(first_file).read(), "These are the contents\n")
|
toil/test/utils/toilKillTest.py
CHANGED
|
@@ -21,8 +21,7 @@ import time
|
|
|
21
21
|
import unittest
|
|
22
22
|
|
|
23
23
|
from toil.common import Toil
|
|
24
|
-
from toil.jobStores.abstractJobStore import
|
|
25
|
-
NoSuchJobStoreException)
|
|
24
|
+
from toil.jobStores.abstractJobStore import NoSuchFileException, NoSuchJobStoreException
|
|
26
25
|
from toil.jobStores.utils import generate_locator
|
|
27
26
|
from toil.test import ToilTest, needs_aws_s3, needs_cwl
|
|
28
27
|
|
|
@@ -61,7 +60,7 @@ class ToilKillTest(ToilTest):
|
|
|
61
60
|
kill_cmd = ["toil", "kill", self.job_store]
|
|
62
61
|
|
|
63
62
|
# run the sleep workflow
|
|
64
|
-
logger.info(
|
|
63
|
+
logger.info("Running workflow: %s", " ".join(run_cmd))
|
|
65
64
|
cwl_process = subprocess.Popen(run_cmd)
|
|
66
65
|
|
|
67
66
|
# wait until workflow starts running
|
|
@@ -75,9 +74,9 @@ class ToilKillTest(ToilTest):
|
|
|
75
74
|
# kill flag exists to be deleted to kill the leader
|
|
76
75
|
break
|
|
77
76
|
else:
|
|
78
|
-
logger.info(
|
|
77
|
+
logger.info("Waiting for kill flag...")
|
|
79
78
|
except (NoSuchJobStoreException, NoSuchFileException):
|
|
80
|
-
logger.info(
|
|
79
|
+
logger.info("Waiting for job store to be openable...")
|
|
81
80
|
time.sleep(2)
|
|
82
81
|
|
|
83
82
|
# run toil kill
|