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>
91 lines
2.4 KiB
Python
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
|