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
@@ -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,16 +31,18 @@ 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
- from toil.utils.toilStats import getStats, processData
45
+ from toil.utils.toilStats import get_stats, process_data
43
46
  from toil.utils.toilStatus import ToilStatus
44
47
  from toil.version import python
45
48
 
@@ -56,7 +59,8 @@ class UtilsTest(ToilTest):
56
59
  super().setUp()
57
60
  self.tempDir = self._createTempDir()
58
61
  self.tempFile = get_temp_file(rootDir=self.tempDir)
59
- self.outputFile = 'someSortedStuff.txt'
62
+ self.outputFile = get_temp_file(rootDir=self.tempDir)
63
+ self.outputFile = "someSortedStuff.txt"
60
64
  self.toilDir = os.path.join(self.tempDir, "jobstore")
61
65
  self.assertFalse(os.path.exists(self.toilDir))
62
66
  self.lines = 1000
@@ -70,19 +74,19 @@ class UtilsTest(ToilTest):
70
74
 
71
75
  self.sort_workflow_cmd = [
72
76
  python,
73
- '-m',
74
- 'toil.test.sort.sort',
75
- f'file:{self.toilDir}',
76
- '--clean=never',
77
- '--numLines=1',
78
- '--lineLength=1'
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",
79
83
  ]
80
84
 
81
85
  self.restart_sort_workflow_cmd = [
82
86
  python,
83
- '-m',
84
- 'toil.test.sort.restart_sort',
85
- f'file:{self.toilDir}'
87
+ "-m",
88
+ "toil.test.sort.restart_sort",
89
+ f"file:{self.toilDir}",
86
90
  ]
87
91
 
88
92
  def tearDown(self):
@@ -91,7 +95,11 @@ class UtilsTest(ToilTest):
91
95
  if os.path.exists(self.toilDir):
92
96
  shutil.rmtree(self.toilDir)
93
97
 
94
- for f in ['fileToSort.txt', 'sortedFile.txt', 'output.txt']:
98
+ for f in [
99
+ self.tempFile,
100
+ self.outputFile,
101
+ os.path.join(self.tempDir, "output.txt"),
102
+ ]:
95
103
  if os.path.exists(f):
96
104
  os.remove(f)
97
105
 
@@ -99,26 +107,26 @@ class UtilsTest(ToilTest):
99
107
 
100
108
  @property
101
109
  def toilMain(self):
102
- return resolveEntryPoint('toil')
110
+ return resolveEntryPoint("toil")
103
111
 
104
112
  @property
105
113
  def cleanCommand(self):
106
- return [self.toilMain, 'clean', self.toilDir]
114
+ return [self.toilMain, "clean", self.toilDir]
107
115
 
108
116
  @property
109
117
  def statsCommand(self):
110
- return [self.toilMain, 'stats', self.toilDir, '--pretty']
118
+ return [self.toilMain, "stats", self.toilDir, "--pretty"]
111
119
 
112
120
  def statusCommand(self, failIfNotComplete=False):
113
- commandTokens = [self.toilMain, 'status', self.toilDir]
121
+ commandTokens = [self.toilMain, "status", self.toilDir]
114
122
  if failIfNotComplete:
115
- commandTokens.append('--failIfNotComplete')
123
+ commandTokens.append("--failIfNotComplete")
116
124
  return commandTokens
117
125
 
118
126
  def test_config_functionality(self):
119
127
  """Ensure that creating and reading back the config file works"""
120
128
  config_file = os.path.abspath("config.yaml")
121
- config_command = [self.toilMain, 'config', config_file]
129
+ config_command = [self.toilMain, "config", config_file]
122
130
  # make sure the command `toil config file_path` works
123
131
  try:
124
132
  subprocess.check_call(config_command)
@@ -129,12 +137,18 @@ class UtilsTest(ToilTest):
129
137
  # make sure that toil can read from the generated config file
130
138
  try:
131
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 }"
132
147
  except SystemExit:
133
148
  self.fail("Failed to parse the default generated config file!")
134
149
  finally:
135
150
  os.remove(config_file)
136
151
 
137
-
138
152
  @needs_rsync3
139
153
  @pytest.mark.timeout(1200)
140
154
  @needs_aws_ec2
@@ -155,30 +169,63 @@ class UtilsTest(ToilTest):
155
169
  :return:
