Skip to content

shared

_shared

Shared AST primitives for test_quality rules.

Registry-neutral helpers ported from the detect_pyramid_level_v6 and triage_tautologies_v4 prototypes. Rules in this subpackage import from here; no @register_rule lives here.

analyze_imports(tree, pkg_prefixes, init_all, pkg_root)

Classify imports + collect IO module names.

Returns (public, internal, modules, has_private, io_module_names, io_signals).

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def analyze_imports(
    tree: ast.Module,
    pkg_prefixes: set[str],
    init_all: set[str] | None,
    pkg_root: Path,
) -> tuple[list[str], list[str], list[str], bool, set[str], list[str]]:
    """Classify imports + collect IO module names.

    Returns ``(public, internal, modules, has_private, io_module_names,
    io_signals)``.
    """
    scan = _ImportScan()
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and node.module:
            _process_import_from(node, pkg_prefixes, init_all, pkg_root, scan)
        elif isinstance(node, ast.Import):
            _process_import(node, scan)
    return (
        scan.public,
        scan.internal,
        scan.import_modules,
        scan.has_private,
        scan.io_module_names,
        scan.io_signals,
    )

canonical_filename(*, symbols_or_tuples, tier, single_binary)

Emit the canonical test_*.py filename for a tier's tuple.

For tier="integration", symbols_or_tuples is an iterable of first-party symbol names; the top-K=2 (already sorted) are snake-cased and joined by __ (PEP 8 module name; a single - would break Python imports). For tier="e2e", symbols_or_tuples is an iterable of (bin, sub) tuples; the same K=2 rule applies. When single_binary is not None, the redundant binary prefix is stripped: (axm-audit, audit) emits test_audit.py; (axm-audit, "") emits the bare binary test_axm_audit.py.

