toil 7.0.0__py3-none-any.whl → 8.1.0b1__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 (197) hide show
  1. toil/__init__.py +124 -86
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +137 -77
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +211 -101
  5. toil/batchSystems/awsBatch.py +237 -128
  6. toil/batchSystems/cleanup_support.py +22 -16
  7. toil/batchSystems/contained_executor.py +30 -26
  8. toil/batchSystems/gridengine.py +85 -49
  9. toil/batchSystems/htcondor.py +164 -87
  10. toil/batchSystems/kubernetes.py +622 -386
  11. toil/batchSystems/local_support.py +17 -12
  12. toil/batchSystems/lsf.py +132 -79
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +288 -149
  16. toil/batchSystems/mesos/executor.py +77 -49
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +39 -29
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +293 -123
  21. toil/batchSystems/slurm.py +651 -155
  22. toil/batchSystems/torque.py +46 -32
  23. toil/bus.py +141 -73
  24. toil/common.py +784 -397
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1137 -534
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +62 -41
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +88 -57
  32. toil/fileStores/cachingFileStore.py +711 -247
  33. toil/fileStores/nonCachingFileStore.py +113 -75
  34. toil/job.py +1031 -349
  35. toil/jobStores/abstractJobStore.py +387 -243
  36. toil/jobStores/aws/jobStore.py +772 -412
  37. toil/jobStores/aws/utils.py +161 -109
  38. toil/jobStores/conftest.py +1 -0
  39. toil/jobStores/fileJobStore.py +289 -151
  40. toil/jobStores/googleJobStore.py +137 -70
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +614 -269
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +55 -28
  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 +204 -58
  49. toil/lib/aws/utils.py +290 -213
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +83 -49
  53. toil/lib/docker.py +131 -103
  54. toil/lib/dockstore.py +379 -0
  55. toil/lib/ec2.py +322 -209
  56. toil/lib/ec2nodes.py +174 -105
  57. toil/lib/encryption/_dummy.py +5 -3
  58. toil/lib/encryption/_nacl.py +10 -6
  59. toil/lib/encryption/conftest.py +1 -0
  60. toil/lib/exceptions.py +26 -7
  61. toil/lib/expando.py +4 -2
  62. toil/lib/ftp_utils.py +217 -0
  63. toil/lib/generatedEC2Lists.py +127 -19
  64. toil/lib/history.py +1271 -0
  65. toil/lib/history_submission.py +681 -0
  66. toil/lib/humanize.py +6 -2
  67. toil/lib/io.py +121 -12
  68. toil/lib/iterables.py +4 -2
  69. toil/lib/memoize.py +12 -8
  70. toil/lib/misc.py +83 -18
  71. toil/lib/objects.py +2 -2
  72. toil/lib/resources.py +19 -7
  73. toil/lib/retry.py +125 -87
  74. toil/lib/threading.py +282 -80
  75. toil/lib/throttle.py +15 -14
  76. toil/lib/trs.py +390 -0
  77. toil/lib/web.py +38 -0
  78. toil/options/common.py +850 -402
  79. toil/options/cwl.py +185 -90
  80. toil/options/runner.py +50 -0
  81. toil/options/wdl.py +70 -19
  82. toil/provisioners/__init__.py +111 -46
  83. toil/provisioners/abstractProvisioner.py +322 -157
  84. toil/provisioners/aws/__init__.py +62 -30
  85. toil/provisioners/aws/awsProvisioner.py +980 -627
  86. toil/provisioners/clusterScaler.py +541 -279
  87. toil/provisioners/gceProvisioner.py +283 -180
  88. toil/provisioners/node.py +147 -79
  89. toil/realtimeLogger.py +34 -22
  90. toil/resource.py +137 -75
  91. toil/server/app.py +127 -61
  92. toil/server/celery_app.py +3 -1
  93. toil/server/cli/wes_cwl_runner.py +84 -55
  94. toil/server/utils.py +56 -31
  95. toil/server/wes/abstract_backend.py +64 -26
  96. toil/server/wes/amazon_wes_utils.py +21 -15
  97. toil/server/wes/tasks.py +121 -63
  98. toil/server/wes/toil_backend.py +142 -107
  99. toil/server/wsgi_app.py +4 -3
  100. toil/serviceManager.py +58 -22
  101. toil/statsAndLogging.py +183 -65
  102. toil/test/__init__.py +263 -179
  103. toil/test/batchSystems/batchSystemTest.py +438 -195
  104. toil/test/batchSystems/batch_system_plugin_test.py +18 -7
  105. toil/test/batchSystems/test_gridengine.py +173 -0
  106. toil/test/batchSystems/test_lsf_helper.py +67 -58
  107. toil/test/batchSystems/test_slurm.py +265 -49
  108. toil/test/cactus/test_cactus_integration.py +20 -22
  109. toil/test/cwl/conftest.py +39 -0
  110. toil/test/cwl/cwlTest.py +375 -72
  111. toil/test/cwl/measure_default_memory.cwl +12 -0
  112. toil/test/cwl/not_run_required_input.cwl +29 -0
  113. toil/test/cwl/optional-file.cwl +18 -0
  114. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  115. toil/test/docs/scriptsTest.py +60 -34
  116. toil/test/jobStores/jobStoreTest.py +412 -235
  117. toil/test/lib/aws/test_iam.py +116 -48
  118. toil/test/lib/aws/test_s3.py +16 -9
  119. toil/test/lib/aws/test_utils.py +5 -6
  120. toil/test/lib/dockerTest.py +118 -141
  121. toil/test/lib/test_conversions.py +113 -115
  122. toil/test/lib/test_ec2.py +57 -49
  123. toil/test/lib/test_history.py +212 -0
  124. toil/test/lib/test_misc.py +12 -5
  125. toil/test/lib/test_trs.py +161 -0
  126. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  127. toil/test/mesos/helloWorld.py +7 -6
  128. toil/test/mesos/stress.py +25 -20
  129. toil/test/options/options.py +7 -2
  130. toil/test/provisioners/aws/awsProvisionerTest.py +293 -140
  131. toil/test/provisioners/clusterScalerTest.py +440 -250
  132. toil/test/provisioners/clusterTest.py +81 -42
  133. toil/test/provisioners/gceProvisionerTest.py +174 -100
  134. toil/test/provisioners/provisionerTest.py +25 -13
  135. toil/test/provisioners/restartScript.py +5 -4
  136. toil/test/server/serverTest.py +188 -141
  137. toil/test/sort/restart_sort.py +137 -68
  138. toil/test/sort/sort.py +134 -66
  139. toil/test/sort/sortTest.py +91 -49
  140. toil/test/src/autoDeploymentTest.py +140 -100
  141. toil/test/src/busTest.py +20 -18
  142. toil/test/src/checkpointTest.py +8 -2
  143. toil/test/src/deferredFunctionTest.py +49 -35
  144. toil/test/src/dockerCheckTest.py +33 -26
  145. toil/test/src/environmentTest.py +20 -10
  146. toil/test/src/fileStoreTest.py +538 -271
  147. toil/test/src/helloWorldTest.py +7 -4
  148. toil/test/src/importExportFileTest.py +61 -31
  149. toil/test/src/jobDescriptionTest.py +32 -17
  150. toil/test/src/jobEncapsulationTest.py +2 -0
  151. toil/test/src/jobFileStoreTest.py +74 -50
  152. toil/test/src/jobServiceTest.py +187 -73
  153. toil/test/src/jobTest.py +120 -70
  154. toil/test/src/miscTests.py +19 -18
  155. toil/test/src/promisedRequirementTest.py +82 -36
  156. toil/test/src/promisesTest.py +7 -6
  157. toil/test/src/realtimeLoggerTest.py +6 -6
  158. toil/test/src/regularLogTest.py +71 -37
  159. toil/test/src/resourceTest.py +80 -49
  160. toil/test/src/restartDAGTest.py +36 -22
  161. toil/test/src/resumabilityTest.py +9 -2
  162. toil/test/src/retainTempDirTest.py +45 -14
  163. toil/test/src/systemTest.py +12 -8
  164. toil/test/src/threadingTest.py +44 -25
  165. toil/test/src/toilContextManagerTest.py +10 -7
  166. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  167. toil/test/src/workerTest.py +33 -16
  168. toil/test/utils/toilDebugTest.py +70 -58
  169. toil/test/utils/toilKillTest.py +4 -5
  170. toil/test/utils/utilsTest.py +239 -102
  171. toil/test/wdl/wdltoil_test.py +789 -148
  172. toil/test/wdl/wdltoil_test_kubernetes.py +37 -23
  173. toil/toilState.py +52 -26
  174. toil/utils/toilConfig.py +13 -4
  175. toil/utils/toilDebugFile.py +44 -27
  176. toil/utils/toilDebugJob.py +85 -25
  177. toil/utils/toilDestroyCluster.py +11 -6
  178. toil/utils/toilKill.py +8 -3
  179. toil/utils/toilLaunchCluster.py +251 -145
  180. toil/utils/toilMain.py +37 -16
  181. toil/utils/toilRsyncCluster.py +27 -14
  182. toil/utils/toilSshCluster.py +45 -22
  183. toil/utils/toilStats.py +75 -36
  184. toil/utils/toilStatus.py +226 -119
  185. toil/utils/toilUpdateEC2Instances.py +3 -1
  186. toil/version.py +6 -6
  187. toil/wdl/utils.py +5 -5
  188. toil/wdl/wdltoil.py +3528 -1053
  189. toil/worker.py +370 -149
  190. toil-8.1.0b1.dist-info/METADATA +178 -0
  191. toil-8.1.0b1.dist-info/RECORD +259 -0
  192. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/WHEEL +1 -1
  193. toil-7.0.0.dist-info/METADATA +0 -158
  194. toil-7.0.0.dist-info/RECORD +0 -244
  195. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/LICENSE +0 -0
  196. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/entry_points.txt +0 -0
  197. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
  import logging