156
170
  """
157
171
  # TODO: Run these for the other clouds.
158
- clusterName = f'cluster-utils-test{uuid.uuid4()}'
159
- keyName = os.getenv('TOIL_AWS_KEYNAME').strip() or 'id_rsa'
160
- 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
161
175
 
162
176
  try:
163
177
  from toil.provisioners.aws.awsProvisioner import AWSProvisioner
178
+
164
179
  aws_provisioner = AWSProvisioner.__module__
165
180
  logger.debug(f"Found AWSProvisioner: {aws_provisioner}.")
166
181
 
167
182
  # launch master with an assortment of custom tags
168
- system([self.toilMain, 'launch-cluster', '--clusterType', 'mesos',
169
- '-t', 'key1=value1', '-t', 'key2=value2', '--tag', 'key3=value3',
170
- '--leaderNodeType=t2.medium', '--keyPairName=' + keyName, clusterName,
171
- '--provisioner=aws', '--zone=us-west-2a', '--logLevel=DEBUG'])
172
-
173
- 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
+ )
174
207
  leader = cluster.getLeader()
175
208
 
176
209
  # check that the leader carries the appropriate tags
177
- 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
+ }
178
217
  for key in tags:
179
218
  self.assertEqual(leader.tags.get(key), tags[key])
180
219
  finally:
181
- 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
+ )
182
229
 
183
230
  @slow
184
231
  def testUtilsSort(self):
@@ -187,19 +234,27 @@ class UtilsTest(ToilTest):
187
234
  sort example with the --restart flag.
188
235
  """
189
236
  # Get the sort command to run
190
- toilCommand = [sys.executable,
191
- '-m', toil.test.sort.sort.__name__,
192
- self.toilDir,
193
- '--logLevel=DEBUG',
194
- '--fileToSort', self.tempFile,
195
- '--outputFile', self.outputFile,
196
- '--N', str(self.N),
197
- '--stats',
198
- '--retryCount=2',
199
- '--badWorker=0.5',
200
- '--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
+ ]
201
254
  # Try restarting it to check that a JobStoreException is thrown
202
- self.assertRaises(subprocess.CalledProcessError, system, toilCommand + ['--restart'])
255
+ self.assertRaises(
256
+ subprocess.CalledProcessError, system, toilCommand + ["--restart"]
257
+ )
203
258
  # Check that trying to run it in restart mode does not create the jobStore
204
259
  self.assertFalse(os.path.exists(self.toilDir))
205
260
 
@@ -208,9 +263,15 @@ class UtilsTest(ToilTest):
208
263
  try:
209
264
  system(toilCommand)
210
265
  finished = True
211
- 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
212
269
  system(self.statusCommand())
213
- self.assertRaises(subprocess.CalledProcessError, system, self.statusCommand(failIfNotComplete=True))
270
+ self.assertRaises(
271
+ subprocess.CalledProcessError,
272
+ system,
273
+ self.statusCommand(failIfNotComplete=True),
274
+ )
214
275
  finished = False
215
276
  self.assertTrue(os.path.exists(self.toilDir))
216
277
 
@@ -221,11 +282,17 @@ class UtilsTest(ToilTest):
221
282
  totalTrys = 1
222
283
  while not finished:
223
284
  try:
224
- system(toilCommand + ['--restart'])
285
+ system(toilCommand + ["--restart"])
225
286
  finished = True
226
- 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
227
290
  system(self.statusCommand())
228
- self.assertRaises(subprocess.CalledProcessError, system, self.statusCommand(failIfNotComplete=True))
291
+ self.assertRaises(
292
+ subprocess.CalledProcessError,
293
+ system,
294
+ self.statusCommand(failIfNotComplete=True),
295
+ )
229
296
  if totalTrys > 16:
230
297
  self.fail() # Exceeded a reasonable number of restarts
231
298
  totalTrys += 1
@@ -253,17 +320,23 @@ class UtilsTest(ToilTest):
253
320
  Tests the stats commands on a complete run of the stats test.