K=0 yields test_UNKNOWN.py — but the FILE_NAMING rule never emits K=0 findings (that case is NO_PACKAGE_SYMBOL's concern).

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def canonical_filename(
    *,
    symbols_or_tuples: object,
    tier: str,
    single_binary: str | None,
) -> str:
    """Emit the canonical ``test_*.py`` filename for a tier's tuple.

    For ``tier="integration"``, *symbols_or_tuples* is an iterable of
    first-party symbol names; the top-K=2 (already sorted) are snake-cased
    and joined by ``__`` (PEP 8 module name; a single ``-`` would break
    Python imports). For ``tier="e2e"``, *symbols_or_tuples* is an iterable
    of ``(bin, sub)`` tuples; the same K=2 rule applies. When *single_binary*
    is not None, the redundant binary prefix is stripped: ``(axm-audit,
    audit)`` emits ``test_audit.py``; ``(axm-audit, "")`` emits the bare
    binary ``test_axm_audit.py``.

    K=0 yields ``test_UNKNOWN.py`` — but the FILE_NAMING rule never emits
    K=0 findings (that case is NO_PACKAGE_SYMBOL's concern).
    """
    items = list(symbols_or_tuples)  # type: ignore[call-overload]
    if not items:
        return "test_UNKNOWN.py"
    if tier == "e2e":
        tokens = _e2e_tokens(items, single_binary)
    else:
        tokens = sorted({to_snake_token(s) for s in items if s})[:_FILE_NAMING_TOP_K]
    if not tokens:
        return "test_UNKNOWN.py"
    # Join tokens with ``__`` (PEP 8 module name) rather than ``-``: a
    # dash is invalid in Python identifiers, which breaks
    # ``from tests.<tier>.test_a-b import *`` re-exports (a real pattern
    # used to satisfy PRACTICE_TEST_MIRROR) and IDE module navigation.
    # ``importlib`` mode lets pytest *collect* dash files, but Python
    # imports of those modules still raise SyntaxError.
    return "test_" + "__".join(tokens) + ".py"

cli_invocation_tuple(*, test_func, mod_ast, project_scripts)

Count (bin, sub) invocations of declared scripts in test_func.

Recognises subprocess.run(["bin", "sub", ...]) shapes (including module aliases like python -m pkg ... and indirect list/string bindings already handled by _argv_from_call). The sub-command is the first non-flag argv element after the matched binary, or "" when the bare binary is invoked. Non-package invocations (git init, shutil.which-resolved plumbing) yield an empty counter.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def cli_invocation_tuple(
    *,
    test_func: ast.FunctionDef,
    mod_ast: ast.Module,
    project_scripts: set[str],
) -> Counter[tuple[str, str]]:
    """Count ``(bin, sub)`` invocations of declared scripts in *test_func*.

    Recognises ``subprocess.run(["bin", "sub", ...])`` shapes (including
    module aliases like ``python -m pkg ...`` and indirect list/string
    bindings already handled by ``_argv_from_call``). The sub-command is the
    first non-flag argv element after the matched binary, or ``""`` when the
    bare binary is invoked. Non-package invocations (``git init``,
    ``shutil.which``-resolved plumbing) yield an empty counter.
    """
    if not project_scripts:
        return Counter()
    counts: Counter[tuple[str, str]] = Counter()
    script_modules = {s.replace("-", "_"): s for s in project_scripts}
    for sub in ast.walk(test_func):
        if not isinstance(sub, ast.Call) or not _is_subprocess_call(sub):
            continue
        if _has_shell_true(sub):
            continue
        argv = _argv_from_call(sub, mod_ast)
        if not argv:
            continue
        result = _match_script_in_argv(argv, project_scripts, script_modules)
        if result is not None:
            counts[result] += 1
    return counts

collect_pkg_contract_classes(pkg_root)

Contract classes (Protocol/ABC/TypedDict) in pkg_root and sibling packages.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def collect_pkg_contract_classes(pkg_root: Path) -> set[str]:
    """Contract classes (Protocol/ABC/TypedDict) in *pkg_root* and sibling packages."""
    out: set[str] = _scan_pkg_for_contracts(pkg_root)

    parent = pkg_root.parent
    if parent.name == "packages" and parent.exists():
        for sibling in parent.iterdir():
            if not sibling.is_dir() or sibling == pkg_root:
                continue
            out |= _scan_pkg_for_contracts(sibling)

    axm_core = (
        pkg_root.parent.parent.parent / "axm-nexus" / "packages" / "axm"
        if parent.name == "packages"
        else None
    )
    if axm_core is not None and axm_core.exists():
        out |= _scan_pkg_for_contracts(axm_core)

    return out

collect_pkg_public_symbols(pkg_root)

Collect top-level public function, class, and constant names across src/.

Walks every *.py file under {pkg_root}/src and returns the union of non-underscore names defined at module top level — functions, async functions, classes, and simple / annotated assignments to Name targets.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def collect_pkg_public_symbols(pkg_root: Path) -> set[str]:
    """Collect top-level public function, class, and constant names across ``src/``.

    Walks every ``*.py`` file under ``{pkg_root}/src`` and returns the union of
    non-underscore names defined at module top level — functions, async
    functions, classes, and simple / annotated assignments to ``Name`` targets.
    """
    out: set[str] = set()
    for path in _iter_src_py(pkg_root):
        tree = _parse_cached(path)
        if tree is None:
            continue
        for node in tree.body:
            out.update(_public_names_from_node(node))
    return out

current_level_from_path(test_file, tests_dir)

Map test_file to its pyramid level based on its location.

Parameters:

Name Type Description Default
test_file Path

Test file path to classify.

required
tests_dir Path

Root tests directory used to compute the relative path.

required

Returns:

Type Description
str

One of "unit", "integration", "e2e", or "root" when

str

the level cannot be inferred. functional folders are mapped to

str

"integration" for backward compatibility.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def current_level_from_path(test_file: Path, tests_dir: Path) -> str:
    """Map ``test_file`` to its pyramid level based on its location.

    Args:
        test_file: Test file path to classify.
        tests_dir: Root tests directory used to compute the relative path.

    Returns:
        One of ``"unit"``, ``"integration"``, ``"e2e"``, or ``"root"`` when
        the level cannot be inferred. ``functional`` folders are mapped to
        ``"integration"`` for backward compatibility.
    """
    try:
        rel = test_file.relative_to(tests_dir)
    except ValueError:
        return "root"
    parts = rel.parts
    if len(parts) > 1:
        first = parts[0]
        if first in ("unit", "integration", "e2e"):
            return first
        if first == "functional":
            return "integration"
    return "root"

decorator_has_marker(dec, marker_name)

True when dec is @pytest.mark.<marker_name> (or aliased).

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def decorator_has_marker(dec: ast.expr, marker_name: str) -> bool:
    """True when *dec* is ``@pytest.mark.<marker_name>`` (or aliased)."""
    target = dec.func if isinstance(dec, ast.Call) else dec
    if isinstance(target, ast.Attribute) and target.attr == marker_name:
        return True
    return isinstance(target, ast.Name) and target.id == marker_name

detect_real_io(tree)

File-scope I/O detection from a parsed test module.

Scans the module for three kinds of evidence that a test performs real I/O: fixtures listed in _IO_FIXTURES consumed by test_* functions, dotted calls listed in _IO_CALLS (e.g. subprocess.run, pathlib.Path.write_text), and CLI runner invocations such as CliRunner().invoke(...). Attribute-IO inside helpers is intentionally out of scope — see :func:func_attr_io_transitive.

Returns:

Type Description
bool

(has_io, has_subprocess, signals). has_io is True when any

bool

signal was found. has_subprocess is True when a

list[str]

subprocess.* call or a CLI runner was seen. signals is the

tuple[bool, bool, list[str]]

list of evidence strings (fixture:<name>, call:<dotted>,

tuple[bool, bool, list[str]]

cli:<runner>) preserving discovery order.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def detect_real_io(tree: ast.Module) -> tuple[bool, bool, list[str]]:
    """File-scope I/O detection from a parsed test module.

    Scans the module for three kinds of evidence that a test performs real I/O:
    fixtures listed in ``_IO_FIXTURES`` consumed by ``test_*`` functions,
    dotted calls listed in ``_IO_CALLS`` (e.g. ``subprocess.run``,
    ``pathlib.Path.write_text``), and CLI runner invocations such as
    ``CliRunner().invoke(...)``. Attribute-IO inside helpers is intentionally
    out of scope — see :func:`func_attr_io_transitive`.

    Returns:
        ``(has_io, has_subprocess, signals)``. ``has_io`` is ``True`` when any
        signal was found. ``has_subprocess`` is ``True`` when a
        ``subprocess.*`` call or a CLI runner was seen. ``signals`` is the
        list of evidence strings (``fixture:<name>``, ``call:<dotted>``,
        ``cli:<runner>``) preserving discovery order.
    """
    fixture_signals = _fixture_arg_signals(tree)
    sub_from_calls, call_signals = _io_call_signals(tree)
    sub_from_cli, cli_signals = _cli_runner_signals(tree)

    signals = fixture_signals + call_signals + cli_signals
    has_io = bool(signals)
    has_subprocess = sub_from_calls or sub_from_cli
    return has_io, has_subprocess, signals

extract_mock_targets(func)

Collect dotted mock/patch targets + mock-factory:Mock/... markers.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def extract_mock_targets(func: ast.FunctionDef) -> list[str]:
    """Collect dotted mock/patch targets + ``mock-factory:Mock/...`` markers."""
    out: list[str] = []
    for node in ast.walk(func):
        if not isinstance(node, ast.Call):
            continue
        _patch_call_targets(node, out)
        qual = _dotted_of(node.func) or ""
        leaf = qual.rsplit(".", 1)[-1] if qual else ""
        if leaf in _MOCK_FACTORIES:
            out.append(f"mock-factory:{leaf}")
    return out

file_has_module_marker(tree, marker_name)

True when pytestmark = pytest.mark.<marker_name> is set at module level.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def file_has_module_marker(tree: ast.Module, marker_name: str) -> bool:
    """True when ``pytestmark = pytest.mark.<marker_name>`` is set at module level."""
    for stmt in tree.body:
        if not isinstance(stmt, ast.Assign):
            continue
        if not any(
            isinstance(t, ast.Name) and t.id == "pytestmark" for t in stmt.targets
        ):
            continue
        if value_marks_node(stmt.value, marker_name):
            return True
    return False

first_party_symbol_counts(*, test_func, mod_ast, pkg_prefixes)

Count direct references to first-party symbols inside test_func.

Aliases are resolved from the module's imports (from pkg.x import Y or import pkg.x as y). Per the FILE_NAMING design, only the bare test body is walked — no helper closure — because tuple emission must reflect direct usage frequency, not transitive reachability.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def first_party_symbol_counts(
    *,
    test_func: ast.FunctionDef,
    mod_ast: ast.Module,
    pkg_prefixes: set[str],
) -> Counter[str]:
    """Count direct references to first-party symbols inside *test_func*.

    Aliases are resolved from the module's imports (``from pkg.x import Y``
    or ``import pkg.x as y``). Per the FILE_NAMING design, only the bare
    test body is walked — no helper closure — because tuple emission must
    reflect direct usage frequency, not transitive reachability.
    """
    aliases = _collect_first_party_aliases(mod_ast, pkg_prefixes)
    if not aliases:
        return Counter()
    known = set(aliases)
    counts: Counter[str] = Counter()
    for sub in ast.walk(test_func):
        name = _ref_name(sub) if isinstance(sub, (ast.Name, ast.Attribute)) else None
        if name is not None and name in known:
            counts[name] += 1
    return counts