15
15
  import os
16
+ import re
16
17
  import shutil
17
18
  import subprocess
18
19
  import sys
@@ -22,7 +23,7 @@ from unittest.mock import patch
22
23
 
23
24
  import pytest
24
25
 
25
- pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) # noqa
26
+ pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) # noqa
26
27
  sys.path.insert(0, pkg_root) # noqa
27
28
 
28
29
  import toil
@@ -30,14 +31,16 @@ from toil import resolveEntryPoint
30
31
  from toil.common import Config, Toil
31
32
  from toil.job import Job
32
33
  from toil.lib.bioio import system
33
- from toil.test import (ToilTest,
34
- get_temp_file,
35
- integrative,
36
- needs_aws_ec2,
37
- needs_cwl,
38
- needs_docker,
39
- needs_rsync3,
40
- slow)
34
+ from toil.test import (
35
+ ToilTest,
36
+ get_temp_file,
37
+ integrative,
38
+ needs_aws_ec2,
39
+ needs_cwl,
40
+ needs_docker,
41
+ needs_rsync3,
42
+ slow,
43
+ )
41
44
  from toil.test.sort.sortTest import makeFileToSort
42
45
  from toil.utils.toilStats import get_stats, process_data
43
46
  from toil.utils.toilStatus import ToilStatus
