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.
Files changed (193) hide show
  1. toil/__init__.py +122 -315
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +173 -89
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
  5. toil/batchSystems/awsBatch.py +244 -135
  6. toil/batchSystems/cleanup_support.py +26 -16
  7. toil/batchSystems/contained_executor.py +31 -28
  8. toil/batchSystems/gridengine.py +86 -50
  9. toil/batchSystems/htcondor.py +166 -89
  10. toil/batchSystems/kubernetes.py +632 -382
  11. toil/batchSystems/local_support.py +20 -15
  12. toil/batchSystems/lsf.py +134 -81
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +290 -151
  16. toil/batchSystems/mesos/executor.py +79 -50
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +46 -28
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +296 -125
  21. toil/batchSystems/slurm.py +603 -138
  22. toil/batchSystems/torque.py +47 -33
  23. toil/bus.py +186 -76
  24. toil/common.py +664 -368
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1136 -483
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +63 -42
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +140 -60
  32. toil/fileStores/cachingFileStore.py +717 -269
  33. toil/fileStores/nonCachingFileStore.py +116 -87
  34. toil/job.py +1225 -368
  35. toil/jobStores/abstractJobStore.py +416 -266
  36. toil/jobStores/aws/jobStore.py +863 -477
  37. toil/jobStores/aws/utils.py +201 -120
  38. toil/jobStores/conftest.py +3 -2
  39. toil/jobStores/fileJobStore.py +292 -154
  40. toil/jobStores/googleJobStore.py +140 -74
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +668 -272
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +74 -31
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +214 -39
  49. toil/lib/aws/utils.py +287 -231
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +104 -47
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +361 -199
  55. toil/lib/ec2nodes.py +174 -106
  56. toil/lib/encryption/_dummy.py +5 -3
  57. toil/lib/encryption/_nacl.py +10 -6
  58. toil/lib/encryption/conftest.py +1 -0
  59. toil/lib/exceptions.py +26 -7
  60. toil/lib/expando.py +5 -3
  61. toil/lib/ftp_utils.py +217 -0
  62. toil/lib/generatedEC2Lists.py +127 -19
  63. toil/lib/humanize.py +6 -2
  64. toil/lib/integration.py +341 -0
  65. toil/lib/io.py +141 -15
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +66 -21
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +68 -15
  71. toil/lib/retry.py +126 -81
  72. toil/lib/threading.py +299 -82
  73. toil/lib/throttle.py +16 -15
  74. toil/options/common.py +843 -409
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +73 -17
  78. toil/provisioners/__init__.py +117 -46
  79. toil/provisioners/abstractProvisioner.py +332 -157
  80. toil/provisioners/aws/__init__.py +70 -33
  81. toil/provisioners/aws/awsProvisioner.py +1145 -715
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +155 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +128 -62
  88. toil/server/celery_app.py +3 -1
  89. toil/server/cli/wes_cwl_runner.py +82 -53
  90. toil/server/utils.py +54 -28
  91. toil/server/wes/abstract_backend.py +64 -26
  92. toil/server/wes/amazon_wes_utils.py +21 -15
  93. toil/server/wes/tasks.py +121 -63
  94. toil/server/wes/toil_backend.py +142 -107
  95. toil/server/wsgi_app.py +4 -3
  96. toil/serviceManager.py +58 -22
  97. toil/statsAndLogging.py +224 -70
  98. toil/test/__init__.py +282 -183
  99. toil/test/batchSystems/batchSystemTest.py +460 -210
  100. toil/test/batchSystems/batch_system_plugin_test.py +90 -0
  101. toil/test/batchSystems/test_gridengine.py +173 -0
  102. toil/test/batchSystems/test_lsf_helper.py +67 -58
  103. toil/test/batchSystems/test_slurm.py +110 -49
  104. toil/test/cactus/__init__.py +0 -0
  105. toil/test/cactus/test_cactus_integration.py +56 -0
  106. toil/test/cwl/cwlTest.py +496 -287
  107. toil/test/cwl/measure_default_memory.cwl +12 -0
  108. toil/test/cwl/not_run_required_input.cwl +29 -0
  109. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  110. toil/test/cwl/seqtk_seq.cwl +1 -1
  111. toil/test/docs/scriptsTest.py +69 -46
  112. toil/test/jobStores/jobStoreTest.py +427 -264
  113. toil/test/lib/aws/test_iam.py +118 -50
  114. toil/test/lib/aws/test_s3.py +16 -9
  115. toil/test/lib/aws/test_utils.py +5 -6
  116. toil/test/lib/dockerTest.py +118 -141
  117. toil/test/lib/test_conversions.py +113 -115
  118. toil/test/lib/test_ec2.py +58 -50
  119. toil/test/lib/test_integration.py +104 -0
  120. toil/test/lib/test_misc.py +12 -5
  121. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  122. toil/test/mesos/helloWorld.py +7 -6
  123. toil/test/mesos/stress.py +25 -20
  124. toil/test/options/__init__.py +13 -0
  125. toil/test/options/options.py +42 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
  127. toil/test/provisioners/clusterScalerTest.py +440 -250
  128. toil/test/provisioners/clusterTest.py +166 -44
  129. toil/test/provisioners/gceProvisionerTest.py +174 -100
  130. toil/test/provisioners/provisionerTest.py +25 -13
  131. toil/test/provisioners/restartScript.py +5 -4
  132. toil/test/server/serverTest.py +188 -141
  133. toil/test/sort/restart_sort.py +137 -68
  134. toil/test/sort/sort.py +134 -66
  135. toil/test/sort/sortTest.py +91 -49
  136. toil/test/src/autoDeploymentTest.py +141 -101
  137. toil/test/src/busTest.py +20 -18
  138. toil/test/src/checkpointTest.py +8 -2
  139. toil/test/src/deferredFunctionTest.py +49 -35
  140. toil/test/src/dockerCheckTest.py +32 -24
  141. toil/test/src/environmentTest.py +135 -0
  142. toil/test/src/fileStoreTest.py +539 -272
  143. toil/test/src/helloWorldTest.py +7 -4
  144. toil/test/src/importExportFileTest.py +61 -31
  145. toil/test/src/jobDescriptionTest.py +46 -21
  146. toil/test/src/jobEncapsulationTest.py +2 -0
  147. toil/test/src/jobFileStoreTest.py +74 -50
  148. toil/test/src/jobServiceTest.py +187 -73
  149. toil/test/src/jobTest.py +121 -71
  150. toil/test/src/miscTests.py +19 -18
  151. toil/test/src/promisedRequirementTest.py +82 -36
  152. toil/test/src/promisesTest.py +7 -6
  153. toil/test/src/realtimeLoggerTest.py +10 -6
  154. toil/test/src/regularLogTest.py +71 -37
  155. toil/test/src/resourceTest.py +80 -49
  156. toil/test/src/restartDAGTest.py +36 -22
  157. toil/test/src/resumabilityTest.py +9 -2
  158. toil/test/src/retainTempDirTest.py +45 -14
  159. toil/test/src/systemTest.py +12 -8
  160. toil/test/src/threadingTest.py +44 -25
  161. toil/test/src/toilContextManagerTest.py +10 -7
  162. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  163. toil/test/src/workerTest.py +73 -23
  164. toil/test/utils/toilDebugTest.py +103 -33
  165. toil/test/utils/toilKillTest.py +4 -5
  166. toil/test/utils/utilsTest.py +245 -106
  167. toil/test/wdl/wdltoil_test.py +818 -149
  168. toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
  169. toil/toilState.py +120 -35
  170. toil/utils/toilConfig.py +13 -4
  171. toil/utils/toilDebugFile.py +44 -27
  172. toil/utils/toilDebugJob.py +214 -27
  173. toil/utils/toilDestroyCluster.py +11 -6
  174. toil/utils/toilKill.py +8 -3
  175. toil/utils/toilLaunchCluster.py +256 -140
  176. toil/utils/toilMain.py +37 -16
  177. toil/utils/toilRsyncCluster.py +32 -14
  178. toil/utils/toilSshCluster.py +49 -22
  179. toil/utils/toilStats.py +356 -273
  180. toil/utils/toilStatus.py +292 -139
  181. toil/utils/toilUpdateEC2Instances.py +3 -1
  182. toil/version.py +12 -12
  183. toil/wdl/utils.py +5 -5
  184. toil/wdl/wdltoil.py +3913 -1033
  185. toil/worker.py +367 -184
  186. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
  187. toil-8.0.0.dist-info/METADATA +173 -0
  188. toil-8.0.0.dist-info/RECORD +253 -0
  189. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  190. toil-6.1.0a1.dist-info/METADATA +0 -125
  191. toil-6.1.0a1.dist-info/RECORD +0 -237
  192. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  193. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