fixture_does_io(fix_name, fixtures, visited, depth)

True if fixture body does real I/O.

Walks the fixture body for attr-IO, _IO_CALLS matches and transitive tmp_path deps; depth-bounded by _FIXTURE_DEPTH_LIMIT.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def fixture_does_io(
    fix_name: str,
    fixtures: dict[str, ast.FunctionDef],
    visited: set[str],
    depth: int,
) -> bool:
    """True if fixture body does real I/O.

    Walks the fixture body for attr-IO, ``_IO_CALLS`` matches and
    transitive ``tmp_path`` deps; depth-bounded by ``_FIXTURE_DEPTH_LIMIT``.
    """
    if fix_name in visited or depth >= _FIXTURE_DEPTH_LIMIT:
        return False
    visited.add(fix_name)
    fdef = fixtures.get(fix_name)
    if fdef is None:
        return False
    return _fixture_has_direct_io(fdef) or _fixture_calls_io_fixture(
        fdef, fix_name, fixtures, visited, depth
    )

func_attr_io_transitive(func, helpers, max_depth=2)

Attr-IO signals over the function subtree + transitively reachable helpers.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def func_attr_io_transitive(
    func: ast.FunctionDef,
    helpers: dict[str, ast.FunctionDef],
    max_depth: int = 2,
) -> list[str]:
    """Attr-IO signals over the function subtree + transitively reachable helpers."""
    visited: set[str] = set()
    sigs: list[str] = list(_attr_signals_in_node(func))

    frontier = _names_called_in(func) & set(helpers.keys())
    depth = 1
    while frontier and depth <= max_depth:
        next_frontier: set[str] = set()
        for name in frontier:
            if name in visited:
                continue
            visited.add(name)
            sigs.extend(_attr_signals_in_node(helpers[name]))
            next_frontier |= _names_called_in(helpers[name]) & set(helpers.keys())
        frontier = next_frontier - visited
        depth += 1
    return sigs

