Skip to content

Test runner

test_runner

Agent-optimized test runner with structured output.

Wraps pytest with pytest-json-report to produce compact, token-efficient results for AI coding agents.

FailureDetail dataclass

Structured detail for a single test failure.

Source code in packages/axm-audit/src/axm_audit/core/test_runner.py
@dataclass
class FailureDetail:
    """Structured detail for a single test failure."""

    test: str
    """Full node ID, e.g. ``tests/unit/test_x.py::TestFoo::test_bar``."""

    error_type: str
    """Exception class name, e.g. ``AssertionError``."""

    message: str
    """One-line error message."""

    file: str
    """Relative file path."""

    line: int
    """Line number of the assertion / raising statement."""

    traceback: str
    """Short traceback (truncated to ``_MAX_TB_LINES``)."""
error_type instance-attribute

Exception class name, e.g. AssertionError.

file instance-attribute

Relative file path.

line instance-attribute

Line number of the assertion / raising statement.

message instance-attribute

One-line error message.

test instance-attribute

Full node ID, e.g. tests/unit/test_x.py::TestFoo::test_bar.

traceback instance-attribute

Short traceback (truncated to _MAX_TB_LINES).

TestReport dataclass

Compact test execution report.

Source code in packages/axm-audit/src/axm_audit/core/test_runner.py
@dataclass
class TestReport:
    """Compact test execution report."""

    passed: int = 0
    failed: int = 0
    errors: int = 0
    skipped: int = 0
    warnings: int = 0
    duration: float = 0.0
    coverage: float | None = None
    failures: list[FailureDetail] = field(default_factory=list)
    coverage_by_file: dict[str, float] | None = None

run_tests(project_path, *, mode='failures', files=None, markers=None, stop_on_first=True, last_coverage=None)

Run tests with agent-optimized structured output.

Parameters:

Name Type Description Default
project_path Path

Root of the project to test.

required
mode TestMode

Output mode — compact (summary only), failures (summary + failure details), delta (failures + coverage changes), targeted (failures for specific files/markers).

'failures'
files list[str] | None

Specific test files or paths to run.

None
markers list[str] | None

Pytest markers to filter (-m).

None
stop_on_first bool

Stop on first failure (-x).

True
last_coverage dict[str, float] | None

Previous per-file coverage dict for delta mode.

None

Returns:

Type Description
TestReport

Structured TestReport with mode-appropriate fields populated.

Source code in packages/axm-audit/src/axm_audit/core/test_runner.py
def run_tests(
    project_path: Path,
    *,
    mode: TestMode = "failures",
    files: list[str] | None = None,
    markers: list[str] | None = None,
    stop_on_first: bool = True,
    last_coverage: dict[str, float] | None = None,
) -> TestReport:
    """Run tests with agent-optimized structured output.

    Args:
        project_path: Root of the project to test.
        mode: Output mode — ``compact`` (summary only),
            ``failures`` (summary + failure details),
            ``delta`` (failures + coverage changes),
            ``targeted`` (failures for specific files/markers).
        files: Specific test files or paths to run.
        markers: Pytest markers to filter (``-m``).
        stop_on_first: Stop on first failure (``-x``).
        last_coverage: Previous per-file coverage dict for delta mode.

    Returns:
        Structured ``TestReport`` with mode-appropriate fields populated.
    """
    include_coverage = mode in ("compact", "failures", "delta")

    # Create temp files for reports
    report_tmp = tempfile.NamedTemporaryFile(
        suffix=".json", prefix="axm_report_", delete=False
    )
    report_path = Path(report_tmp.name)
    report_tmp.close()

    coverage_path: Path | None = None
    if include_coverage:
        cov_tmp = tempfile.NamedTemporaryFile(
            suffix=".json", prefix="axm_cov_", delete=False
        )
        coverage_path = Path(cov_tmp.name)
        cov_tmp.close()

    try:
        cmd = _build_pytest_cmd(
            report_path=report_path,
            coverage_path=coverage_path,
            files=files,
            markers=markers,
            stop_on_first=stop_on_first,
        )

        logger.debug("Running: %s", " ".join(cmd))
        run_in_project(
            cmd,
            project_path,
            with_packages=["pytest-json-report", "pytest-cov"],
            capture_output=True,
            text=True,
            check=False,
        )

        # Parse JSON report
        report_data = _parse_json_report(report_path)

        # Parse coverage
        total_cov: float | None = None
        per_file_cov: dict[str, float] = {}
        if coverage_path is not None:
            total_cov, per_file_cov = _parse_coverage(coverage_path)

        return _build_test_report(
            report_data=report_data,
            total_cov=total_cov,
            per_file_cov=per_file_cov,
            mode=mode,
            last_coverage=last_coverage,
        )

    finally:
        report_path.unlink(missing_ok=True)
        if coverage_path is not None:
            coverage_path.unlink(missing_ok=True)