@@ -57,7 +60,7 @@ class UtilsTest(ToilTest):
57
60
  self.tempDir = self._createTempDir()
58
61
  self.tempFile = get_temp_file(rootDir=self.tempDir)
59
62
  self.outputFile = get_temp_file(rootDir=self.tempDir)
60
- self.outputFile = 'someSortedStuff.txt'
63
+ self.outputFile = "someSortedStuff.txt"
61
64
  self.toilDir = os.path.join(self.tempDir, "jobstore")
62
65
  self.assertFalse(os.path.exists(self.toilDir))
63
66
  self.lines = 1000
@@ -71,19 +74,19 @@ class UtilsTest(ToilTest):
71
74
 
72
75
  self.sort_workflow_cmd = [
73
76
  python,
74
- '-m',
75
- 'toil.test.sort.sort',
76
- f'file:{self.toilDir}',
77
- f'--fileToSort={self.tempFile}',
78
- f'--outputFile={self.outputFile}',
79
- '--clean=never',
77
+ "-m",
78
+ "toil.test.sort.sort",
79
+ f"file:{self.toilDir}",
80
+ f"--fileToSort={self.tempFile}",
81
+ f"--outputFile={self.outputFile}",
82
+ "--clean=never",
80
83
  ]
81
84
 
82
85
  self.restart_sort_workflow_cmd = [
83
86
  python,
84
- '-m',
85
- 'toil.test.sort.restart_sort',
86
- f'file:{self.toilDir}'
87
+ "-m",
88
+ "toil.test.sort.restart_sort",
89
+ f"file:{self.toilDir}",
87
90
  ]
88
91
 
89
92
  def tearDown(self):
@@ -92,7 +95,11 @@ class UtilsTest(ToilTest):
92
95
  if os.path.exists(self.toilDir):
93
96
  shutil.rmtree(self.toilDir)
94
97
 