get_init_all(pkg_root)

Read __all__ from the first src/<pkg>/__init__.py that defines it.

Parameters:

Name Type Description Default
pkg_root Path

Repository root expected to follow the src/ layout.

required

Returns:

Type Description
set[str] | None

Set of exported names, or None when no top-level package

set[str] | None

declares __all__ (or src is missing).

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def get_init_all(pkg_root: Path) -> set[str] | None:
    """Read ``__all__`` from the first ``src/<pkg>/__init__.py`` that defines it.

    Args:
        pkg_root: Repository root expected to follow the ``src/`` layout.

    Returns:
        Set of exported names, or ``None`` when no top-level package
        declares ``__all__`` (or ``src`` is missing).
    """
    src_dir = pkg_root / "src"
    if not src_dir.exists():
        return None
    for pkg_dir in src_dir.iterdir():
        if not pkg_dir.is_dir() or pkg_dir.name.startswith("."):
            continue
        init = pkg_dir / "__init__.py"
        if not init.exists():
            continue
        tree = _parse_cached(init)
        if tree is None:
            continue
        found = _extract_all_from_tree(tree)
        if found is not None:
            return found
    return None

get_module_all(pkg_root, dotted)

Resolve __all__ for dotted module within the src tree.

Parameters:

Name Type Description Default
pkg_root Path

