mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-26 16:26:42 +00:00
test(build): pin shrink-guard safety and shared-stub survival for orphan sweep (#2807)
Documents the target-only invariant that makes a single sweep pass sufficient, and adds two tests: the sweep does not trip the #479 shrink guard (swept source-less orphans count as explained loss), and a stub still referenced by a surviving file is not swept. Also adds the CHANGELOG entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ab73657432
commit
795102cc28
@@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
|
||||
|
||||
## 0.9.46 (unreleased)
|
||||
|
||||
- Fix: pruning a source file now also sweeps the external-import placeholder nodes it strands at degree 0, instead of leaving them to accumulate in the node count, `GRAPH_REPORT.md`, and exports; genuinely-isolated real nodes (which carry a `source_file`) are never touched (#2807, thanks @abhay-codes07).
|
||||
- Fix: when two edges connect the same node pair with different relations, the graph builder now keeps the more specific relation (`calls`, `imports`, `inherits`, ...) instead of letting a generic `references`/`uses`/`mentions` overwrite it; previously a real `calls` could be downgraded to `references` and then dropped from the call graph (#2803, thanks @abhay-codes07).
|
||||
|
||||
## 0.9.45 (2026-08-16)
|
||||
|
||||
@@ -1839,6 +1839,12 @@ def build_merge(
|
||||
# match on source_file. A node with neither a source_file nor an edge
|
||||
# names nothing and connects nothing, so dropping it loses no
|
||||
# information (#2807).
|
||||
#
|
||||
# A single pass suffices: external-import stubs are only ever edge
|
||||
# TARGETS (extractors mint them as the target of an imports_from/
|
||||
# references/inherits edge, never as a source), so removing one can
|
||||
# never drop another to degree 0. A future extractor emitting a
|
||||
# stub->stub edge would require iterating this to a fixpoint.
|
||||
orphaned = [
|
||||
n for n, d in G.nodes(data=True)
|
||||
if not d.get("source_file")
|
||||
|
||||
@@ -153,3 +153,33 @@ def test_a_prune_that_matches_nothing_sweeps_nothing(tmp_path):
|
||||
def test_pruning_every_file_leaves_an_empty_graph(tmp_path):
|
||||
G = _prune(_corpus_graph(), tmp_path, ["a.py", "b.py"])
|
||||
assert G.number_of_nodes() == 0, sorted(G.nodes)
|
||||
|
||||
|
||||
def test_sweeping_an_orphan_does_not_trip_the_shrink_guard(tmp_path):
|
||||
"""The #479 shrink guard raises on unexplained node loss. Swept orphans are
|
||||
source-less, so they must be treated as explained: build_merge completes
|
||||
(does not raise) and returns the pruned graph with the orphan gone."""
|
||||
# build_merge itself runs the guard; this pruning both a file AND sweeping
|
||||
# its stranded stub must not raise.
|
||||
G = _prune(_corpus_graph(), tmp_path, ["a.py"])
|
||||
assert "mod_a_path" not in G.nodes # swept orphan
|
||||
assert "mod_b_keep" in G.nodes # unrelated file survives
|
||||
|
||||
|
||||
def test_a_stub_referenced_by_a_surviving_file_is_not_swept(tmp_path):
|
||||
"""A stub shared by two files must survive when only one referrer is pruned
|
||||
(single-pass sweep only removes it once it is genuinely degree 0)."""
|
||||
nodes = [
|
||||
{"id": "mod_a_run", "label": "run()", "file_type": "code", "source_file": "a.py"},
|
||||
{"id": "mod_b_run", "label": "run()", "file_type": "code", "source_file": "b.py"},
|
||||
{"id": "ext_path", "label": "Path", "file_type": "code"}, # shared external stub
|
||||
]
|
||||
edges = [
|
||||
{"source": "mod_a_run", "target": "ext_path", "relation": "references",
|
||||
"confidence": "EXTRACTED", "source_file": "a.py"},
|
||||
{"source": "mod_b_run", "target": "ext_path", "relation": "references",
|
||||
"confidence": "EXTRACTED", "source_file": "b.py"},
|
||||
]
|
||||
G = _prune(build_from_json(_extraction(nodes, edges)), tmp_path, ["a.py"])
|
||||
assert "ext_path" in G.nodes, "stub still referenced by b.py must not be swept"
|
||||
assert "mod_b_run" in G.nodes
|
||||
|
||||
Reference in New Issue
Block a user