@@ -6,28 +6,29 @@ import time
6
6
  import traceback
7
7
  from functools import partial
8
8
 
9
- from toil.lib.threading import (LastProcessStandingArena,
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('Iteration %d', it)
20
+ log.info("Iteration %d", it)
21
21
 
22
22
  scope = self._createTempDir()
23
- mutex = '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('Iteration %d', it)
44
+ log.info("Iteration %d", it)
44
45
 
45
46
  scope = self._createTempDir()
46
- arena_name = 'thunderdome'
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('precious'), f"File {filename} still exists"
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, 'potato')
74
+ potato = os.path.join(scope, "potato")
70
75
 
71
76
  with global_mutex(scope, mutex):
72
- log.info('PID %d = num %d running', os.getpid(), number)
73
- assert not os.path.exists(potato), "We see someone else holding the potato file"
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, 'w') as out_stream:
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(number), f"We are {number} but {seen} stole our potato!"
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('PID %d = num %d dropped potato', os.getpid(), number)
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('PID %d = num %d entered arena', os.getpid(), number)
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, 'precious' + str(number))
113
+ my_precious = os.path.join(scope, "precious" + str(number))
104
114
 
105
115
  # Put our name there
106
- with open(my_precious, 'w') as out_stream:
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(my_precious), f"Precious file {my_precious} has been stolen!"
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(number), f"We are {number} but saw {seen} in our precious file!"
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('PID %d = num %d is last standing', os.getpid(), number)
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('precious'):
126
- log.info('PID %d = num %d cleaning up %s', os.getpid(), number, filename)
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('PID %d = num %d left arena', os.getpid(), number)
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 = 'INFO'
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 = 'INFO'
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("we died after workflow completion but before our export finished")
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, 'file://' + self.exportPath)
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='1M')
67
+ Job.__init__(self, memory=100000, disk="1M")
65
68
 
