Files
grm/scripts/check_test_speed.py
T
Emil SimeonovandDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> d4ea2eef61
CI / quality (pull_request) Successful in 1m2s
CI / molecule-tests (0) (pull_request) Failing after 3m18s
CI / molecule-tests (2) (pull_request) Failing after 3m33s
CI / molecule-tests (1) (pull_request) Failing after 3m39s
fix: parse pytest output with warnings in check_test_speed
The regex only matched "N passed in X.XXs" but pytest can output
"N passed, M warnings in X.XXs". Updated regex to handle both.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-06-20 21:06:41 +02:00

91 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""Run unit tests and enforce a maximum execution-time budget.
Usage:
python3 scripts/check_test_speed.py [--max-seconds N]
"""
from __future__ import annotations
import re
import subprocess # nosec B404
import click
from gitea_runner_manager.i18n import _
DEFAULT_MAX_SECONDS = 2.0
TEST_COMMAND = ["make", "test-unit"]
_TIMING_RE = re.compile(r"(\d+) passed.* in ([0-9.]+)s")
def run_tests() -> tuple[str, str]:
"""Execute the unit-test suite and return (stdout, stderr)."""
result = subprocess.run( # nosec B603
TEST_COMMAND,
capture_output=True,
text=True,
check=False,
)
return result.stdout, result.stderr
def parse_duration(output: str) -> float:
"""Extract elapsed seconds from pytest summary line.
Raises:
click.ClickException: when the timing line cannot be found.
"""
for line in output.splitlines():
match = _TIMING_RE.search(line)
if match:
return float(match.group(2))
raise click.ClickException(_("Could not parse test execution time from output."))
def check_speed(duration: float, max_seconds: float) -> None:
"""Validate duration is within budget; raise on violation."""
if duration > max_seconds:
raise click.ClickException(
_(
"Unit tests too slow: {duration:.2f}s (max allowed: {max}s).\n"
" Fix: run 'make pytest-cov' to profile, then optimise slow tests.\n"
" Hint: avoid unnecessary imports, use lighter mocks, or cache fixtures.",
duration=duration,
max=max_seconds,
)
)
def main(max_seconds: float) -> None:
"""Run tests, parse timing, and enforce the budget."""
stdout, stderr = run_tests()
combined = stdout + "\n" + stderr
click.echo(combined, err=False)
duration = parse_duration(combined)
check_speed(duration, max_seconds)
click.echo(
_(
"Unit tests passed in {duration:.2f}s (under {max}s limit).",
duration=duration,
max=max_seconds,
)
)
@click.command()
@click.option(
"--max-seconds",
type=float,
default=DEFAULT_MAX_SECONDS,
show_default=True,
help="Maximum allowed execution time in seconds.",
)
def cli(max_seconds: float) -> None:
main(max_seconds)
if __name__ == "__main__": # pragma: no cover
cli() # pragma: no cover