Repository root expected to follow the src/ layout.

required
dotted str

Dotted module path relative to src (e.g. pkg.sub).

required

Returns:

Type Description
set[str] | None

Set of exported names, or None if the module is missing or does

set[str] | None

not declare __all__.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def get_module_all(pkg_root: Path, dotted: str) -> set[str] | None:
    """Resolve ``__all__`` for ``dotted`` module within the ``src`` tree.

    Args:
        pkg_root: Repository root expected to follow the ``src/`` layout.
        dotted: Dotted module path relative to ``src`` (e.g. ``pkg.sub``).

    Returns:
        Set of exported names, or ``None`` if the module is missing or does
        not declare ``__all__``.
    """
    src_dir = pkg_root / "src"
    rel = dotted.replace(".", "/")
    candidates = [src_dir / (rel + ".py"), src_dir / rel / "__init__.py"]
    for cand in candidates:
        if not cand.exists():
            continue
        tree = _parse_cached(cand)
        if tree is None:
            continue
        found = _extract_all_from_tree(tree)
        if found is not None:
            return found
    return None

get_pkg_prefixes(pkg_root)

Return top-level package directory names under <pkg_root>/src.

Parameters:

Name Type Description Default
pkg_root Path

Repository root expected to follow the src/ layout.

required

Returns:

Type Description
set[str]

Set of directory names directly under src, excluding hidden

set[str]

entries. Empty when src is missing.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def get_pkg_prefixes(pkg_root: Path) -> set[str]:
    """Return top-level package directory names under ``<pkg_root>/src``.

    Args:
        pkg_root: Repository root expected to follow the ``src/`` layout.

    Returns:
        Set of directory names directly under ``src``, excluding hidden
        entries. Empty when ``src`` is missing.
    """
    src_dir = pkg_root / "src"
    if not src_dir.exists():
        return set()
    return {
        d.name for d in src_dir.iterdir() if d.is_dir() and not d.name.startswith(".")
    }

has_in_package_subprocess_invocation(*, call, module_ast, project_scripts)

Return true when call invokes a declared package script.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def has_in_package_subprocess_invocation(
    *,
    call: ast.Call,
    module_ast: ast.Module,
    project_scripts: set[str],
) -> bool:
    """Return true when *call* invokes a declared package script."""
    if not project_scripts:
        return False
    argv = _argv_from_call(call, module_ast)
    if argv is None:
        return False
    return _argv_contains_package_entrypoint(argv, project_scripts)

is_import_smoke_test(func)

Detect an import + weak-assert smoke test.

