📝 Add `pytest-timeout` to fail hanging tests fast (#1195)
FEATURE
Thesis
Add
pytest-timeout to the test dependencies and enable a global per-test timeout, so a hanging test fails fast and names itself instead of stalling the job.[dependency-groups]
unit-test = [
# ...
"pytest-timeout>=2.4,<3",
]
[tool.pytest.ini_options]
# Fail hanging tests instead of blocking CI:
timeout = 30
timeout_method = "thread"
Tests that legitimately need longer raise their own budget with the marker:
@pytest.mark.timeout(120)
def test_something_genuinely_slow() -> None: ...
Reasoning
• Nothing currently bounds a hang. Only
.github/workflows/relator.yml sets timeout-minutes; test.yml, test-extras.yml, build-wheels.yml and codspeed.yml do not, so a single deadlocked test burns the GitHub Actions default of 6 hours per job — multiplied across the Python/Django matrix.• The suite has real hang-prone surface.
asyncio_mode = 'auto' means every async test can deadlock on an unawaited future; tests/test_integration/test_throttling/test_backends/test_redis_backend/ talks to a live Redis/Valkey; the streaming tests consume generators; the smoke tests spawn subprocesses. Today a wedged backend looks like "CI is slow", not "CI is broken".• Attribution. A job-level
timeout-minutes kills the run without telling you which test hung. pytest-timeout dumps the stack of the offending test and keeps going, which is the difference between a five-minute fix and a bisect.• Locally too. The same protection applies to
just unit, where a hang currently requires a manual Ctrl-C and offers no traceback.• Cheap. One pure-Python dev dependency, no runtime impact on the shipped package.
Notes / open questions
• It has to go in the
unit-test group specifically: that group is the minimal one installed by cibuildwheel, and with --strict-config in addopts an unknown timeout ini key would make pytest error out anywhere the plugin isn't installed.•
timeout_method = "thread" behaves the same on every platform (relevant for the Windows wheel jobs); signal gives a slightly nicer traceback but is POSIX-only. Happy to go with either.• The
30s default is a starting point — it should be set above the slowest current test so the change is a no-op for a green suite. Worth a --durations run to pick the number.#feature #good_first_issue #help_wanted #django_modern_rest
sent via relator