|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import math |
3 | 4 | import os |
4 | 5 | import sys |
5 | 6 | from typing import TYPE_CHECKING |
@@ -92,15 +93,13 @@ def _auto_num_workers_psutil(config: pytest.Config) -> int | None: |
92 | 93 |
|
93 | 94 |
|
94 | 95 | def _auto_num_workers_os_sched_getaffinity(config: pytest.Config) -> int | None: |
95 | | - try: |
96 | | - from os import sched_getaffinity |
97 | | - |
| 96 | + sched_getaffinity = getattr(os, "sched_getaffinity", None) |
| 97 | + if sched_getaffinity is not None: |
98 | 98 | return len(sched_getaffinity(0)) |
99 | | - except ImportError: |
100 | | - if os.environ.get("TRAVIS") == "true": |
101 | | - # workaround https://github.com/pypy/pypy/issues/2375 |
102 | | - return 2 |
103 | | - return None |
| 99 | + if os.environ.get("TRAVIS") == "true": |
| 100 | + # workaround https://github.com/pypy/pypy/issues/2375 |
| 101 | + return 2 |
| 102 | + return None |
104 | 103 |
|
105 | 104 |
|
106 | 105 | def _auto_num_workers_os_multiprocessing_cpu_count(config: pytest.Config) -> int | None: |
@@ -141,6 +140,32 @@ def parse_numprocesses(s: str) -> int | Literal["auto", "logical"]: |
141 | 140 | return int(s) |
142 | 141 |
|
143 | 142 |
|
| 143 | +def parse_ramp_duration(s: str) -> float: |
| 144 | + value = s.strip() |
| 145 | + if not value: |
| 146 | + raise pytest.UsageError("--ramp requires a duration") |
| 147 | + |
| 148 | + unit = value[-1] if value[-1].isalpha() else "" |
| 149 | + number = value[:-1] if unit else value |
| 150 | + multipliers = {"": 1.0, "s": 1.0, "m": 60.0, "h": 3600.0} |
| 151 | + if unit not in multipliers or not number: |
| 152 | + raise pytest.UsageError( |
| 153 | + "--ramp duration must be a non-negative number with optional s, m, or h suffix" |
| 154 | + ) |
| 155 | + |
| 156 | + try: |
| 157 | + seconds = float(number) |
| 158 | + except ValueError as e: |
| 159 | + raise pytest.UsageError( |
| 160 | + "--ramp duration must be a non-negative number with optional s, m, or h suffix" |
| 161 | + ) from e |
| 162 | + |
| 163 | + if seconds < 0 or not math.isfinite(seconds): |
| 164 | + raise pytest.UsageError("--ramp duration must be a non-negative finite value") |
| 165 | + |
| 166 | + return seconds * multipliers[unit] |
| 167 | + |
| 168 | + |
144 | 169 | @pytest.hookimpl |
145 | 170 | def pytest_addoption(parser: pytest.Parser) -> None: |
146 | 171 | # 'Help' formatting (same rules as pytest's): |
@@ -178,6 +203,18 @@ def pytest_addoption(parser: pytest.Parser) -> None: |
178 | 203 | help="Maximum number of workers that can be restarted " |
179 | 204 | "when crashed (set to zero to disable this feature)", |
180 | 205 | ) |
| 206 | + group.addoption( |
| 207 | + "--ramp", |
| 208 | + action="store", |
| 209 | + default=0.0, |
| 210 | + dest="ramp", |
| 211 | + metavar="DURATION", |
| 212 | + type=parse_ramp_duration, |
| 213 | + help=( |
| 214 | + "Gradually start worker test execution over the given duration. " |
| 215 | + "Accepts seconds by default, or s, m, h suffixes." |
| 216 | + ), |
| 217 | + ) |
181 | 218 | group.addoption( |
182 | 219 | "--dist", |
183 | 220 | metavar="distmode", |
|
0 commit comments