Pyramid-level soft-signal rule — R1+R2+R3 core.
Classifies every tests/**/test_*.py function into unit /
integration / e2e based on attr-IO, fixture arguments, taint of
tmp_path, and import provenance (public vs internal). Mismatches
between the classified level and the folder the test lives in are
reported as findings.
R4 (conftest fixture-IO resolution) and R5 (mock neutralisation) are
stubbed here as identities — ticket #4b replaces the two placeholders.
Finding
Bases: BaseModel
One classification verdict for a single test function.
Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/pyramid_level.py
| Python |
|---|
| class Finding(BaseModel):
"""One classification verdict for a single test function."""
path: str
function: str
level: str
reason: str
current_level: str
has_real_io: bool
has_subprocess: bool
io_signals: list[str] = Field(default_factory=list)
imports_public: list[str] = Field(default_factory=list)
imports_internal: list[str] = Field(default_factory=list)
suggested_file: str = ""
severity: Severity = Severity.WARNING
model_config = {"extra": "forbid"}
|
PyramidCheckResult
Bases: CheckResult
:class:CheckResult subclass exposing findings and score.
Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/pyramid_level.py
| Python |
|---|
| class PyramidCheckResult(CheckResult):
""":class:`CheckResult` subclass exposing ``findings`` and ``score``."""
findings: list[Finding] = Field(default_factory=list)
score: int = 100
model_config = {"extra": "forbid"}
|
PyramidLevelRule
dataclass
Bases: ProjectRule
Report tests whose classified pyramid level mismatches their folder.
Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/pyramid_level.py
| Python |
|---|
| @register_rule("test_quality")
@dataclass
class PyramidLevelRule(ProjectRule):
"""Report tests whose classified pyramid level mismatches their folder."""
strict_mismatches: bool = True
@property
def rule_id(self) -> str:
"""Stable identifier for this rule."""
return "TEST_QUALITY_PYRAMID_LEVEL"
def check(self, project_path: Path) -> PyramidCheckResult:
"""Classify tests in ``project_path`` against their pyramid folder."""
tests_dir = project_path / "tests"
if not tests_dir.exists():
return PyramidCheckResult(
rule_id=self.rule_id,
passed=True,
message="no tests/ directory",
severity=Severity.INFO,
score=100,
)
all_findings = scan_package(project_path)
mismatches = [
f for f in all_findings if f.current_level not in ("root", f.level)
]
count = len(mismatches) if self.strict_mismatches else 0
score = max(0, 100 - count * _SCORE_PENALTY)
passed = count == 0
message = (
"pyramid levels match folder layout"
if passed
else f"{count} test(s) mis-located vs. classified pyramid level"
)
return PyramidCheckResult(
rule_id=self.rule_id,
passed=passed,
message=message,
severity=Severity.WARNING,
findings=all_findings,
score=score,
)
|
rule_id
property
Stable identifier for this rule.
check(project_path)
Classify tests in project_path against their pyramid folder.
Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/pyramid_level.py
| Python |
|---|
| def check(self, project_path: Path) -> PyramidCheckResult:
"""Classify tests in ``project_path`` against their pyramid folder."""
tests_dir = project_path / "tests"
if not tests_dir.exists():
return PyramidCheckResult(
rule_id=self.rule_id,
passed=True,
message="no tests/ directory",
severity=Severity.INFO,
score=100,
)
all_findings = scan_package(project_path)
mismatches = [
f for f in all_findings if f.current_level not in ("root", f.level)
]
count = len(mismatches) if self.strict_mismatches else 0
score = max(0, 100 - count * _SCORE_PENALTY)
passed = count == 0
message = (
"pyramid levels match folder layout"
if passed
else f"{count} test(s) mis-located vs. classified pyramid level"
)
return PyramidCheckResult(
rule_id=self.rule_id,
passed=passed,
message=message,
severity=Severity.WARNING,
findings=all_findings,
score=score,
)
|
scan_package(pkg_root)
Scan every test file under <pkg_root>/tests and return findings.
Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/pyramid_level.py
| Python |
|---|
| def scan_package(pkg_root: Path) -> list[Finding]:
"""Scan every test file under ``<pkg_root>/tests`` and return findings."""
tests_dir = pkg_root / "tests"
if not tests_dir.exists():
return []
pkg_prefixes = get_pkg_prefixes(pkg_root)
init_all = get_init_all(pkg_root)
findings: list[Finding] = []
for test_file, tree in iter_test_files(pkg_root):
if tree is None:
continue
findings.extend(
scan_test_file(test_file, tree, pkg_root, pkg_prefixes, init_all, tests_dir)
)
return findings
|
scan_test_file(test_file, tree, pkg_root, pkg_prefixes, init_all, tests_dir)
Classify every test_* function in tree and return findings.
Emits one :class:Finding per function whose classified level differs
from its folder-derived current level.
Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/pyramid_level.py
| Python |
|---|
| def scan_test_file( # noqa: PLR0913
test_file: Path,
tree: ast.Module,
pkg_root: Path,
pkg_prefixes: set[str],
init_all: set[str] | None,
tests_dir: Path,
) -> list[Finding]:
"""Classify every ``test_*`` function in *tree* and return findings.
Emits one :class:`Finding` per function whose classified level differs
from its folder-derived current level.
"""
ctx = _build_scan_context(
test_file, tree, pkg_root, pkg_prefixes, init_all, tests_dir
)
return [
_classify_test_function(ctx, node)
for node in ast.walk(tree)
if isinstance(node, ast.FunctionDef) and node.name.startswith("test_")
]
|