95
- for f in [self.tempFile, self.outputFile, os.path.join(self.tempDir, "output.txt")]:
98
+ for f in [
99
+ self.tempFile,
100
+ self.outputFile,
101
+ os.path.join(self.tempDir, "output.txt"),
102
+ ]:
96
103
  if os.path.exists(f):
97
104
  os.remove(f)
98
105
 
@@ -100,26 +107,26 @@ class UtilsTest(ToilTest):
100
107
 
101
108
  @property
102
109
  def toilMain(self):
103
- return resolveEntryPoint('toil')
110
+ return resolveEntryPoint("toil")
104
111
 
105
112
  @property
106
113
  def cleanCommand(self):
107
- return [self.toilMain, 'clean', self.toilDir]
114
+ return [self.toilMain, "clean", self.toilDir]
108
115
 
109
116
  @property
110
117
  def statsCommand(self):
111
- return [self.toilMain, 'stats', self.toilDir, '--pretty']
118
+ return [self.toilMain, "stats", self.toilDir, "--pretty"]
112
119
 
113
120
  def statusCommand(self, failIfNotComplete=False):
114
- commandTokens = [self.toilMain, 'status', self.toilDir]
121
+ commandTokens = [self.toilMain, "status", self.toilDir]
115
122
  if failIfNotComplete:
116
- commandTokens.append('--failIfNotComplete')
123
+ commandTokens.append("--failIfNotComplete")
117
124
  return commandTokens
118
125
 
119
126
  def test_config_functionality(self):
120
127
  """Ensure that creating and reading back the config file works"""
121
128
  config_file = os.path.abspath("config.yaml")
122
- config_command = [self.toilMain, 'config', config_file]
129
+ config_command = [self.toilMain, "config", config_file]
123
130
  # make sure the command `toil config file_path` works
124
131
  try:
125
132
  subprocess.check_call(config_command)
@@ -130,12 +137,18 @@ class UtilsTest(ToilTest):
130
137
  # make sure that toil can read from the generated config file
131
138
  try:
132
139
  parser.parse_args(["random_jobstore", "--config", config_file])
140
+ with open(config_file) as cm:
141
+ payload = cm.read()
142
+ expected = "workDir batchSystem symlinkImports defaultMemory retryCount"
143
+ assert all(
144
+ re.search(rf"^#*{ param }:", payload, re.MULTILINE)
145
+ for param in expected.split(" ")
146
+ ), f"Generated config contains { expected }"
133
147
  except SystemExit:
134
148
  self.fail("Failed to parse the default generated config file!")
135
149
  finally:
136
150
  os.remove(config_file)
137
151
 
138
-
139
152
  @needs_rsync3
140
153
  @pytest.mark.timeout(1200)
141
154
  @needs_aws_ec2
@@ -156,30 +169,63 @@ class UtilsTest(ToilTest):
156
169
  :return:
157
170
  """
158
171
  # TODO: Run these for the other clouds.
159
- clusterName = f'cluster-utils-test{uuid.uuid4()}'
160
- keyName = os.getenv('TOIL_AWS_KEYNAME').strip() or 'id_rsa'
161
- expected_owner = os.getenv('TOIL_OWNER_TAG') or keyName
172
+ clusterName = f"cluster-utils-test{uuid.uuid4()}"
173
+ keyName = os.getenv("TOIL_AWS_KEYNAME").strip() or "id_rsa"
174
+ expected_owner = os.getenv("TOIL_OWNER_TAG") or keyName
162
175
 
163
176
  try:
164
177
  from toil.provisioners.aws.awsProvisioner import AWSProvisioner
178
+
165
179
  aws_provisioner = AWSProvisioner.__module__
166
180
  logger.debug(f"Found AWSProvisioner: {aws_provisioner}.")
167
181
 
168
182
  # launch master with an assortment of custom tags
169
- system([self.toilMain, 'launch-cluster', '--clusterType', 'mesos',
170
- '-t', 'key1=value1', '-t', 'key2=value2', '--tag', 'key3=value3',
171
- '--leaderNodeType=t2.medium', '--keyPairName=' + keyName, clusterName,
172
- '--provisioner=aws', '--zone=us-west-2a', '--logLevel=DEBUG'])
173
-
174
- cluster = toil.provisioners.cluster_factory(provisioner='aws', zone='us-west-2a', clusterName=clusterName)
183
+ system(
184
+ [
185
+ self.toilMain,
186
+ "launch-cluster",
187
+ "--clusterType",
188
+ "mesos",
189
+ "-t",
190
+ "key1=value1",
191
+ "-t",
192
+ "key2=value2",
193
+ "--tag",
194
+ "key3=value3",
195
+ "--leaderNodeType=t2.medium",
196
+ "--keyPairName=" + keyName,
197
+ clusterName,
198
+ "--provisioner=aws",
199
+ "--zone=us-west-2a",
200
+ "--logLevel=DEBUG",
201
+ ]
202
+ )
203
+
204
+ cluster = toil.provisioners.cluster_factory(
205
+ provisioner="aws", zone="us-west-2a", clusterName=clusterName
206
+ )
175
207
  leader = cluster.getLeader()
176
208
 
177
209
  # check that the leader carries the appropriate tags
178
- tags = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3', 'Name': clusterName, 'Owner': expected_owner}
210
+ tags = {
211
+ "key1": "value1",
212
+ "key2": "value2",
213
+ "key3": "value3",
214
+ "Name": clusterName,
215
+ "Owner": expected_owner,
216
+ }
179
217
  for key in tags:
180
218
  self.assertEqual(leader.tags.get(key), tags[key])
181
219
  finally:
182
- system([self.toilMain, 'destroy-cluster', '--zone=us-west-2a', '--provisioner=aws', clusterName])
220
+ system(
221
+ [
222
+ self.toilMain,
223
+ "destroy-cluster",
224
+ "--zone=us-west-2a",
225
+ "--provisioner=aws",
226
+ clusterName,
227
+ ]
228
+ )
183
229
 
184
230
  @slow
185
231
  def testUtilsSort(self):
@@ -188,19 +234,27 @@ class UtilsTest(ToilTest):
188
234
  sort example with the --restart flag.
189
235
  """