Returns True when the function body (docstrings excluded, ≤ 4 stmts) contains at least one import/from … import … statement and at least one weak assertion (assert name, assert x is not None, assert isinstance(...), assert callable(...), or self.assertIsNotNone(...)), with no statement stronger than these.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def is_import_smoke_test(func: ast.FunctionDef) -> bool:
    """Detect an import + weak-assert smoke test.

    Returns True when the function body (docstrings excluded, ≤ 4 stmts)
    contains at least one ``import``/``from … import …`` statement and at
    least one weak assertion (``assert name``, ``assert x is not None``,
    ``assert isinstance(...)``, ``assert callable(...)``, or
    ``self.assertIsNotNone(...)``), with no statement stronger than these.
    """
    body = [s for s in func.body if not _is_docstring_stmt(s)]
    if len(body) > _SMOKE_BODY_BUDGET:
        return False
    kinds = {_classify_smoke_stmt(stmt) for stmt in body}
    return "import" in kinds and "weak" in kinds and not (kinds & {"strong", "other"})

iter_test_files(pkg_root)

Yield (path, ast) for every tests/**/test_*.py.

Skips tests/fixtures/ — by AXM convention that directory holds static test data (corpora, snapshots, baselines) consumed by real tests; pytest excludes it from collection via collect_ignore_glob, and the test_quality rules must do the same so corpus files don't get flagged as pyramid / naming / tautology offenders.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def iter_test_files(pkg_root: Path) -> Iterator[tuple[Path, ast.Module | None]]:
    """Yield ``(path, ast)`` for every ``tests/**/test_*.py``.

    Skips ``tests/fixtures/`` — by AXM convention that directory holds
    static test data (corpora, snapshots, baselines) consumed by real
    tests; pytest excludes it from collection via ``collect_ignore_glob``,
    and the test_quality rules must do the same so corpus files don't
    get flagged as pyramid / naming / tautology offenders.
    """
    tests_dir = pkg_root / "tests"
    if not tests_dir.exists():
        return
    fixtures_dir = tests_dir / "fixtures"
    for test_file in sorted(tests_dir.rglob("test_*.py")):
        if fixtures_dir in test_file.parents:
            continue
        yield test_file, _parse_cached(test_file)

iter_test_funcs(tree)

Yield top-level test_* functions and Test*-class test methods.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def iter_test_funcs(tree: ast.Module) -> list[ast.FunctionDef]:
    """Yield top-level ``test_*`` functions and ``Test*``-class test methods."""
    funcs: list[ast.FunctionDef] = []
    for node in tree.body:
        if isinstance(node, ast.FunctionDef) and node.name.startswith("test_"):
            funcs.append(node)
        elif isinstance(node, ast.ClassDef) and node.name.startswith("Test"):
            for child in node.body:
                if isinstance(child, ast.FunctionDef) and child.name.startswith(
                    "test_"
                ):
                    funcs.append(child)
    return funcs

load_project_scripts(pkg_root)

Return scripts declared by [project.scripts] in pyproject.toml.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def load_project_scripts(pkg_root: Path) -> set[str]:
    """Return scripts declared by ``[project.scripts]`` in pyproject.toml."""
    pyproject = pkg_root / "pyproject.toml"
    if not pyproject.exists():
        return set()
    with pyproject.open("rb") as handle:
        data = tomllib.load(handle)
    scripts = data.get("project", {}).get("scripts", {})
    if not isinstance(scripts, dict):
        return set()
    return {name for name in scripts if isinstance(name, str)}

target_matches_io(target)

Return True when target references a known I/O call or module.

Parameters:

Name Type Description Default
target str

Dotted call expression captured from the AST (e.g. pathlib.Path.read_text).

required

Returns:

Type Description
bool

True when the leaf call name, the full dotted target, or any of

bool

its dotted segments matches the package's I/O catalog; False

bool