254
321
  """
255
322
  # Get the sort command to run
256
- toilCommand = [sys.executable,
257
- '-m', toil.test.sort.sort.__name__,
258
- self.toilDir,
259
- '--logLevel=DEBUG',
260
- '--fileToSort', self.tempFile,
261
- '--outputFile', self.outputFile,
262
- '--N', str(self.N),
263
- '--stats',
264
- '--retryCount=99',
265
- '--badWorker=0.5',
266
- '--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
+ ]
267
340
 
268
341
  # Run the script for the first time
269
342
  system(toilCommand)
@@ -282,8 +355,8 @@ class UtilsTest(ToilTest):
282
355
 
283
356
  def testUnicodeSupport(self):
284
357
  options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
285
- options.clean = 'always'
286
- options.logLevel = 'debug'
358
+ options.clean = "always"
359
+ options.logLevel = "debug"
287
360
  Job.Runner.startToil(Job.wrapFn(printUnicodeCharacter), options)
288
361
 
289
362
  @slow
@@ -292,36 +365,60 @@ class UtilsTest(ToilTest):
292
365
  Tests case where multiple jobs are run on 1 worker to ensure that all jobs report back their data
293
366
  """
294
367
  options = Job.Runner.getDefaultOptions(self._getTestJobStorePath())
295
- options.clean = 'never'
368
+ options.clean = "never"
296
369
  options.stats = True
297
370
  Job.Runner.startToil(RunTwoJobsPerWorker(), options)
298
371
  config = Config()
299
372
  config.setOptions(options)
300
373
  jobStore = Toil.resumeJobStore(config.jobStore)
301
- stats = getStats(jobStore)
302
- collatedStats = processData(jobStore.config, stats)
303
- self.assertTrue(len(collatedStats.job_types) == 2, "Some jobs are not represented in the stats.")
304
-
305
- def check_status(self, status, status_fn, seconds=20):
306
- i = 0.0
307
- while status_fn(self.toilDir) != status:
374
+ stats = get_stats(jobStore)
375
+ collatedStats = process_data(jobStore.config, stats)
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
+ )
308
400
  time.sleep(0.5)
309
- i += 0.5
310
- if i > seconds:
311
- s = status_fn(self.toilDir)
312
- 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
+ )
313
410
 
314
411
  def testGetPIDStatus(self):
315
412
  """Test that ToilStatus.getPIDStatus() behaves as expected."""
316
413
  wf = subprocess.Popen(self.sort_workflow_cmd)
317
- self.check_status('RUNNING', status_fn=ToilStatus.getPIDStatus, seconds=20)
414
+ self.check_status("RUNNING", status_fn=ToilStatus.getPIDStatus, process=wf, seconds=60)
318
415
  wf.wait()
319
- self.check_status('COMPLETED', status_fn=ToilStatus.getPIDStatus)
416
+ self.check_status("COMPLETED", status_fn=ToilStatus.getPIDStatus, process=wf, seconds=60)
320
417
 
321
418
  # TODO: we need to reach into the FileJobStore's files and delete this
322
419
  # shared file. We assume we know its internal layout.
323
- os.remove(os.path.join(self.toilDir, 'files/shared/pid.log'))
324
- self.check_status('QUEUED', status_fn=ToilStatus.getPIDStatus)
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)
325
422
 
326
423
  def testGetStatusFailedToilWF(self):
327
424
  """
@@ -330,41 +427,71 @@ class UtilsTest(ToilTest):
330
427
  opportunity to test the 'RUNNING' functionality of getStatus().
331
428
  """
332
429
  # --badWorker is set to force failure.
333
- wf = subprocess.Popen(self.sort_workflow_cmd + ['--badWorker=1'])
334
- self.check_status('RUNNING', status_fn=ToilStatus.getStatus)
430
+ wf = subprocess.Popen(self.sort_workflow_cmd + ["--badWorker=1"])
431
+ self.check_status("RUNNING", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
335
432
  wf.wait()
336
- self.check_status('ERROR', status_fn=ToilStatus.getStatus)
433
+ self.check_status("ERROR", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
337
434
 
338
435
  @needs_cwl
339
436
  @needs_docker
340
437
  def testGetStatusFailedCWLWF(self):
341
438
  """Test that ToilStatus.getStatus() behaves as expected with a failing CWL workflow."""
342
439
  # --badWorker is set to force failure.
343
- cmd = ['toil-cwl-runner', '--jobStore', self.toilDir, '--clean=never', '--badWorker=1',
344
- 'src/toil/test/cwl/sorttool.cwl', '--reverse', '--input', 'src/toil/test/cwl/whale.txt']
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))
345
454
  wf = subprocess.Popen(cmd)