190
236
  # Get the sort command to run
191
- toilCommand = [sys.executable,
192
- '-m', toil.test.sort.sort.__name__,
193
- self.toilDir,
194
- '--logLevel=DEBUG',
195
- '--fileToSort', self.tempFile,
196
- '--outputFile', self.outputFile,
197
- '--N', str(self.N),
198
- '--stats',
199
- '--retryCount=2',
200
- '--badWorker=0.5',
201
- '--badWorkerFailInterval=0.05']
237
+ toilCommand = [
238
+ sys.executable,
239
+ "-m",
240
+ toil.test.sort.sort.__name__,
241
+ self.toilDir,
242
+ "--logLevel=DEBUG",
243
+ "--fileToSort",
244
+ self.tempFile,
245
+ "--outputFile",
246
+ self.outputFile,
247
+ "--N",
248
+ str(self.N),
249
+ "--stats",
250
+ "--retryCount=2",
251
+ "--badWorker=0.5",
252
+ "--badWorkerFailInterval=0.05",
253
+ ]
202
254
  # Try restarting it to check that a JobStoreException is thrown
203
- self.assertRaises(subprocess.CalledProcessError, system, toilCommand + ['--restart'])
255
+ self.assertRaises(
256
+ subprocess.CalledProcessError, system, toilCommand + ["--restart"]
257
+ )
204
258
  # Check that trying to run it in restart mode does not create the jobStore
205
259
  self.assertFalse(os.path.exists(self.toilDir))
206
260
 
@@ -209,9 +263,15 @@ class UtilsTest(ToilTest):
209
263
  try:
210
264
  system(toilCommand)
211
265
  finished = True
212
- except subprocess.CalledProcessError: # This happens when the script fails due to having unfinished jobs
266
+ except (
267
+ subprocess.CalledProcessError
268
+ ): # This happens when the script fails due to having unfinished jobs
213
269
  system(self.statusCommand())
214
- self.assertRaises(subprocess.CalledProcessError, system, self.statusCommand(failIfNotComplete=True))
270
+ self.assertRaises(
271
+ subprocess.CalledProcessError,
272
+ system,
273
+ self.statusCommand(failIfNotComplete=True),
274
+ )
215
275
  finished = False
216
276
  self.assertTrue(os.path.exists(self.toilDir))
217
277
 
@@ -222,11 +282,17 @@ class UtilsTest(ToilTest):
222
282
  totalTrys = 1
223
283
  while not finished:
224
284
  try:
225
- system(toilCommand + ['--restart'])
285
+ system(toilCommand + ["--restart"])
226
286
  finished = True
227
- except subprocess.CalledProcessError: # This happens when the script fails due to having unfinished jobs
287
+ except (
288
+ subprocess.CalledProcessError
289
+ ): # This happens when the script fails due to having unfinished jobs
228
290
  system(self.statusCommand())