otherwise (including for empty input).

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def target_matches_io(target: str) -> bool:
    """Return ``True`` when ``target`` references a known I/O call or module.

    Args:
        target: Dotted call expression captured from the AST (e.g.
            ``pathlib.Path.read_text``).

    Returns:
        ``True`` when the leaf call name, the full dotted target, or any of
        its dotted segments matches the package's I/O catalog; ``False``
        otherwise (including for empty input).
    """
    if not target:
        return False
    if target in _IO_CALLS:
        return True
    for call in _IO_CALLS:
        if target.endswith("." + call):
            return True
    leaf_io_mods = {m.split(".")[-1] for m in _IO_MODULES}
    tokens = target.split(".")
    return any(tok in leaf_io_mods for tok in tokens)

test_invokes_in_package_script(*, test_func, module_ast, project_scripts)

True when the test's closure invokes a declared package script.

Criterion (b) of TEST_QUALITY_NO_PACKAGE_SYMBOL. Recognises subprocess.run(["script", ...]) (also python -m pkg ...) and CliRunner().invoke(app, [...]) shapes. The walk uses the same intra-module closure as criterion (a) so a helper-wrapped invocation still counts.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def test_invokes_in_package_script(
    *,
    test_func: ast.FunctionDef,
    module_ast: ast.Module,
    project_scripts: set[str],
) -> bool:
    """True when the test's closure invokes a declared package script.

    Criterion (b) of TEST_QUALITY_NO_PACKAGE_SYMBOL. Recognises
    ``subprocess.run(["script", ...])`` (also ``python -m pkg ...``) and
    ``CliRunner().invoke(app, [...])`` shapes. The walk uses the same
    intra-module closure as criterion (a) so a helper-wrapped invocation
    still counts.
    """
    if not project_scripts:
        return False
    mod_funcs = _module_level_funcs_by_name(module_ast)
    mod_classes = _module_level_classes_by_name(module_ast)
    closure = _closure_nodes_for_test(test_func, mod_funcs, mod_classes)
    for call in _calls_in_nodes(closure):
        if _is_subprocess_call(call) and has_in_package_subprocess_invocation(
            call=call,
            module_ast=module_ast,
            project_scripts=project_scripts,
        ):
            return True
        if _is_clirunner_invoke_call(call) and call.args:
            return True
    return False

test_invokes_inline_python_script(*, test_func, module_ast, pkg_prefixes)

True when the test drives the package via python -c "<script>".

