Files
stack/tests/sem/test_plan.py
kert cb530c25d9 chore: snapshot WIP + fix cross-test conf pollution
Bundled commit across unrelated threads: perf collector/meter/logs/export
updates, SSO bootstrap and traefik/nginx/gitea infra, dev backend scripts
(gitea/github/woodpecker), diag test updates, and misc config.

Also fix test pollution: five tests were doing raw sys.modules.pop("conf")
to simulate ImportError on `import conf`, but raw dict mutation isn't
tracked by monkeypatch and leaked the eviction into subsequent tests.
Downstream tests that did `from conf import secret` at module level held
references to the pre-eviction conf, while re-imports inside tests got a
fresh conf, so patches to conf.cfg._data didn't land on the secret()
closure's cfg — causing tests/conf/test_conf.py::TestSecret and
tests/conf/test_connect.py::TestDuckdb::test_custom_db_name to fail.

Fix: use monkeypatch.delitem(sys.modules, "conf", raising=False) so the
eviction is reverted on teardown. Applied to test_diag_ci.py, test_init.py,
test_tracer.py, and test_resource.py (two sites).
2026-04-10 13:09:14 -04:00

138 lines
5.0 KiB
Python

"""Tests for sem.plan — prioritisation and target selection."""
from __future__ import annotations
from sem.nodes import NodeKind, RuntimeInfo, SemanticNode, Span
from sem.plan import next_targets, rank_nodes, score_node
def _node(
kind: NodeKind = NodeKind.BRANCH_IF,
hit: bool = False,
ruff: list[str] | None = None,
ty: list[str] | None = None,
attempts: int = 0,
module: str = "mod",
symbol_ctx: list[str] | None = None,
) -> SemanticNode:
return SemanticNode(
node_id=f"{module}::{'::'.join(symbol_ctx or [])}::{kind.value}[0]",
kind=kind,
span=Span(start_line=1, end_line=5),
module=module,
symbol_context=symbol_ctx or [],
ruff_codes=ruff or [],
ty_codes=ty or [],
runtime=RuntimeInfo(hit=hit),
attempts=attempts,
)
class TestScoring:
def test_uncovered_branch_scores_high(self):
n = _node(kind=NodeKind.BRANCH_IF, hit=False)
assert score_node(n, [n]) >= 5.0
def test_covered_node_penalised(self):
n = _node(hit=True)
assert score_node(n, [n]) < 0
def test_ty_codes_boost(self):
base = _node(kind=NodeKind.FUNCTION, hit=False)
with_ty = _node(kind=NodeKind.FUNCTION, hit=False, ty=["possibly-none"])
assert score_node(with_ty, [with_ty]) > score_node(base, [base])
def test_ruff_codes_boost(self):
base = _node(kind=NodeKind.FUNCTION, hit=False)
with_ruff = _node(kind=NodeKind.FUNCTION, hit=False, ruff=["B904"])
assert score_node(with_ruff, [with_ruff]) > score_node(base, [base])
def test_prior_failures_penalised(self):
fresh = _node(attempts=0)
stale = _node(attempts=3)
assert score_node(fresh, [fresh]) > score_node(stale, [stale])
def test_exception_handler_scores_high(self):
n = _node(kind=NodeKind.EXCEPT_HANDLER, hit=False)
assert score_node(n, [n]) >= 3.0
class TestRanking:
def test_rank_order(self):
high = _node(kind=NodeKind.BRANCH_IF, hit=False, ty=["x"])
low = _node(kind=NodeKind.FUNCTION, hit=True)
ranked = rank_nodes([low, high])
assert ranked[0] is high
def test_next_targets_excludes_covered(self):
covered = _node(hit=True)
uncovered = _node(hit=False, kind=NodeKind.BRANCH_IF)
targets = next_targets([covered, uncovered], limit=5)
assert all(not t.runtime.hit for t in targets)
def test_next_targets_respects_limit(self):
nodes = [_node(hit=False, kind=NodeKind.BRANCH_IF) for _ in range(20)]
targets = next_targets(nodes, limit=3)
assert len(targets) <= 3
class TestFunctionCoverageRatio:
def test_partial_coverage_boosts_score(self):
"""Nodes in partially-tested functions get a +3 boost."""
from sem.plan import _function_coverage_ratio
# Two siblings in same function — one hit, one not
hit_node = _node(
kind=NodeKind.BRANCH_IF, hit=True, module="m", symbol_ctx=["func_a"]
)
miss_node = _node(
kind=NodeKind.BRANCH_ELSE, hit=False, module="m", symbol_ctx=["func_a"]
)
all_nodes = [hit_node, miss_node]
ratio = _function_coverage_ratio(miss_node, all_nodes)
assert 0.0 < ratio < 1.0 # partial coverage
# Score should include partial-function bonus (+3)
score = score_node(miss_node, all_nodes)
# branch_if uncovered=5, partial=3 = 8 minimum
assert score >= 8.0
def test_no_symbol_context_returns_zero(self):
from sem.plan import _function_coverage_ratio
node = _node(kind=NodeKind.FUNCTION, hit=False, symbol_ctx=[])
assert _function_coverage_ratio(node, [node]) == 0.0
def test_all_covered_ratio_is_one(self):
from sem.plan import _function_coverage_ratio
n1 = _node(kind=NodeKind.BRANCH_IF, hit=True, module="m", symbol_ctx=["fn"])
n2 = _node(kind=NodeKind.BRANCH_ELSE, hit=True, module="m", symbol_ctx=["fn"])
assert _function_coverage_ratio(n1, [n1, n2]) == 1.0
def test_node_not_in_all_nodes_returns_zero(self):
"""When the node has a function context but no siblings match, return 0."""
from sem.plan import _function_coverage_ratio
# Node has symbol_ctx ["lonely"] but all_nodes only has nodes from "other"
node = _node(
kind=NodeKind.BRANCH_IF, hit=False, module="m", symbol_ctx=["lonely"]
)
unrelated = _node(
kind=NodeKind.BRANCH_IF, hit=True, module="m", symbol_ctx=["other"]
)
assert _function_coverage_ratio(node, [unrelated]) == 0.0
def test_rank_nodes_deduplicates(self):
"""rank_nodes scores and sorts without error."""
nodes = [
_node(kind=NodeKind.BRANCH_IF, hit=False, ty=["x"]),
_node(kind=NodeKind.FUNCTION, hit=True),
_node(kind=NodeKind.EXCEPT_HANDLER, hit=False),
]
ranked = rank_nodes(nodes)
assert len(ranked) == 3
# Highest priority first
assert ranked[0].priority >= ranked[1].priority >= ranked[2].priority