Files
stack/tests/sem/test_state.py
kert 96b8ccf6df
Some checks failed
CI / skinny-install (aco) (push) Successful in 53s
CI / lint-test (push) Successful in 1m15s
CI / skinny-install (api) (push) Successful in 26s
CI / skinny-install (bcda) (push) Successful in 29s
CI / skinny-install (bib) (push) Successful in 31s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 30s
CI / skinny-install (cli) (push) Successful in 30s
CI / skinny-install (cms) (push) Successful in 26s
CI / skinny-install (conf) (push) Successful in 26s
CI / skinny-install (opps) (push) Successful in 28s
CI / skinny-install (perf) (push) Successful in 29s
CI / skinny-install (pfs) (push) Successful in 27s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Successful in 7s
CI / skinny-install (rex) (push) Successful in 27s
Infra CI / docs (push) Failing after 9s
Infra CI / api (push) Successful in 8s
Infra CI / mc (push) Successful in 6s
Package Supply Chain / pkg-supply-chain (push) Successful in 52s
CI / lint-test (pull_request) Successful in 1m18s
CI / skinny-install (aco) (pull_request) Successful in 44s
CI / skinny-install (api) (pull_request) Successful in 24s
CI / skinny-install (bcda) (pull_request) Successful in 32s
CI / skinny-install (bib) (pull_request) Successful in 30s
CI / skinny-install (bls) (pull_request) Successful in 25s
CI / skinny-install (ccw) (pull_request) Successful in 23s
CI / skinny-install (cli) (pull_request) Successful in 30s
CI / skinny-install (cms) (pull_request) Successful in 27s
CI / skinny-install (conf) (pull_request) Successful in 30s
CI / skinny-install (opps) (pull_request) Successful in 29s
CI / skinny-install (perf) (pull_request) Successful in 32s
CI / skinny-install (pfs) (pull_request) Successful in 33s
Infra CI / notebooks (pull_request) Successful in 7s
Infra CI / zotero (pull_request) Successful in 6s
Infra CI / docs (pull_request) Failing after 6s
CI / skinny-install (rex) (pull_request) Successful in 27s
Infra CI / api (pull_request) Successful in 6s
Infra CI / mc (pull_request) Successful in 6s
feat: sem — semantic coverage orchestration (AST + Ruff + ty + coverage)
4-layer architecture for tracking coverage as semantic AST nodes rather
than raw line numbers: parse → enrich → runtime → plan.  Stable node
identities, Ruff/ty diagnostic attachment, coverage mapping, priority
scoring heuristic, and drift-resistant rolling state persistence.

39 tests, ruff clean.
2026-03-26 14:37:54 -04:00

92 lines
2.9 KiB
Python

"""Tests for sem.state — rolling state persistence."""
from __future__ import annotations
from sem.nodes import NodeKind, SemanticNode, Span
from sem.state import (
apply_state,
load_state,
record_attempt,
save_state,
sync_state,
)
def _node(nid: str, src_hash: str = "abc123") -> SemanticNode:
return SemanticNode(
node_id=nid,
kind=NodeKind.FUNCTION,
span=Span(start_line=1, end_line=5),
module="mod",
source_hash=src_hash,
)
class TestPersistence:
def test_round_trip(self, tmp_path):
p = tmp_path / "state.json"
state = {"node_a": {"status": "covered", "attempts": 1}}
save_state(state, p)
loaded = load_state(p)
assert loaded["node_a"]["status"] == "covered"
def test_load_missing_file(self, tmp_path):
assert load_state(tmp_path / "nope.json") == {}
class TestSync:
def test_new_node_gets_fresh_entry(self):
nodes = [_node("n1")]
result = sync_state(nodes, {})
assert "n1" in result
assert result["n1"]["status"] == "unexplored"
def test_unchanged_node_keeps_history(self):
nodes = [_node("n1", "abc")]
old = {"n1": {"status": "covered", "attempts": 3, "source_hash": "abc"}}
result = sync_state(nodes, old)
assert result["n1"]["status"] == "covered"
assert result["n1"]["attempts"] == 3
def test_changed_hash_resets_status(self):
nodes = [_node("n1", "new_hash")]
old = {"n1": {"status": "covered", "attempts": 3, "source_hash": "old_hash"}}
result = sync_state(nodes, old)
assert result["n1"]["status"] == "unexplored"
assert result["n1"]["attempts"] == 0
def test_stale_entries_removed(self):
nodes = [_node("n1")]
old = {"n1": {"source_hash": "abc123"}, "n_gone": {"source_hash": "x"}}
result = sync_state(nodes, old)
assert "n_gone" not in result
class TestApplyState:
def test_pushes_state_to_nodes(self):
node = _node("n1")
state = {"n1": {"status": "partial", "attempts": 2}}
apply_state([node], state)
assert node.status == "partial"
assert node.attempts == 2
class TestRecordAttempt:
def test_increments_attempts(self):
state = {"n1": {"status": "unexplored", "attempts": 0, "notes": []}}
record_attempt(state, "n1", "partial", "needs None input")
assert state["n1"]["attempts"] == 1
assert state["n1"]["last_result"] == "partial"
assert "needs None input" in state["n1"]["notes"]
def test_covered_sets_status(self):
state = {"n1": {"status": "unexplored", "attempts": 0, "notes": []}}
record_attempt(state, "n1", "covered")
assert state["n1"]["status"] == "covered"
def test_new_node_creates_entry(self):
state = {}
record_attempt(state, "new", "partial")
assert "new" in state
assert state["new"]["attempts"] == 1