229
- self.assertRaises(subprocess.CalledProcessError, system, self.statusCommand(failIfNotComplete=True))
291
+ self.assertRaises(
292
+ subprocess.CalledProcessError,
293
+ system,
294
+ self.statusCommand(failIfNotComplete=True),
295
+ )
230
296
  if totalTrys > 16:
231
297
  self.fail() # Exceeded a reasonable number of restarts
232
298
  totalTrys += 1
@@ -254,17 +320,23 @@ class UtilsTest(ToilTest):
254
320
  Tests the stats commands on a complete run of the stats test.
255
321
  """
256
322
  # Get the sort command to run
257
- toilCommand = [sys.executable,
258
- '-m', toil.test.sort.sort.__name__,
259
- self.toilDir,
260
- '--logLevel=DEBUG',
261
- '--fileToSort', self.tempFile,
262
- '--outputFile', self.outputFile,
263
- '--N', str(self.N),
264
- '--stats',
265
- '--retryCount=99',
266
- '--badWorker=0.5',
267
- '--badWorkerFailInterval=0.01']
323
+ toilCommand = [
324
+ sys.executable,
325
+ "-m",
326
+ toil.test.sort.sort.__name__,
327
+ self.toilDir,
328
+ "--logLevel=DEBUG",
329
+ "--fileToSort",
330
+ self.tempFile,
331
+ "--outputFile",
332
+ self.outputFile,
333
+ "--N",
334
+ str(self.N),
335
+ "--stats",
336
+ "--retryCount=99",
337
+ "--badWorker=0.5",
338
+ "--badWorkerFailInterval=0.01",
339
+ ]
268
340
 
269
341
  # Run the script for the first time
270
342
  system(toilCommand)
@@ -283,8 +355,8 @@ class UtilsTest(ToilTest):
283
355
 
284
356
  def testUnicodeSupport(self):
285
357
  options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
286
- options.clean = 'always'
287
- options.logLevel = 'debug'
358
+ options.clean = "always"
359
+ options.logLevel = "debug"
288
360
  Job.Runner.startToil(Job.wrapFn(printUnicodeCharacter), options)
289
361
 
290
362
  @slow
@@ -293,7 +365,7 @@ class UtilsTest(ToilTest):
293
365
  Tests case where multiple jobs are run on 1 worker to ensure that all jobs report back their data
294
366
  """
295
367
  options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
296
- options.clean = 'never'
368
+ options.clean = "never"
297
369
  options.stats = True
298
370
  Job.Runner.startToil(RunTwoJobsPerWorker(), options)
299
371
  config = Config()
@@ -301,28 +373,52 @@ class UtilsTest(ToilTest):
301
373
  jobStore = Toil.resumeJobStore(config.jobStore)
302
374
  stats = get_stats(jobStore)
303
375
  collatedStats = process_data(jobStore.config, stats)
304
- self.assertTrue(len(collatedStats.job_types) == 2, "Some jobs are not represented in the stats.")
305
-
306
- def check_status(self, status, status_fn, seconds=20):
307
- i = 0.0
308
- while status_fn(self.toilDir) != status:
376
+ self.assertTrue(
377
+ len(collatedStats.job_types) == 2,
378
+ "Some jobs are not represented in the stats.",
379
+ )
380
+
381
+ def check_status(self, status, status_fn, process=None, seconds=20):
382
+ time_elapsed = 0.0
383
+ has_stopped = process.poll() is not None if process else False
384
+ current_status = status_fn(self.toilDir)
385
+ while current_status != status:
386
+ if has_stopped:
387
+ # If the process has stopped and the stratus is wrong, it will never be right.
388
+ self.assertEqual(
389
+ current_status,
390
+ status,
391
+ f"Process returned {process.returncode} without status reaching {status}; stuck at {current_status}",
392
+ )
393
+ logger.debug(
394
+ "Workflow is %s; waiting for %s (%s/%s elapsed)",
395
+ current_status,
396
+ status,
397
+ time_elapsed,
398
+ seconds
399
+ )
309
400
  time.sleep(0.5)
310
- i += 0.5
311
- if i > seconds:
312
- s = status_fn(self.toilDir)
313
- self.assertEqual(s, status, f'Waited {seconds} seconds without status reaching {status}; stuck at {s}')
401
+ time_elapsed += 0.5
402
+ has_stopped = process.poll() is not None if process else False
403
+ current_status = status_fn(self.toilDir)
404
+ if time_elapsed > seconds:
405
+ self.assertEqual(
406
+ current_status,
407
+ status,
408
+ f"Waited {seconds} seconds without status reaching {status}; stuck at {current_status}",
409
+ )
314
410
 
