toil 9.1.1__py3-none-any.whl → 9.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toil/__init__.py +5 -9
- toil/batchSystems/abstractBatchSystem.py +23 -22
- toil/batchSystems/abstractGridEngineBatchSystem.py +17 -12
- toil/batchSystems/awsBatch.py +8 -8
- toil/batchSystems/cleanup_support.py +4 -4
- toil/batchSystems/contained_executor.py +3 -3
- toil/batchSystems/gridengine.py +3 -4
- toil/batchSystems/htcondor.py +5 -5
- toil/batchSystems/kubernetes.py +65 -63
- toil/batchSystems/local_support.py +2 -3
- toil/batchSystems/lsf.py +6 -7
- toil/batchSystems/mesos/batchSystem.py +11 -7
- toil/batchSystems/mesos/test/__init__.py +1 -2
- toil/batchSystems/options.py +9 -10
- toil/batchSystems/registry.py +3 -7
- toil/batchSystems/singleMachine.py +8 -11
- toil/batchSystems/slurm.py +49 -38
- toil/batchSystems/torque.py +3 -4
- toil/bus.py +36 -34
- toil/common.py +129 -89
- toil/cwl/cwltoil.py +857 -729
- toil/cwl/utils.py +44 -35
- toil/fileStores/__init__.py +3 -1
- toil/fileStores/abstractFileStore.py +28 -30
- toil/fileStores/cachingFileStore.py +8 -8
- toil/fileStores/nonCachingFileStore.py +10 -21
- toil/job.py +159 -158
- toil/jobStores/abstractJobStore.py +68 -69
- toil/jobStores/aws/jobStore.py +249 -213
- toil/jobStores/aws/utils.py +13 -24
- toil/jobStores/fileJobStore.py +28 -22
- toil/jobStores/googleJobStore.py +21 -17
- toil/jobStores/utils.py +3 -7
- toil/leader.py +17 -22
- toil/lib/accelerators.py +6 -4
- toil/lib/aws/__init__.py +9 -10
- toil/lib/aws/ami.py +33 -19
- toil/lib/aws/iam.py +6 -6
- toil/lib/aws/s3.py +259 -157
- toil/lib/aws/session.py +76 -76
- toil/lib/aws/utils.py +51 -43
- toil/lib/checksum.py +19 -15
- toil/lib/compatibility.py +3 -2
- toil/lib/conversions.py +45 -18
- toil/lib/directory.py +29 -26
- toil/lib/docker.py +93 -99
- toil/lib/dockstore.py +77 -50
- toil/lib/ec2.py +39 -38
- toil/lib/ec2nodes.py +11 -4
- toil/lib/exceptions.py +8 -5
- toil/lib/ftp_utils.py +9 -14
- toil/lib/generatedEC2Lists.py +161 -20
- toil/lib/history.py +141 -97
- toil/lib/history_submission.py +163 -72
- toil/lib/io.py +27 -17
- toil/lib/memoize.py +2 -1
- toil/lib/misc.py +15 -11
- toil/lib/pipes.py +40 -25
- toil/lib/plugins.py +12 -8
- toil/lib/resources.py +1 -0
- toil/lib/retry.py +32 -38
- toil/lib/threading.py +12 -12
- toil/lib/throttle.py +1 -2
- toil/lib/trs.py +113 -51
- toil/lib/url.py +14 -23
- toil/lib/web.py +7 -2
- toil/options/common.py +18 -15
- toil/options/cwl.py +2 -2
- toil/options/runner.py +9 -5
- toil/options/wdl.py +1 -3
- toil/provisioners/__init__.py +9 -9
- toil/provisioners/abstractProvisioner.py +22 -20
- toil/provisioners/aws/__init__.py +20 -14
- toil/provisioners/aws/awsProvisioner.py +10 -8
- toil/provisioners/clusterScaler.py +19 -18
- toil/provisioners/gceProvisioner.py +2 -3
- toil/provisioners/node.py +11 -13
- toil/realtimeLogger.py +4 -4
- toil/resource.py +5 -5
- toil/server/app.py +2 -2
- toil/server/cli/wes_cwl_runner.py +11 -11
- toil/server/utils.py +18 -21
- toil/server/wes/abstract_backend.py +9 -8
- toil/server/wes/amazon_wes_utils.py +3 -3
- toil/server/wes/tasks.py +3 -5
- toil/server/wes/toil_backend.py +17 -21
- toil/server/wsgi_app.py +3 -3
- toil/serviceManager.py +3 -4
- toil/statsAndLogging.py +12 -13
- toil/test/__init__.py +33 -24
- toil/test/batchSystems/batchSystemTest.py +12 -11
- toil/test/batchSystems/batch_system_plugin_test.py +3 -5
- toil/test/batchSystems/test_slurm.py +38 -24
- toil/test/cwl/conftest.py +5 -6
- toil/test/cwl/cwlTest.py +194 -78
- toil/test/cwl/download_file_uri.json +6 -0
- toil/test/cwl/download_file_uri_no_hostname.json +6 -0
- toil/test/docs/scripts/tutorial_staging.py +1 -0
- toil/test/jobStores/jobStoreTest.py +9 -7
- toil/test/lib/aws/test_iam.py +1 -3
- toil/test/lib/aws/test_s3.py +1 -1
- toil/test/lib/dockerTest.py +9 -9
- toil/test/lib/test_ec2.py +12 -11
- toil/test/lib/test_history.py +4 -4
- toil/test/lib/test_trs.py +16 -14
- toil/test/lib/test_url.py +7 -6
- toil/test/lib/url_plugin_test.py +12 -18
- toil/test/provisioners/aws/awsProvisionerTest.py +10 -8
- toil/test/provisioners/clusterScalerTest.py +2 -5
- toil/test/provisioners/clusterTest.py +1 -3
- toil/test/server/serverTest.py +13 -4
- toil/test/sort/restart_sort.py +2 -6
- toil/test/sort/sort.py +3 -8
- toil/test/src/deferredFunctionTest.py +7 -7
- toil/test/src/environmentTest.py +1 -2
- toil/test/src/fileStoreTest.py +5 -5
- toil/test/src/importExportFileTest.py +5 -6
- toil/test/src/jobServiceTest.py +22 -14
- toil/test/src/jobTest.py +121 -25
- toil/test/src/miscTests.py +5 -7
- toil/test/src/promisedRequirementTest.py +8 -7
- toil/test/src/regularLogTest.py +2 -3
- toil/test/src/resourceTest.py +5 -8
- toil/test/src/restartDAGTest.py +5 -6
- toil/test/src/resumabilityTest.py +2 -2
- toil/test/src/retainTempDirTest.py +3 -3
- toil/test/src/systemTest.py +3 -3
- toil/test/src/threadingTest.py +1 -1
- toil/test/src/workerTest.py +1 -2
- toil/test/utils/toilDebugTest.py +6 -4
- toil/test/utils/toilKillTest.py +1 -1
- toil/test/utils/utilsTest.py +15 -14
- toil/test/wdl/wdltoil_test.py +247 -124
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/toilState.py +2 -3
- toil/utils/toilDebugFile.py +3 -8
- toil/utils/toilDebugJob.py +1 -2
- toil/utils/toilLaunchCluster.py +1 -2
- toil/utils/toilSshCluster.py +2 -0
- toil/utils/toilStats.py +19 -24
- toil/utils/toilStatus.py +11 -14
- toil/version.py +10 -10
- toil/wdl/wdltoil.py +313 -209
- toil/worker.py +18 -12
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/METADATA +11 -14
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/RECORD +150 -153
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/WHEEL +1 -1
- toil/test/cwl/staging_cat.cwl +0 -27
- toil/test/cwl/staging_make_file.cwl +0 -25
- toil/test/cwl/staging_workflow.cwl +0 -43
- toil/test/cwl/zero_default.cwl +0 -61
- toil/test/utils/ABCWorkflowDebug/ABC.txt +0 -1
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/entry_points.txt +0 -0
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/licenses/LICENSE +0 -0
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/top_level.txt +0 -0
toil/statsAndLogging.py
CHANGED
|
@@ -18,9 +18,10 @@ import logging
|
|
|
18
18
|
import os
|
|
19
19
|
import time
|
|
20
20
|
from argparse import ArgumentParser, Namespace
|
|
21
|
+
from collections.abc import Callable
|
|
21
22
|
from logging.handlers import RotatingFileHandler
|
|
22
23
|
from threading import Event, Thread
|
|
23
|
-
from typing import IO, TYPE_CHECKING, Any,
|
|
24
|
+
from typing import IO, TYPE_CHECKING, Any, Union
|
|
24
25
|
|
|
25
26
|
from toil.lib.conversions import strtobool
|
|
26
27
|
from toil.lib.expando import Expando
|
|
@@ -60,9 +61,7 @@ class StatsAndLogging:
|
|
|
60
61
|
self._worker.start()
|
|
61
62
|
|
|
62
63
|
@classmethod
|
|
63
|
-
def formatLogStream(
|
|
64
|
-
cls, stream: Union[IO[str], IO[bytes]], stream_name: str
|
|
65
|
-
) -> str:
|
|
64
|
+
def formatLogStream(cls, stream: IO[str] | IO[bytes], stream_name: str) -> str:
|
|
66
65
|
"""
|
|
67
66
|
Given a stream of text or bytes, and the job name, job itself, or some
|
|
68
67
|
other optional stringifyable identity info for the job, return a big
|
|
@@ -90,9 +89,9 @@ class StatsAndLogging:
|
|
|
90
89
|
def logWithFormatting(
|
|
91
90
|
cls,
|
|
92
91
|
stream_name: str,
|
|
93
|
-
jobLogs:
|
|
92
|
+
jobLogs: IO[str] | IO[bytes],
|
|
94
93
|
method: Callable[[str], None] = logger.debug,
|
|
95
|
-
message:
|
|
94
|
+
message: str | None = None,
|
|
96
95
|
) -> None:
|
|
97
96
|
if message is not None:
|
|
98
97
|
method(message)
|
|
@@ -174,7 +173,7 @@ class StatsAndLogging:
|
|
|
174
173
|
startTime = time.time()
|
|
175
174
|
startClock = ResourceMonitor.get_total_cpu_time()
|
|
176
175
|
|
|
177
|
-
def callback(fileHandle:
|
|
176
|
+
def callback(fileHandle: IO[bytes] | IO[str]) -> None:
|
|
178
177
|
statsStr = fileHandle.read()
|
|
179
178
|
if not isinstance(statsStr, str):
|
|
180
179
|
statsStr = statsStr.decode()
|
|
@@ -259,7 +258,7 @@ class StatsAndLogging:
|
|
|
259
258
|
cores=float(job.requested_cores),
|
|
260
259
|
cpu_seconds=float(job.clock),
|
|
261
260
|
memory_bytes=int(job.memory) * 1024,
|
|
262
|
-
disk_bytes=int(job.disk)
|
|
261
|
+
disk_bytes=int(job.disk),
|
|
263
262
|
)
|
|
264
263
|
except:
|
|
265
264
|
logger.exception("Could not record job attempt in history!")
|
|
@@ -304,7 +303,7 @@ class StatsAndLogging:
|
|
|
304
303
|
# in addition to cleaning on exceptions, onError should clean if there are any failed jobs
|
|
305
304
|
|
|
306
305
|
|
|
307
|
-
def set_log_level(level: str, set_logger:
|
|
306
|
+
def set_log_level(level: str, set_logger: logging.Logger | None = None) -> None:
|
|
308
307
|
"""Sets the root logger level to a given string level (like "INFO")."""
|
|
309
308
|
level = "CRITICAL" if level.upper() == "OFF" else level.upper()
|
|
310
309
|
set_logger = set_logger if set_logger else root_logger
|
|
@@ -314,7 +313,7 @@ def set_log_level(level: str, set_logger: Optional[logging.Logger] = None) -> No
|
|
|
314
313
|
suppress_exotic_logging(__name__)
|
|
315
314
|
|
|
316
315
|
|
|
317
|
-
def install_log_color(set_logger:
|
|
316
|
+
def install_log_color(set_logger: logging.Logger | None = None) -> None:
|
|
318
317
|
"""Make logs colored."""
|
|
319
318
|
# Most of this code is taken from miniwdl
|
|
320
319
|
# delayed import
|
|
@@ -349,7 +348,7 @@ def install_log_color(set_logger: Optional[logging.Logger] = None) -> None:
|
|
|
349
348
|
|
|
350
349
|
|
|
351
350
|
def add_logging_options(
|
|
352
|
-
parser: ArgumentParser, default_level:
|
|
351
|
+
parser: ArgumentParser, default_level: int | None = None
|
|
353
352
|
) -> None:
|
|
354
353
|
"""
|
|
355
354
|
Add logging options to set the global log level.
|
|
@@ -424,11 +423,11 @@ def configure_root_logger() -> None:
|
|
|
424
423
|
root_logger.setLevel(DEFAULT_LOGLEVEL)
|
|
425
424
|
|
|
426
425
|
|
|
427
|
-
def log_to_file(log_file:
|
|
426
|
+
def log_to_file(log_file: str | None, log_rotation: bool) -> None:
|
|
428
427
|
if log_file and log_file not in __loggingFiles:
|
|
429
428
|
logger.debug(f"Logging to file '{log_file}'.")
|
|
430
429
|
__loggingFiles.append(log_file)
|
|
431
|
-
handler:
|
|
430
|
+
handler: RotatingFileHandler | logging.FileHandler
|
|
432
431
|
if log_rotation:
|
|
433
432
|
handler = RotatingFileHandler(log_file, maxBytes=1000000, backupCount=1)
|
|
434
433
|
else:
|
toil/test/__init__.py
CHANGED
|
@@ -35,7 +35,8 @@ from pathlib import Path
|
|
|
35
35
|
from shutil import which
|
|
36
36
|
from tempfile import mkstemp
|
|
37
37
|
from textwrap import dedent
|
|
38
|
-
from typing import Any,
|
|
38
|
+
from typing import Any, Literal, Optional, TypeVar, Union, cast
|
|
39
|
+
from collections.abc import Callable
|
|
39
40
|
from unittest.util import strclass
|
|
40
41
|
from urllib.error import HTTPError, URLError
|
|
41
42
|
from urllib.request import urlopen
|
|
@@ -60,6 +61,7 @@ except ImportError:
|
|
|
60
61
|
class ProxyConnectionError(BaseException): # type: ignore[no-redef]
|
|
61
62
|
...
|
|
62
63
|
|
|
64
|
+
|
|
63
65
|
logger = logging.getLogger(__name__)
|
|
64
66
|
|
|
65
67
|
|
|
@@ -100,7 +102,7 @@ class ToilTest(unittest.TestCase):
|
|
|
100
102
|
"""
|
|
101
103
|
|
|
102
104
|
_rootpath: Path
|
|
103
|
-
_tempBaseDir:
|
|
105
|
+
_tempBaseDir: str | None = None
|
|
104
106
|
_tempDirs: list[str] = []
|
|
105
107
|
|
|
106
108
|
def setup_method(self, method: Any) -> None:
|
|
@@ -179,11 +181,11 @@ class ToilTest(unittest.TestCase):
|
|
|
179
181
|
assert region
|
|
180
182
|
return region.group(1)
|
|
181
183
|
|
|
182
|
-
def _createTempDir(self, purpose:
|
|
184
|
+
def _createTempDir(self, purpose: str | None = None) -> str:
|
|
183
185
|
return self._createTempDirEx(self._testMethodName, purpose)
|
|
184
186
|
|
|
185
187
|
@classmethod
|
|
186
|
-
def _createTempDirEx(cls, *names:
|
|
188
|
+
def _createTempDirEx(cls, *names: str | None) -> str:
|
|
187
189
|
classname = strclass(cls)
|
|
188
190
|
if classname.startswith("toil.test."):
|
|
189
191
|
classname = classname[len("toil.test.") :]
|
|
@@ -207,7 +209,7 @@ class ToilTest(unittest.TestCase):
|
|
|
207
209
|
return path
|
|
208
210
|
|
|
209
211
|
@classmethod
|
|
210
|
-
def _run(cls, command: str, *args: str, **kwargs: Any) ->
|
|
212
|
+
def _run(cls, command: str, *args: str, **kwargs: Any) -> str | None:
|
|
211
213
|
"""
|
|
212
214
|
Run a command.
|
|
213
215
|
|
|
@@ -265,7 +267,7 @@ else:
|
|
|
265
267
|
return cast(MT, getattr(pytest_mark, name)(test_item))
|
|
266
268
|
|
|
267
269
|
|
|
268
|
-
def get_temp_file(suffix: str = "", rootDir:
|
|
270
|
+
def get_temp_file(suffix: str = "", rootDir: str | None = None) -> str:
|
|
269
271
|
"""Return a string representing a temporary file, that must be manually deleted."""
|
|
270
272
|
if rootDir is None:
|
|
271
273
|
handle, tmp_file = mkstemp(suffix)
|
|
@@ -282,7 +284,7 @@ def get_temp_file(suffix: str = "", rootDir: Optional[str] = None) -> str:
|
|
|
282
284
|
return tmp_file
|
|
283
285
|
|
|
284
286
|
|
|
285
|
-
def needs_env_var(var_name: str, comment:
|
|
287
|
+
def needs_env_var(var_name: str, comment: str | None = None) -> Callable[[MT], MT]:
|
|
286
288
|
"""
|
|
287
289
|
Use as a decorator before test classes or methods to run only if the given
|
|
288
290
|
environment variable is set.
|
|
@@ -369,9 +371,7 @@ def needs_aws_s3(test_item: MT) -> MT:
|
|
|
369
371
|
test_item
|
|
370
372
|
)
|
|
371
373
|
except ProxyConnectionError as e:
|
|
372
|
-
return unittest.skip(f"Proxy error: {e}, skipping this test.")(
|
|
373
|
-
test_item
|
|
374
|
-
)
|
|
374
|
+
return unittest.skip(f"Proxy error: {e}, skipping this test.")(test_item)
|
|
375
375
|
from toil.lib.aws import running_on_ec2
|
|
376
376
|
|
|
377
377
|
if not (
|
|
@@ -569,6 +569,7 @@ def needs_mesos(test_item: MT) -> MT:
|
|
|
569
569
|
)(test_item)
|
|
570
570
|
try:
|
|
571
571
|
import psutil # noqa
|
|
572
|
+
|
|
572
573
|
# If pymesos is installed, because it isn't typed, mypy sees an
|
|
573
574
|
# import-untyped error here.
|
|
574
575
|
#
|
|
@@ -592,6 +593,9 @@ def _mesos_avail() -> bool:
|
|
|
592
593
|
try:
|
|
593
594
|
import psutil
|
|
594
595
|
import pymesos
|
|
596
|
+
|
|
597
|
+
str(psutil) # to prevent removal of this import
|
|
598
|
+
str(pymesos) # to prevent removal of this import
|
|
595
599
|
except ImportError:
|
|
596
600
|
return False
|
|
597
601
|
return True
|
|
@@ -793,6 +797,8 @@ def needs_cwl(test_item: MT) -> MT:
|
|
|
793
797
|
def _cwl_available() -> bool:
|
|
794
798
|
try:
|
|
795
799
|
import cwltool
|
|
800
|
+
|
|
801
|
+
str(cwltool) # to prevent removal of this import
|
|
796
802
|
except ImportError:
|
|
797
803
|
return False
|
|
798
804
|
return True
|
|
@@ -1038,7 +1044,7 @@ def timeLimit(seconds: int) -> Generator[None, None, None]:
|
|
|
1038
1044
|
|
|
1039
1045
|
def make_tests(
|
|
1040
1046
|
generalMethod: Callable[[Any], Any],
|
|
1041
|
-
targetClass:
|
|
1047
|
+
targetClass: Callable[[Any], Any] | None,
|
|
1042
1048
|
**kwargs: Any,
|
|
1043
1049
|
) -> None:
|
|
1044
1050
|
"""
|
|
@@ -1185,7 +1191,7 @@ class ApplianceTestSupport(ToilTest):
|
|
|
1185
1191
|
|
|
1186
1192
|
@contextmanager
|
|
1187
1193
|
def _applianceCluster(
|
|
1188
|
-
self, mounts: dict[str, str], numCores:
|
|
1194
|
+
self, mounts: dict[str, str], numCores: int | None = None
|
|
1189
1195
|
) -> Generator[
|
|
1190
1196
|
tuple["ApplianceTestSupport.LeaderThread", "ApplianceTestSupport.WorkerThread"],
|
|
1191
1197
|
None,
|
|
@@ -1244,22 +1250,25 @@ class ApplianceTestSupport(ToilTest):
|
|
|
1244
1250
|
self.mounts = mounts
|
|
1245
1251
|
self.cleanMounts = cleanMounts
|
|
1246
1252
|
self.containerName = str(uuid.uuid4())
|
|
1247
|
-
self.popen:
|
|
1253
|
+
self.popen: subprocess.Popen[bytes] | None = None
|
|
1248
1254
|
|
|
1249
1255
|
def __enter__(self) -> Self:
|
|
1250
1256
|
with self.lock:
|
|
1251
1257
|
image = applianceSelf()
|
|
1252
1258
|
# Omitting --rm, it's unreliable, see https://github.com/docker/docker/issues/16575
|
|
1253
|
-
args =
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
self.
|
|
1259
|
+
args = (
|
|
1260
|
+
[
|
|
1261
|
+
"docker",
|
|
1262
|
+
"run",
|
|
1263
|
+
f"--entrypoint={self._entryPoint()}",
|
|
1264
|
+
"--net=host",
|
|
1265
|
+
"-i",
|
|
1266
|
+
f"--name={self.containerName}",
|
|
1267
|
+
]
|
|
1268
|
+
+ ["--volume=%s:%s" % mount for mount in self.mounts.items()]
|
|
1269
|
+
+ [image]
|
|
1270
|
+
+ self._containerCommand()
|
|
1271
|
+
)
|
|
1263
1272
|
logger.info("Running %r", args)
|
|
1264
1273
|
self.popen = subprocess.Popen(args)
|
|
1265
1274
|
self.start()
|
|
@@ -1347,7 +1356,7 @@ class ApplianceTestSupport(ToilTest):
|
|
|
1347
1356
|
self.runOnAppliance("tee", path, input=contents)
|
|
1348
1357
|
|
|
1349
1358
|
def deployScript(
|
|
1350
|
-
self, path: str, packagePath: str, script:
|
|
1359
|
+
self, path: str, packagePath: str, script: str | Callable[..., Any]
|
|
1351
1360
|
) -> None:
|
|
1352
1361
|
"""
|
|
1353
1362
|
Deploy a Python module on the appliance.
|
|
@@ -11,22 +11,24 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
from collections.abc import Iterable, Generator
|
|
15
14
|
import argparse
|
|
16
15
|
import fcntl
|
|
17
16
|
import itertools
|
|
18
17
|
import logging
|
|
19
18
|
import os
|
|
20
|
-
from pathlib import Path
|
|
21
19
|
import subprocess
|
|
22
20
|
import sys
|
|
23
21
|
import tempfile
|
|
24
22
|
import textwrap
|
|
25
23
|
import time
|
|
26
24
|
from abc import ABCMeta, abstractmethod
|
|
25
|
+
from collections.abc import Generator
|
|
27
26
|
from fractions import Fraction
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import TYPE_CHECKING, Any
|
|
28
29
|
from unittest import skipIf
|
|
29
|
-
|
|
30
|
+
|
|
31
|
+
import pytest
|
|
30
32
|
|
|
31
33
|
from toil.batchSystems.abstractBatchSystem import (
|
|
32
34
|
AbstractBatchSystem,
|
|
@@ -45,9 +47,9 @@ from toil.batchSystems.registry import (
|
|
|
45
47
|
)
|
|
46
48
|
from toil.batchSystems.singleMachine import SingleMachineBatchSystem
|
|
47
49
|
from toil.common import Config, Toil
|
|
48
|
-
from toil.fileStores.abstractFileStore import AbstractFileStore
|
|
49
50
|
from toil.job import Job, JobDescription, Requirer, ServiceHostJob
|
|
50
51
|
from toil.lib.misc import StrPath
|
|
52
|
+
from toil.lib.plugins import remove_plugin
|
|
51
53
|
from toil.lib.retry import retry_flaky_test
|
|
52
54
|
from toil.lib.threading import cpu_count
|
|
53
55
|
from toil.test import (
|
|
@@ -63,13 +65,10 @@ from toil.test import (
|
|
|
63
65
|
needs_mesos,
|
|
64
66
|
needs_slurm,
|
|
65
67
|
needs_torque,
|
|
66
|
-
slow,
|
|
67
|
-
pslow,
|
|
68
68
|
pneeds_mesos,
|
|
69
|
+
pslow,
|
|
70
|
+
slow,
|
|
69
71
|
)
|
|
70
|
-
from toil.lib.plugins import remove_plugin
|
|
71
|
-
|
|
72
|
-
import pytest
|
|
73
72
|
|
|
74
73
|
if TYPE_CHECKING:
|
|
75
74
|
from toil.batchSystems.mesos.batchSystem import MesosBatchSystem
|
|
@@ -119,6 +118,7 @@ class hidden:
|
|
|
119
118
|
|
|
120
119
|
http://stackoverflow.com/questions/1323455/python-unit-test-with-base-and-sub-class#answer-25695512
|
|
121
120
|
"""
|
|
121
|
+
|
|
122
122
|
class AbstractBatchSystemTest(ToilTest, metaclass=ABCMeta):
|
|
123
123
|
"""
|
|
124
124
|
A base test case with generic tests that every batch system should pass.
|
|
@@ -159,7 +159,7 @@ class hidden:
|
|
|
159
159
|
return self.createConfig()
|
|
160
160
|
|
|
161
161
|
def _mockJobDescription(
|
|
162
|
-
self, jobStoreID:
|
|
162
|
+
self, jobStoreID: str | None = None, **kwargs: Any
|
|
163
163
|
) -> JobDescription:
|
|
164
164
|
"""
|
|
165
165
|
Create a mock-up JobDescription with the given ID and other parameters.
|
|
@@ -818,7 +818,8 @@ class SingleMachineBatchSystemTest(hidden.AbstractBatchSystemTest):
|
|
|
818
818
|
import signal
|
|
819
819
|
import sys
|
|
820
820
|
import time
|
|
821
|
-
from
|
|
821
|
+
from collections.abc import Iterable
|
|
822
|
+
from typing import Any
|
|
822
823
|
|
|
823
824
|
def handle_signal(sig: Any, frame: Any) -> None:
|
|
824
825
|
sys.stderr.write(f"{os.getpid()} ignoring signal {sig}\n")
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import logging
|
|
15
|
-
from typing import Optional
|
|
16
15
|
|
|
17
16
|
from configargparse import ArgParser, ArgumentParser
|
|
18
17
|
|
|
@@ -25,8 +24,8 @@ from toil.batchSystems.options import OptionSetter
|
|
|
25
24
|
from toil.batchSystems.registry import add_batch_system_factory
|
|
26
25
|
from toil.common import Toil, addOptions
|
|
27
26
|
from toil.job import JobDescription
|
|
28
|
-
from toil.test import ToilTest
|
|
29
27
|
from toil.lib.plugins import remove_plugin
|
|
28
|
+
from toil.test import ToilTest
|
|
30
29
|
|
|
31
30
|
logger = logging.getLogger(__name__)
|
|
32
31
|
|
|
@@ -40,7 +39,7 @@ class FakeBatchSystem(BatchSystemCleanupSupport):
|
|
|
40
39
|
self,
|
|
41
40
|
command: str,
|
|
42
41
|
job_desc: JobDescription,
|
|
43
|
-
job_environment:
|
|
42
|
+
job_environment: dict[str, str] | None = None,
|
|
44
43
|
) -> int:
|
|
45
44
|
pass
|
|
46
45
|
|
|
@@ -53,7 +52,7 @@ class FakeBatchSystem(BatchSystemCleanupSupport):
|
|
|
53
52
|
def getRunningBatchJobIDs(self) -> dict[int, float]:
|
|
54
53
|
pass
|
|
55
54
|
|
|
56
|
-
def getUpdatedBatchJob(self, maxWait: int) ->
|
|
55
|
+
def getUpdatedBatchJob(self, maxWait: int) -> UpdatedBatchJobInfo | None:
|
|
57
56
|
pass
|
|
58
57
|
|
|
59
58
|
def shutdown(self) -> None:
|
|
@@ -82,7 +81,6 @@ class BatchSystemPluginTest(ToilTest):
|
|
|
82
81
|
|
|
83
82
|
def fake_batch_system_factory() -> type[AbstractBatchSystem]:
|
|
84
83
|
return FakeBatchSystem
|
|
85
|
-
|
|
86
84
|
|
|
87
85
|
add_batch_system_factory("fake", fake_batch_system_factory)
|
|
88
86
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import errno
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
2
4
|
import textwrap
|
|
5
|
+
from datetime import datetime, timedelta
|
|
3
6
|
from queue import Queue
|
|
4
7
|
|
|
5
|
-
import logging
|
|
6
8
|
import pytest
|
|
7
|
-
import sys
|
|
8
|
-
|
|
9
|
-
from datetime import datetime, timedelta
|
|
10
9
|
|
|
11
10
|
import toil.batchSystems.slurm
|
|
12
11
|
from toil.batchSystems.abstractBatchSystem import (
|
|
@@ -29,6 +28,7 @@ logger = logging.getLogger(__name__)
|
|
|
29
28
|
# we hope is not days and days away from the time the tests actually run.
|
|
30
29
|
JOB_BASE_TIME = datetime.now().astimezone(None) - timedelta(days=5)
|
|
31
30
|
|
|
31
|
+
|
|
32
32
|
def call_either(args, **_) -> str:
|
|
33
33
|
"""
|
|
34
34
|
Pretend to call either sacct or scontrol as appropriate.
|
|
@@ -40,6 +40,7 @@ def call_either(args, **_) -> str:
|
|
|
40
40
|
else:
|
|
41
41
|
raise RuntimeError(f"Cannot fake command call: {args}")
|
|
42
42
|
|
|
43
|
+
|
|
43
44
|
def call_sacct(args, **_) -> str:
|
|
44
45
|
"""
|
|
45
46
|
The arguments passed to `call_command` when executing `sacct` are something like:
|
|
@@ -84,26 +85,24 @@ def call_sacct(args, **_) -> str:
|
|
|
84
85
|
|
|
85
86
|
# See if they asked for a job list
|
|
86
87
|
try:
|
|
87
|
-
j_index = args.index(
|
|
88
|
+
j_index = args.index("-j")
|
|
88
89
|
job_ids = [int(job_id) for job_id in args[j_index + 1].split(",")]
|
|
89
90
|
except ValueError:
|
|
90
91
|
# We're not restricting to a list of jobs.
|
|
91
92
|
job_ids = list(sacct_info.keys())
|
|
92
93
|
# See if they asked for start or end times
|
|
93
94
|
try:
|
|
94
|
-
flag_index = args.index(
|
|
95
|
+
flag_index = args.index("-S")
|
|
95
96
|
begin_time = datetime.fromisoformat(args[flag_index + 1]).astimezone(None)
|
|
96
97
|
except ValueError:
|
|
97
98
|
# By default, Slurm uses today at midnight
|
|
98
|
-
begin_time =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
second=0,
|
|
102
|
-
microsecond=0,
|
|
103
|
-
fold=0
|
|
99
|
+
begin_time = (
|
|
100
|
+
datetime.now()
|
|
101
|
+
.astimezone(None)
|
|
102
|
+
.replace(hour=0, minute=0, second=0, microsecond=0, fold=0)
|
|
104
103
|
)
|
|
105
104
|
try:
|
|
106
|
-
flag_index = args.index(
|
|
105
|
+
flag_index = args.index("-E")
|
|
107
106
|
end_time = datetime.fromisoformat(args[flag_index + 1]).astimezone(None)
|
|
108
107
|
except ValueError:
|
|
109
108
|
end_time = None
|
|
@@ -251,6 +250,7 @@ def call_sacct_raises(*_):
|
|
|
251
250
|
1, "sacct: error: Problem talking to the database: " "Connection timed out"
|
|
252
251
|
)
|
|
253
252
|
|
|
253
|
+
|
|
254
254
|
def call_sinfo(*_) -> str:
|
|
255
255
|
"""
|
|
256
256
|
Simulate asking for partition info from Slurm
|
|
@@ -272,6 +272,7 @@ def call_sinfo(*_) -> str:
|
|
|
272
272
|
)
|
|
273
273
|
return stdout
|
|
274
274
|
|
|
275
|
+
|
|
275
276
|
class FakeBatchSystem(BatchSystemSupport):
|
|
276
277
|
"""
|
|
277
278
|
Class that implements a minimal Batch System, needed to create a Worker (see below).
|
|
@@ -299,13 +300,17 @@ class FakeBatchSystem(BatchSystemSupport):
|
|
|
299
300
|
|
|
300
301
|
config.workflowID = str(uuid4())
|
|
301
302
|
config.cleanWorkDir = "always"
|
|
302
|
-
toil.batchSystems.slurm.SlurmBatchSystem.setOptions(
|
|
303
|
+
toil.batchSystems.slurm.SlurmBatchSystem.setOptions(
|
|
304
|
+
lambda o: setattr(config, o, None)
|
|
305
|
+
)
|
|
303
306
|
return config
|
|
304
307
|
|
|
308
|
+
|
|
305
309
|
# Make the mock class not have abstract methods anymore, even though we don't
|
|
306
310
|
# implement them. See <https://stackoverflow.com/a/17345619>.
|
|
307
311
|
FakeBatchSystem.__abstractmethods__ = set()
|
|
308
312
|
|
|
313
|
+
|
|
309
314
|
class SlurmTest(ToilTest):
|
|
310
315
|
"""
|
|
311
316
|
Class for unit-testing SlurmBatchSystem
|
|
@@ -373,7 +378,6 @@ class SlurmTest(ToilTest):
|
|
|
373
378
|
result = self.worker._getJobDetailsFromSacct(list(expected_result))
|
|
374
379
|
assert result == expected_result, f"{result} != {expected_result}"
|
|
375
380
|
|
|
376
|
-
|
|
377
381
|
####
|
|
378
382
|
#### tests for _getJobDetailsFromScontrol()
|
|
379
383
|
####
|
|
@@ -524,7 +528,7 @@ class SlurmTest(ToilTest):
|
|
|
524
528
|
(130, BatchJobExitReason.FAILED),
|
|
525
529
|
(0, BatchJobExitReason.FINISHED),
|
|
526
530
|
(0, BatchJobExitReason.FINISHED),
|
|
527
|
-
None
|
|
531
|
+
None,
|
|
528
532
|
]
|
|
529
533
|
result = self.worker.coalesce_job_exit_codes(job_ids)
|
|
530
534
|
assert result == expected_result, f"{result} != {expected_result}"
|
|
@@ -625,13 +629,17 @@ class SlurmTest(ToilTest):
|
|
|
625
629
|
assert "--partition=short" in command
|
|
626
630
|
|
|
627
631
|
# With a partition override, we should not. But the override will be rewritten.
|
|
628
|
-
self.worker.boss.config.slurm_args =
|
|
632
|
+
self.worker.boss.config.slurm_args = (
|
|
633
|
+
"--something --partition foo --somethingElse"
|
|
634
|
+
)
|
|
629
635
|
command = self.worker.prepareSbatch(1, 100, 5, "job5", None, None)
|
|
630
636
|
assert "--partition=short" not in command
|
|
631
637
|
assert "--partition=foo" in command
|
|
632
638
|
|
|
633
639
|
# All ways of setting partition should work, including =
|
|
634
|
-
self.worker.boss.config.slurm_args =
|
|
640
|
+
self.worker.boss.config.slurm_args = (
|
|
641
|
+
"--something --partition=foo --somethingElse"
|
|
642
|
+
)
|
|
635
643
|
command = self.worker.prepareSbatch(1, 100, 5, "job5", None, None)
|
|
636
644
|
assert "--partition=short" not in command
|
|
637
645
|
assert "--partition=foo" in command
|
|
@@ -649,7 +657,9 @@ class SlurmTest(ToilTest):
|
|
|
649
657
|
assert "--partition=foobar" in command
|
|
650
658
|
|
|
651
659
|
# But they should be overridden by the argument overrides
|
|
652
|
-
self.worker.boss.config.slurm_args =
|
|
660
|
+
self.worker.boss.config.slurm_args = (
|
|
661
|
+
"--something --partition=baz --somethingElse"
|
|
662
|
+
)
|
|
653
663
|
command = self.worker.prepareSbatch(1, 100, 5, "job5", None, None)
|
|
654
664
|
assert "--partition=baz" in command
|
|
655
665
|
|
|
@@ -669,14 +679,18 @@ class SlurmTest(ToilTest):
|
|
|
669
679
|
|
|
670
680
|
# With a time override, we should use it, slightly translated, and it
|
|
671
681
|
# should change the selected partition.
|
|
672
|
-
self.worker.boss.config.slurm_args =
|
|
682
|
+
self.worker.boss.config.slurm_args = (
|
|
683
|
+
"--something --time 10:00:00 --somethingElse"
|
|
684
|
+
)
|
|
673
685
|
command = self.worker.prepareSbatch(1, 100, 5, "job5", None, None)
|
|
674
686
|
logger.debug("Command: %s", command)
|
|
675
687
|
assert "--partition=medium" in command
|
|
676
688
|
assert "--time=0:36000" in command
|
|
677
689
|
|
|
678
690
|
# All ways of setting time should work, including =
|
|
679
|
-
self.worker.boss.config.slurm_args =
|
|
691
|
+
self.worker.boss.config.slurm_args = (
|
|
692
|
+
"--something --time=10:00:00 --somethingElse"
|
|
693
|
+
)
|
|
680
694
|
command = self.worker.prepareSbatch(1, 100, 5, "job5", None, None)
|
|
681
695
|
logger.debug("Command: %s", command)
|
|
682
696
|
assert "--partition=medium" in command
|
|
@@ -724,7 +738,9 @@ class SlurmTest(ToilTest):
|
|
|
724
738
|
detector = toil.batchSystems.slurm.any_option_detector([])
|
|
725
739
|
self.assertFalse(detector("--anything"))
|
|
726
740
|
|
|
727
|
-
detector = toil.batchSystems.slurm.any_option_detector(
|
|
741
|
+
detector = toil.batchSystems.slurm.any_option_detector(
|
|
742
|
+
[("foobar", "f"), "many-bothans", ("bazz-only", "B")]
|
|
743
|
+
)
|
|
728
744
|
|
|
729
745
|
self.assertTrue(detector("--foobar"))
|
|
730
746
|
self.assertTrue(detector("-f"))
|
|
@@ -733,5 +749,3 @@ class SlurmTest(ToilTest):
|
|
|
733
749
|
self.assertTrue(detector("-B"))
|
|
734
750
|
self.assertFalse(detector("--no-bazz"))
|
|
735
751
|
self.assertFalse(detector("--foo-bar=--bazz-only"))
|
|
736
|
-
|
|
737
|
-
|
toil/test/cwl/conftest.py
CHANGED
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
import json
|
|
18
18
|
import logging
|
|
19
19
|
from io import StringIO
|
|
20
|
-
from typing import Any
|
|
20
|
+
from typing import Any
|
|
21
21
|
|
|
22
22
|
from cwltest import utils
|
|
23
|
+
|
|
23
24
|
logger = logging.getLogger(__name__)
|
|
24
25
|
|
|
25
26
|
collect_ignore = ["spec"]
|
|
@@ -30,15 +31,13 @@ collect_ignore = ["spec"]
|
|
|
30
31
|
# See cwltool's reference implementation:
|
|
31
32
|
# https://github.com/common-workflow-language/cwltool/blob/05af6c1357c327b3146e9f5da40e7c0aa3e6d976/tests/cwl-conformance/cwltool-conftest.py
|
|
32
33
|
def pytest_cwl_execute_test(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
jobfile: Optional[str]
|
|
36
|
-
) -> Tuple[int, Optional[Dict[str, Any]]]:
|
|
34
|
+
config: utils.CWLTestConfig, processfile: str, jobfile: str | None
|
|
35
|
+
) -> tuple[int, dict[str, Any] | None]:
|
|
37
36
|
"""Use Toil to execute CWL tests (equivalent to running toil-cwl-runner)."""
|
|
38
37
|
from toil.cwl.cwltoil import main
|
|
39
38
|
|
|
40
39
|
stdout = StringIO()
|
|
41
|
-
argsl:
|
|
40
|
+
argsl: list[str] = [f"--outdir={config.outdir}"]
|
|
42
41
|
if config.runner_quiet:
|
|
43
42
|
argsl.append("--quiet")
|
|
44
43
|
elif config.verbose:
|