Skip to content

Axm audit report plugin

axm_audit_report_plugin

Tiny pytest plugin that writes audit_test's structured result as JSON.

It is loaded by name (-p axm_audit_report_plugin) into the target's pytest, whose interpreter does not have axm-audit installed and must not need any third-party reporting plugin: this module imports nothing but the standard library and pytest, and uses only long-stable hooks (pytest 7.0 and later).

The destination comes from --axm-report-file=PATH; without it the plugin is inert. The JSON keeps the shape the runner has always consumed (the one pytest-json-report produced), restricted to what the runner reads:

  • summary: count per test outcome, total and collected (deselected when some were);
  • tests: per node nodeid, lineno, outcome and one entry per executed phase (setup / call / teardown) with duration, outcome, crash (path, lineno, message) and longrepr, the failure text bounded to head and tail;
  • collectors: the collectors that failed, with their bounded longrepr;
  • duration, root, exitcode, pytest_version and schema.

pytest_addoption(parser)

Declare the report destination option.

Source code in packages/axm-audit/src/axm_audit/_report_plugin/axm_audit_report_plugin.py
Python
def pytest_addoption(parser: pytest.Parser) -> None:
    """Declare the report destination option."""
    group = parser.getgroup("axm-audit", "axm-audit structured report")
    group.addoption(
        OPTION,
        dest="axm_report_file",
        default=None,
        help="write the structured test result to this JSON file",
    )

pytest_collectreport(report)

Record a collector that failed (syntax or import error in a test file).

Source code in packages/axm-audit/src/axm_audit/_report_plugin/axm_audit_report_plugin.py
Python
def pytest_collectreport(report: pytest.CollectReport) -> None:
    """Record a collector that failed (syntax or import error in a test file)."""
    if not report.failed:
        return
    _STATE.collectors.append(
        {
            "nodeid": report.nodeid,
            "outcome": report.outcome,
            "longrepr": _bounded(str(report.longrepr or "")),
        }
    )

pytest_deselected(items)

Count the items deselected after collection.

Source code in packages/axm-audit/src/axm_audit/_report_plugin/axm_audit_report_plugin.py
Python
def pytest_deselected(items: list[pytest.Item]) -> None:
    """Count the items deselected after collection."""
    _STATE.deselected += len(items)

pytest_runtest_logreport(report)

Record one phase of a test and fold it into the test's outcome.

Source code in packages/axm-audit/src/axm_audit/_report_plugin/axm_audit_report_plugin.py
Python
def pytest_runtest_logreport(report: pytest.TestReport) -> None:
    """Record one phase of a test and fold it into the test's outcome."""
    config = _STATE.config
    item = _STATE.tests.setdefault(
        report.nodeid,
        {"nodeid": report.nodeid, "lineno": report.location[1], "outcome": "passed"},
    )
    if config is not None:
        status = config.hook.pytest_report_teststatus(report=report, config=config)
        outcome = status[0]
        if outcome not in ("passed", ""):
            item["outcome"] = outcome
    item[report.when] = _stage(report)

pytest_sessionfinish(session, exitstatus)

Write the structured result to the requested file.

Source code in packages/axm-audit/src/axm_audit/_report_plugin/axm_audit_report_plugin.py
Python
def pytest_sessionfinish(session: pytest.Session, exitstatus: int) -> None:
    """Write the structured result to the requested file."""
    config = session.config
    if not _active(config) or _STATE.config is None:
        return
    payload = {
        "schema": SCHEMA,
        "pytest_version": pytest.__version__,
        "duration": time.time() - _STATE.started,
        "exitcode": int(exitstatus),
        "root": str(config.rootpath),
        "summary": _summary(session),
        "collectors": _STATE.collectors,
        "tests": list(_STATE.tests.values()),
    }
    path = str(config.getoption("axm_report_file"))
    scratch = f"{path}.{os.getpid()}.tmp"
    with open(scratch, "w", encoding="utf-8") as handle:
        json.dump(payload, handle, default=str)
    os.replace(scratch, path)

pytest_sessionstart(session)

Reset the recorded state for the session.

Source code in packages/axm-audit/src/axm_audit/_report_plugin/axm_audit_report_plugin.py
Python
def pytest_sessionstart(session: pytest.Session) -> None:
    """Reset the recorded state for the session."""
    global _STATE
    _STATE = _Session()
    _STATE.started = time.time()
    _STATE.config = session.config