315
411
  def testGetPIDStatus(self):
316
412
  """Test that ToilStatus.getPIDStatus() behaves as expected."""
317
413
  wf = subprocess.Popen(self.sort_workflow_cmd)
318
- self.check_status('RUNNING', status_fn=ToilStatus.getPIDStatus, seconds=60)
414
+ self.check_status("RUNNING", status_fn=ToilStatus.getPIDStatus, process=wf, seconds=60)
319
415
  wf.wait()
320
- self.check_status('COMPLETED', status_fn=ToilStatus.getPIDStatus, seconds=60)
416
+ self.check_status("COMPLETED", status_fn=ToilStatus.getPIDStatus, process=wf, seconds=60)
321
417
 
322
418
  # TODO: we need to reach into the FileJobStore's files and delete this
323
419
  # shared file. We assume we know its internal layout.
324
- os.remove(os.path.join(self.toilDir, 'files/shared/pid.log'))
325
- self.check_status('QUEUED', status_fn=ToilStatus.getPIDStatus, seconds=60)
420
+ os.remove(os.path.join(self.toilDir, "files/shared/pid.log"))
421
+ self.check_status("QUEUED", status_fn=ToilStatus.getPIDStatus, process=wf, seconds=60)
326
422
 
327
423
  def testGetStatusFailedToilWF(self):
328
424
  """
@@ -331,41 +427,71 @@ class UtilsTest(ToilTest):
331
427
  opportunity to test the 'RUNNING' functionality of getStatus().
332
428
  """
333
429
  # --badWorker is set to force failure.