A black-box e2e idiom: the test spawns a subprocess running an inline driver script (subprocess.run([sys.executable, "-c", script, ...])) whose string body imports a first-party package. Criterion (a) misses it because the first-party imports live inside a string literal (not the test's AST closure); the declared-script branch of criterion (b) misses it because the entrypoint is inline code, not a [project.scripts] entry or python -m pkg. This helper closes that gap, so such a test counts as exercising the package (criterion (b), subprocess tier).

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def test_invokes_inline_python_script(
    *,
    test_func: ast.FunctionDef,
    module_ast: ast.Module,
    pkg_prefixes: set[str],
) -> bool:
    """True when the test drives the package via ``python -c "<script>"``.

    A black-box e2e idiom: the test spawns a subprocess running an inline
    driver script (``subprocess.run([sys.executable, "-c", script, ...])``)
    whose string body imports a first-party package. Criterion (a) misses it
    because the first-party imports live inside a string literal (not the
    test's AST closure); the declared-script branch of criterion (b) misses
    it because the entrypoint is inline code, not a ``[project.scripts]``
    entry or ``python -m pkg``. This helper closes that gap, so such a test
    counts as exercising the package (criterion (b), subprocess tier).
    """
    if not pkg_prefixes:
        return False
    mod_funcs = _module_level_funcs_by_name(module_ast)
    mod_classes = _module_level_classes_by_name(module_ast)
    closure = _closure_nodes_for_test(test_func, mod_funcs, mod_classes)
    for call in _calls_in_nodes(closure):
        if not _is_subprocess_call(call):
            continue
        for script in _inline_scripts_in_call(call, module_ast):
            if _string_imports_first_party(script, pkg_prefixes):
                return True
    return False

test_is_in_lazy_import_context(func, tree_module, test_file)

Detect when the import itself is the system-under-test.

Returns True when the surrounding test file, class, or module docstring signals that the test exists to verify lazy/optional imports — in which case bare import statements inside the test body must not be flagged as smoke tests.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def test_is_in_lazy_import_context(
    func: ast.FunctionDef,
    tree_module: ast.Module,
    test_file: Path,
) -> bool:
    """Detect when the import itself is the system-under-test.

    Returns True when the surrounding test file, class, or module
    docstring signals that the test exists to verify lazy/optional
    imports — in which case bare ``import`` statements inside the
    test body must not be flagged as smoke tests.
    """
    return (
        _has_lazy_filename(test_file)
        or _has_lazy_classname(func, tree_module)
        or _has_lazy_module_docstring(tree_module)
    )

test_references_first_party(*, test_func, module_ast, pkg_prefixes)

True when test_func exercises a first-party Python symbol.

Criterion (a) of the TEST_QUALITY_NO_PACKAGE_SYMBOL rule. A symbol is "exercised" if it is referenced anywhere in the intra-module closure of test_func (its body + bodies of every top-level helper it calls transitively), or indirectly via a fixture whose return-type annotation or return/yield expression resolves to a first-party alias.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def test_references_first_party(
    *,
    test_func: ast.FunctionDef,
    module_ast: ast.Module,
    pkg_prefixes: set[str],
) -> bool:
    """True when *test_func* exercises a first-party Python symbol.

    Criterion (a) of the TEST_QUALITY_NO_PACKAGE_SYMBOL rule. A symbol is
    "exercised" if it is referenced anywhere in the intra-module closure
    of *test_func* (its body + bodies of every top-level helper it calls
    transitively), or indirectly via a fixture whose return-type annotation
    or return/yield expression resolves to a first-party alias.
    """
    aliases = _collect_first_party_aliases(module_ast, pkg_prefixes)
    if not aliases:
        return False
    known: set[str] = set(aliases)
    mod_funcs = _module_level_funcs_by_name(module_ast)
    mod_classes = _module_level_classes_by_name(module_ast)
    closure = _closure_nodes_for_test(test_func, mod_funcs, mod_classes)
    if any(_walk_touches_known(node, known) for node in closure):
        return True
    fixture_map = _collect_first_party_fixture_map(module_ast, known)
    if not fixture_map:
        return False
    params = _func_param_names(test_func)
    return any(name in params for name in fixture_map)

to_snake_token(name)

Normalize a PascalCase / kebab-case token to snake_case.

Used to compose canonical filenames. Empty input returns empty.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def to_snake_token(name: str) -> str:
    """Normalize a PascalCase / kebab-case token to snake_case.

    Used to compose canonical filenames. Empty input returns empty.
    """
    if not name:
        return name
    name = name.replace("-", "_")
    out: list[str] = []
    for i, ch in enumerate(name):
        if (
            ch.isupper()
            and i > 0
            and (name[i - 1].islower() or (i + 1 < len(name) and name[i + 1].islower()))
        ):
            out.append("_")
        out.append(ch.lower())
    return "".join(out).lstrip("_")

value_marks_node(value, marker_name)

True when value is pytest.mark.<marker_name> (with or without a reason argument).

Also matches a list/tuple containing the marker.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/_shared.py
Python
def value_marks_node(value: ast.AST, marker_name: str) -> bool:
    """True when *value* is ``pytest.mark.<marker_name>`` (with or without a
    reason argument).

    Also matches a list/tuple containing the marker.
    """
    if isinstance(value, ast.Call):
        value = value.func
    if isinstance(value, ast.Attribute) and value.attr == marker_name:
        return True
    if isinstance(value, (ast.List, ast.Tuple)):
        return any(value_marks_node(elt, marker_name) for elt in value.elts)
    return False