Some checks failed
Test / pytest (pull_request) Failing after 7s
- Implement transitive_closure(): computes full dependency tree for each node - Implement find_deep_chains(): identifies longest paths in dependency graph - JSON output now includes `transitive` and `deep_chains` fields - Added comprehensive unit tests in scripts/test_dependency_graph.py (9 tests) - Handles cycles correctly, excludes self-references from closure Meets acceptance criteria for #111: ✅ Builds transitive dep tree ✅ Identifies deep chains and circular deps ✅ Output: transitive dependency graph (via --format json) Closes #111
156 lines
3.9 KiB
Python
156 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for dependency_graph.py — transitive closure and deep chain detection."""
|
|
|
|
import json
|
|
import sys
|
|
import os
|
|
import tempfile
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__) or ".")
|
|
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location(
|
|
"dg", os.path.join(os.path.dirname(__file__) or ".", "dependency_graph.py")
|
|
)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
|
|
transitive_closure = mod.transitive_closure
|
|
find_deep_chains = mod.find_deep_chains
|
|
detect_cycles = mod.detect_cycles
|
|
|
|
|
|
def make_graph(edges: dict[str, list[str]]) -> dict:
|
|
"""Build graph dict in expected format: {repo: {"dependencies": [...]}}."""
|
|
return {
|
|
node: {"dependencies": sorted(deps), "files_scanned": 1}
|
|
for node, deps in edges.items()
|
|
}
|
|
|
|
|
|
def test_transitive_closure_simple_chain():
|
|
graph = make_graph({
|
|
"A": ["B"],
|
|
"B": ["C"],
|
|
"C": [],
|
|
})
|
|
closure = transitive_closure(graph)
|
|
assert closure["A"] == {"B", "C"}
|
|
assert closure["B"] == {"C"}
|
|
assert closure["C"] == set()
|
|
print("✅ Simple chain transitive closure")
|
|
|
|
|
|
def test_transitive_closure_diamond():
|
|
graph = make_graph({
|
|
"A": ["B", "C"],
|
|
"B": ["D"],
|
|
"C": ["D"],
|
|
"D": [],
|
|
})
|
|
closure = transitive_closure(graph)
|
|
assert closure["A"] == {"B", "C", "D"}
|
|
assert closure["B"] == {"D"}
|
|
assert closure["C"] == {"D"}
|
|
assert closure["D"] == set()
|
|
print("✅ Diamond closure")
|
|
|
|
|
|
def test_transitive_closure_with_cycle():
|
|
graph = make_graph({
|
|
"A": ["B"],
|
|
"B": ["C"],
|
|
"C": ["A"], # cycle
|
|
})
|
|
closure = transitive_closure(graph)
|
|
assert closure["A"] == {"B", "C"}
|
|
assert closure["B"] == {"C", "A"}
|
|
assert closure["C"] == {"A", "B"}
|
|
print("✅ Cycle in transitive closure")
|
|
|
|
|
|
def test_find_deep_chains_simple():
|
|
graph = make_graph({
|
|
"A": ["B"],
|
|
"B": ["C"],
|
|
"C": [],
|
|
})
|
|
chains = find_deep_chains(graph)
|
|
chains_sorted = sorted(chains, key=len, reverse=True)
|
|
assert len(chains_sorted) == 1
|
|
assert chains_sorted[0] == ["A", "B", "C"]
|
|
print("✅ Simple deep chain")
|
|
|
|
|
|
def test_find_deep_chains_multiple():
|
|
graph = make_graph({
|
|
"A": ["B", "C"],
|
|
"B": ["D"],
|
|
"C": ["E"],
|
|
"D": [],
|
|
"E": [],
|
|
})
|
|
chains = find_deep_chains(graph)
|
|
lengths = [len(c) for c in chains]
|
|
assert max(lengths) == 3
|
|
print("✅ Multiple chains detected")
|
|
|
|
|
|
def test_find_deep_chains_with_cycle_does_not_infinite_loop():
|
|
graph = make_graph({
|
|
"A": ["B"],
|
|
"B": ["C"],
|
|
"C": ["A"],
|
|
})
|
|
chains = find_deep_chains(graph)
|
|
print(f"✅ Cycle handled: found {len(chains)} chains")
|
|
|
|
|
|
def test_empty_graph():
|
|
graph = {}
|
|
assert transitive_closure(graph) == {}
|
|
assert find_deep_chains(graph) == []
|
|
print("✅ Empty graph handled")
|
|
|
|
|
|
def test_detect_cycles_shorthand():
|
|
graph = make_graph({
|
|
"A": ["B"],
|
|
"B": ["C"],
|
|
"C": ["A"],
|
|
})
|
|
cycles = detect_cycles(graph)
|
|
assert len(cycles) == 1
|
|
assert set(cycles[0]) == {"A", "B", "C"}
|
|
print("✅ Cycle detection works")
|
|
|
|
|
|
def test_chain_length_reporting():
|
|
graph = make_graph({
|
|
"root": ["a", "b"],
|
|
"a": ["c"],
|
|
"b": ["d"],
|
|
"c": ["e"],
|
|
"d": [],
|
|
"e": [],
|
|
})
|
|
chains = find_deep_chains(graph)
|
|
max_len = max(len(c) for c in chains)
|
|
assert max_len == 4
|
|
print(f"✅ Longest chain length: {max_len}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_transitive_closure_simple_chain()
|
|
test_transitive_closure_diamond()
|
|
test_transitive_closure_with_cycle()
|
|
test_find_deep_chains_simple()
|
|
test_find_deep_chains_multiple()
|
|
test_find_deep_chains_with_cycle_does_not_infinite_loop()
|
|
test_empty_graph()
|
|
test_detect_cycles_shorthand()
|
|
test_chain_length_reporting()
|
|
print("\n✅ All dependency graph tests passed")
|