66
69
  def run(self, fileStore):
67
- fileID = self.addChildJobFn(childFn, memory='1M', disk='1M').rv()
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, 'LocalCopy'])
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('.')[-1]
56
- self.assertTrue(testMethodName.endswith('FromMain'))
57
- subprocess.check_call([sys.executable, '-m', self.__module__, testMethodName[:-8]])
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(jobFunction, self.level + 1, Foo(), cores=1, memory="1M",
70
- disk="300M")
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):
@@ -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 = 'file:%s' % path
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
- def createTestJobDesc(memory, cores, disk, preemptible, checkpoint):
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 = 'job%d' % self.jobNumber
51
+ name = "job%d" % self.jobNumber
41
52
  self.jobNumber += 1
42
53
 
43
54
  descClass = CheckpointJobDescription if checkpoint else JobDescription
44
- jobDesc = descClass(requirements={'memory': memory, 'cores': cores, 'disk': disk, 'preemptible': preemptible}, jobName=name)
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 ['addChild', 'addFollowOn']:
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, True, False)
57
- jobDesc2 = createTestJobDesc(1, 2, 3, True, False)
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(jobDesc2.jobStoreID, chainable.jobStoreID)
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, False)
65
- jobDesc2 = createTestJobDesc(1, 2, 3, True, 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(None, nextChainable(jobDesc1, self.jobStore, self.config))
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, True, False)
71
- self.assertEqual(None, nextChainable(jobDesc1, self.jobStore, self.config))
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, True, False)
75
- jobDesc2 = createTestJobDesc(1, 2, 3, True, False)
76
- jobDesc3 = createTestJobDesc(1, 2, 3, True, False)
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(None, nextChainable(jobDesc1, self.jobStore, self.config))
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
- reqs = {'memory': 1, 'cores': 2, 'disk': 3, 'preemptible': True, 'checkpoint': False}
83
- for increased_attribute in ('memory', 'cores', 'disk'):
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(None, nextChainable(jobDesc1, self.jobStore, self.config))
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, False)
92
- jobDesc2 = createTestJobDesc(1, 2, 3, False, True)
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(None, nextChainable(jobDesc1, self.jobStore, self.config))
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)
@@ -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-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)
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 = "kind-explode/" + os.listdir(os.path.join(job_store, "jobs/kind-explode"))[0]
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
- "debug-job",
173
- "--logDebug",
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('utf-8')
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
- "debug-job",
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")
@@ -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 (NoSuchFileException,
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('Running workflow: %s', ' '.join(run_cmd))
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('Waiting for kill flag...')
77
+ logger.info("Waiting for kill flag...")
79
78
  except (NoSuchJobStoreException, NoSuchFileException):
80
- logger.info('Waiting for job store to be openable...')
79
+ logger.info("Waiting for job store to be openable...")
81
80
  time.sleep(2)
82
81
 
83
82
  # run toil kill