346
- self.check_status('RUNNING', status_fn=ToilStatus.getStatus)
455
+ self.check_status("RUNNING", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
347
456
  wf.wait()
348
- self.check_status('ERROR', status_fn=ToilStatus.getStatus)
457
+ self.check_status("ERROR", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
349
458
 
350
459
  @needs_cwl
351
460
  @needs_docker
352
461
  def testGetStatusSuccessfulCWLWF(self):
353
462
  """Test that ToilStatus.getStatus() behaves as expected with a successful CWL workflow."""
354
- cmd = ['toil-cwl-runner', '--jobStore', self.toilDir, '--clean=never',
355
- 'src/toil/test/cwl/sorttool.cwl', '--reverse', '--input', 'src/toil/test/cwl/whale.txt']
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
+ ]
356
474
  wf = subprocess.Popen(cmd)
357
- self.check_status('RUNNING', status_fn=ToilStatus.getStatus, seconds=20)
475
+ self.check_status("RUNNING", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
358
476
  wf.wait()
359
- self.check_status('COMPLETED', status_fn=ToilStatus.getStatus)
477
+ self.check_status("COMPLETED", status_fn=ToilStatus.getStatus, process=wf, seconds=60)
360
478
 
361
479
  @needs_cwl
362
- @patch('builtins.print')
480
+ @patch("builtins.print")
363
481
  def testPrintJobLog(self, mock_print):
364
482
  """Test that ToilStatus.printJobLog() reads the log from a failed command without error."""
365
483
  # Run a workflow that will always fail
366
- cmd = ['toil-cwl-runner', '--jobStore', self.toilDir, '--clean=never',
367
- '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))
368
495
  wf = subprocess.Popen(cmd)
369
496
  wf.wait()
370
497
  # print log and check output
@@ -373,26 +500,37 @@ class UtilsTest(ToilTest):
373
500
 
374
501
  # Make sure it printed some kind of complaint about the missing command.
375
502
  args, kwargs = mock_print.call_args
376
- self.assertIn('invalidcommand', args[0])
503
+ self.assertIn("invalidcommand", args[0])
377
504
 
505
+ @pytest.mark.timeout(1200)
378
506
  def testRestartAttribute(self):
379
507
  """
380
- Test that the job store is only destroyed when we observe a succcessful workflow run.
508
+ Test that the job store is only destroyed when we observe a successful workflow run.
381
509
  The following simulates a failing workflow that attempts to resume without restart().
382
510
  In this case, the job store should not be destroyed until restart() is called.
383
511
  """
384
512
  # Run a workflow that will always fail
385
- cmd = self.restart_sort_workflow_cmd + ['--badWorker=1']
513
+ cmd = self.restart_sort_workflow_cmd + ["--badWorker=1", "--logDebug"]
386
514
  subprocess.run(cmd)
387
515
 
388
- restart_cmd = self.restart_sort_workflow_cmd + ['--badWorker=0', '--restart']
516
+ restart_cmd = self.restart_sort_workflow_cmd + [
517
+ "--badWorker=0",
518
+ "--logDebug",
519
+ "--restart",
520
+ ]
389
521
  subprocess.run(restart_cmd)
390
522
 
391
523
  # Check the job store exists after restart attempt
392
524
  self.assertTrue(os.path.exists(self.toilDir))
393
525
 
394
- successful_cmd = [python, '-m', 'toil.test.sort.sort', 'file:' + self.toilDir,
395
- '--restart']
526
+ successful_cmd = [
527
+ python,
528
+ "-m",
529
+ "toil.test.sort.sort",
530
+ "--logDebug",
531
+ "file:" + self.toilDir,
532
+ "--restart",
533
+ ]
396
534
  subprocess.run(successful_cmd)
397
535
 
398
536
  # Check the job store is destroyed after calling restart()
@@ -403,13 +541,14 @@ def printUnicodeCharacter():
403
541
  # We want to get a unicode character to stdout but we can't print it directly because of
404
542
  # Python encoding issues. To work around this we print in a separate Python process. See
405
543
  # http://stackoverflow.com/questions/492483/setting-the-correct-encoding-when-piping-stdout-in-python
406
- subprocess.check_call([sys.executable, '-c', "print('\\xc3\\xbc')"])
544
+ subprocess.check_call([sys.executable, "-c", "print('\\xc3\\xbc')"])
407
545
 
408
546
 
409
547
  class RunTwoJobsPerWorker(Job):
410
548
  """
411
549
  Runs child job with same resources as self in an attempt to chain the jobs on the same worker
412
550
  """
551
+
413
552
  def __init__(self):
414
553
  Job.__init__(self)
415
554