334
- wf = subprocess.Popen(self.sort_workflow_cmd + ['--badWorker=1'])
335
- self.check_status('RUNNING', status_fn=ToilStatus.getStatus, seconds=60)
430
+ wf = subprocess.Popen(self.sort_workflow_cmd + ["--badWorker=1"])
431
+ self.check_status("RUNNING", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
336
432
  wf.wait()
337
- self.check_status('ERROR', status_fn=ToilStatus.getStatus, seconds=60)
433
+ self.check_status("ERROR", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
338
434
 
339
435
  @needs_cwl
340
436
  @needs_docker
341
437
  def testGetStatusFailedCWLWF(self):
342
438
  """Test that ToilStatus.getStatus() behaves as expected with a failing CWL workflow."""
343
439
  # --badWorker is set to force failure.
344
- cmd = ['toil-cwl-runner', '--jobStore', self.toilDir, '--clean=never', '--badWorker=1',
345
- 'src/toil/test/cwl/sorttool.cwl', '--reverse', '--input', 'src/toil/test/cwl/whale.txt', f'--outdir={self.tempDir}']
440
+ cmd = [
441
+ "toil-cwl-runner",
442
+ "--logDebug",
443
+ "--jobStore",
444
+ self.toilDir,
445
+ "--clean=never",
446
+ "--badWorker=1",
447
+ "src/toil/test/cwl/sorttool.cwl",
448
+ "--reverse",
449
+ "--input",
450
+ "src/toil/test/cwl/whale.txt",
451
+ f"--outdir={self.tempDir}",
452
+ ]
453
+ logger.info("Run command: %s", " ".join(cmd))
346
454
  wf = subprocess.Popen(cmd)
347
- self.check_status('RUNNING', status_fn=ToilStatus.getStatus, seconds=60)
455
+ self.check_status("RUNNING", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
348
456
  wf.wait()
349
- self.check_status('ERROR', status_fn=ToilStatus.getStatus, seconds=60)
457
+ self.check_status("ERROR", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
350
458
 
351
459
  @needs_cwl
352
460
  @needs_docker
353
461
  def testGetStatusSuccessfulCWLWF(self):
354
462
  """Test that ToilStatus.getStatus() behaves as expected with a successful CWL workflow."""
355
- cmd = ['toil-cwl-runner', '--jobStore', self.toilDir, '--clean=never',
356
- 'src/toil/test/cwl/sorttool.cwl', '--reverse', '--input', 'src/toil/test/cwl/whale.txt', f'--outdir={self.tempDir}']
463
+ cmd = [
464
+ "toil-cwl-runner",
465
+ "--jobStore",
466
+ self.toilDir,
467
+ "--clean=never",
468
+ "src/toil/test/cwl/sorttool.cwl",
469
+ "--reverse",
470
+ "--input",
471
+ "src/toil/test/cwl/whale.txt",
472
+ f"--outdir={self.tempDir}",
473
+ ]
357
474
  wf = subprocess.Popen(cmd)
358
- self.check_status('RUNNING', status_fn=ToilStatus.getStatus, seconds=60)
475
+ self.check_status("RUNNING", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
359
476
  wf.wait()
360
- self.check_status('COMPLETED', status_fn=ToilStatus.getStatus, seconds=60)
477
+ self.check_status("COMPLETED", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
361
478
 
362
479
  @needs_cwl
363
- @patch('builtins.print')
480
+ @patch("builtins.print")
364
481
  def testPrintJobLog(self, mock_print):
365
482
  """Test that ToilStatus.printJobLog() reads the log from a failed command without error."""
366
483
  # Run a workflow that will always fail
367
- cmd = ['toil-cwl-runner', '--jobStore', self.toilDir, '--clean=never',
368
- 'src/toil/test/cwl/alwaysfails.cwl', '--message', 'Testing']
484
+ cmd = [
485
+ "toil-cwl-runner",
486
+ "--logDebug",
487
+ "--jobStore",
488
+ self.toilDir,
489
+ "--clean=never",
490
+ "src/toil/test/cwl/alwaysfails.cwl",
491
+ "--message",
492
+ "Testing",
493
+ ]
494
+ logger.info("Run command: %s", " ".join(cmd))
369
495
  wf = subprocess.Popen(cmd)
370
496
  wf.wait()
371
497
  # print log and check output
@@ -374,7 +500,7 @@ class UtilsTest(ToilTest):
374
500
 
375
501
  # Make sure it printed some kind of complaint about the missing command.
376
502
  args, kwargs = mock_print.call_args
377
- self.assertIn('invalidcommand', args[0])
503
+ self.assertIn("invalidcommand", args[0])
378
504
 
379
505
  @pytest.mark.timeout(1200)
380
506
  def testRestartAttribute(self):
@@ -384,17 +510,27 @@ class UtilsTest(ToilTest):
384
510
  In this case, the job store should not be destroyed until restart() is called.
385
511
  """
386
512
  # Run a workflow that will always fail
387
- cmd = self.restart_sort_workflow_cmd + ['--badWorker=1', '--logDebug']
513
+ cmd = self.restart_sort_workflow_cmd + ["--badWorker=1", "--logDebug"]
388
514
  subprocess.run(cmd)
389
515
 
390
- restart_cmd = self.restart_sort_workflow_cmd + ['--badWorker=0', '--logDebug', '--restart']
516
+ restart_cmd = self.restart_sort_workflow_cmd + [
517
+ "--badWorker=0",
518
+ "--logDebug",
519
+ "--restart",
520
+ ]
391
521
  subprocess.run(restart_cmd)
392
522
 
393
523
  # Check the job store exists after restart attempt
394
524
  self.assertTrue(os.path.exists(self.toilDir))
395
525
 
396
- successful_cmd = [python, '-m', 'toil.test.sort.sort', '--logDebug', 'file:' + self.toilDir,
397
- '--restart']
526
+ successful_cmd = [
527
+ python,
528
+ "-m",
529
+ "toil.test.sort.sort",
530
+ "--logDebug",
531
+ "file:" + self.toilDir,
532
+ "--restart",
533
+ ]
398
534
  subprocess.run(successful_cmd)
399
535
 
400
536
  # Check the job store is destroyed after calling restart()
@@ -405,13 +541,14 @@ def printUnicodeCharacter():
405
541
  # We want to get a unicode character to stdout but we can't print it directly because of
406
542
  # Python encoding issues. To work around this we print in a separate Python process. See
407
543
  # http://stackoverflow.com/questions/492483/setting-the-correct-encoding-when-piping-stdout-in-python
408
- subprocess.check_call([sys.executable, '-c', "print('\\xc3\\xbc')"])
544
+ subprocess.check_call([sys.executable, "-c", "print('\\xc3\\xbc')"])
409
545
 
410
546
 
411
547
  class RunTwoJobsPerWorker(Job):
412
548
  """
413
549
  Runs child job with same resources as self in an attempt to chain the jobs on the same worker
414
550
  """
551
+
415
552
  def __init__(self):
416
553
  Job.__init__(self)
417
554