use super::*;
use crate::store::Store;
use tempfile::TempDir;
fn scan_repo(files: &[(&str, &str)]) -> (TempDir, Store, MapCache) {
crate::store::init_isolated_cache();
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
for (name, body) in files {
std::fs::write(root.join(name), body.as_bytes()).expect("write fixture");
}
let cfg = crate::config::ConfigV1::with_defaults();
let mut store = Store::open(root, crate::store::VIEW_WORKING).expect("open store");
crate::scanner::scan(
root,
&mut store,
&cfg,
crate::scanner::ScanSource::WorkingTree,
crate::scanner::EmbedMode::Inline,
)
.expect("scan");
let cache = MapCache::build(&store);
(dir, store, cache)
}
fn provenance_fixture() -> (TempDir, Store, MapCache) {
scan_repo(&[
(
"mod_a.rs",
"pub struct Widget;\n\
pub trait Drawable { fn draw(&self); }\n\
impl Drawable for Widget { fn draw(&self) {} }\n\
pub fn gadget() {}\n\
pub fn helper() {}\n\
pub fn caller() { helper(); }\n\
pub fn shared() {}\n",
),
(
"mod_b.rs",
"use crate::mod_a::gadget;\n\
use crate::unknown_mod::Thing;\n\
pub fn shared() {}\n\
pub fn use_shared() { shared(); }\n",
),
("mod_c.py", "class Foo:\n pass\n\nclass Bar(Foo):\n pass\n"),
])
}
fn built(store: &Store, cache: &MapCache, kinds: EdgeKindSet) -> CodeGraph {
build(
store.index_db.as_ref(),
cache,
&BuildOpts {
kinds,
focus: None,
scan_cap: 1_000_000,
},
)
.expect("build codegraph")
}
fn name_of(sym_path: &str, key: &NodeKey, cache: &MapCache) -> Option<String> {
if let NodeKey::Symbol { path, start_byte } = key
&& path.as_str() == Some(sym_path)
{
let l1 = cache.by_path.get(path)?;
return l1
.symbols
.iter()
.find(|s| s.start_byte == *start_byte)
.map(|s| s.name.clone());
}
None
}
#[test]
fn confidence_ladder_is_fixed() {
assert_eq!(Provenance::Extracted.confidence(), 1.0);
assert_eq!(Provenance::Inferred.confidence(), 0.5);
assert_eq!(Provenance::Ambiguous.confidence(), 0.2);
assert!(Provenance::Extracted.rank() > Provenance::Inferred.rank());
assert!(Provenance::Inferred.rank() > Provenance::Ambiguous.rank());
}
#[test]
fn edge_kind_strings_are_stable() {
assert_eq!(EdgeKind::Calls.as_str(), "calls");
assert_eq!(EdgeKind::Imports.as_str(), "imports");
assert_eq!(EdgeKind::Inherits.as_str(), "inherits");
assert_eq!(EdgeKind::Contains.as_str(), "contains");
assert_eq!(EdgeKind::Annotates.as_str(), "annotates");
assert_eq!(EdgeKind::Cites.as_str(), "cites");
}
#[test]
fn decision_ids_normalize_consistently() {
use crate::path::RelPath;
assert_eq!(normalize_decision_id("adr", 7), "ADR-0007");
assert_eq!(normalize_decision_id("RFC", 2119), "RFC-2119");
assert_eq!(
decision_id_of_path(&RelPath::from("docs/adr/0007-graph.md")).as_deref(),
Some("ADR-0007")
);
assert_eq!(
decision_id_of_path(&RelPath::from("spec/rfc/2119-keywords.txt")).as_deref(),
Some("RFC-2119")
);
assert_eq!(decision_id_of_path(&RelPath::from("src/mcp/codegraph.rs")), None);
assert_eq!(decision_id_of_path(&RelPath::from("src/0001-notes.md")), None);
}
#[test]
fn attach_symbol_prefers_containing_then_following() {
let syms = [(0u32, 10u32), (20u32, 40u32)];
assert_eq!(attach_symbol(&syms, 25), Some(20));
assert_eq!(attach_symbol(&syms, 5), Some(0));
assert_eq!(attach_symbol(&syms, 15), Some(20));
assert_eq!(attach_symbol(&syms, 50), None);
}
fn cache_with_rationale(
files: &[(&str, Vec<crate::extract::Symbol>, Vec<crate::extract::RationaleRecord>)],
) -> MapCache {
use crate::extract::FileMapL1;
use crate::path::RelPath;
let mut cache = MapCache::empty();
for (path, symbols, rationale) in files {
let l1 = FileMapL1 {
schema_ver: crate::extract::SCHEMA_VER,
language: "rust".to_string(),
size_bytes: 0,
had_errors: false,
error_count: 0,
symbols: symbols.clone(),
imports: Vec::new(),
implementations: Vec::new(),
rationale: rationale.clone(),
};
cache.by_path.insert(RelPath::from(*path), l1);
}
cache
}
fn sym(name: &str, start: u32, end: u32) -> crate::extract::Symbol {
crate::extract::Symbol {
name: name.to_string(),
kind: crate::extract::SymbolKind::Function,
start_byte: start,
end_byte: end,
start_row: 0,
start_col: 0,
signature: None,
decorators: Vec::new(),
}
}
fn rationale(kind: crate::extract::RationaleKind, start: u32, citations: &[&str]) -> crate::extract::RationaleRecord {
crate::extract::RationaleRecord {
kind,
text: "why we did it".to_string(),
start_byte: start,
citations: citations.iter().map(|c| c.to_string()).collect(),
}
}
fn build_from(cache: &MapCache, kinds: EdgeKindSet) -> CodeGraph {
build(
None,
cache,
&BuildOpts {
kinds,
focus: None,
scan_cap: CODEGRAPH_SCAN_CAP,
},
)
.expect("build")
}
#[test]
fn rationale_annotates_the_enclosing_symbol() {
use crate::extract::RationaleKind;
let cache = cache_with_rationale(&[(
"m.rs",
vec![sym("helper", 0, 20)],
vec![rationale(RationaleKind::Why, 5, &[])],
)]);
let g = build_from(&cache, EdgeKindSet::all());
let ann: Vec<&CodeEdge> = g.edges.iter().filter(|e| e.kind == EdgeKind::Annotates).collect();
assert_eq!(ann.len(), 1, "one annotates edge");
assert_eq!(
ann[0].provenance,
Provenance::Inferred,
"proximity attachment is inferred"
);
assert!(matches!(&ann[0].from, NodeKey::Rationale { start_byte: 5, .. }));
assert!(
matches!(&ann[0].to, NodeKey::Symbol { start_byte: 0, .. }),
"attaches to the enclosing symbol"
);
}
#[test]
fn rationale_cites_resolve_to_decision_or_virtual_name() {
use crate::extract::RationaleKind;
let cache = cache_with_rationale(&[
(
"m.rs",
vec![sym("helper", 0, 20)],
vec![rationale(RationaleKind::Why, 5, &["ADR-0001", "ADR-0099"])],
),
("docs/adr/0001-codegraph.md", vec![], vec![]),
]);
let g = build_from(&cache, EdgeKindSet::all());
let cites: Vec<&CodeEdge> = g.edges.iter().filter(|e| e.kind == EdgeKind::Cites).collect();
assert_eq!(cites.len(), 2, "one cite per citation");
let resolved = cites
.iter()
.find(|e| matches!(&e.to, NodeKey::Decision { .. }))
.expect("ADR-0001 resolves to a decision node");
assert_eq!(
resolved.provenance,
Provenance::Extracted,
"a resolved citation is extracted"
);
let dangling = cites
.iter()
.find(|e| matches!(&e.to, NodeKey::Name(n) if n == "ADR-0099"))
.expect("ADR-0099 falls back to a virtual name node");
assert_eq!(
dangling.provenance,
Provenance::Inferred,
"an unresolved citation is inferred"
);
}
#[test]
fn rationale_lanes_are_opt_in() {
use crate::extract::RationaleKind;
let cache = cache_with_rationale(&[(
"m.rs",
vec![sym("helper", 0, 20)],
vec![rationale(RationaleKind::Why, 5, &["ADR-0001"])],
)]);
let calls_only = EdgeKindSet {
calls: true,
..EdgeKindSet::none()
};
let g = build_from(&cache, calls_only);
assert!(
!g.edges
.iter()
.any(|e| matches!(e.kind, EdgeKind::Annotates | EdgeKind::Cites)),
"rationale lanes stay off unless selected"
);
}
#[cfg(feature = "documents")]
#[test]
fn documents_lane_links_doc_chunks_to_code() {
let (_dir, store, mut cache) = provenance_fixture();
cache.doc_links = vec![
DocLink {
doc_path: crate::path::RelPath::from("docs/guide.md"),
chunk_idx: 0,
mention: DocMention::Name("helper".to_string()),
},
DocLink {
doc_path: crate::path::RelPath::from("docs/guide.md"),
chunk_idx: 1,
mention: DocMention::Path(crate::path::RelPath::from("mod_a.rs")),
},
DocLink {
doc_path: crate::path::RelPath::from("docs/guide.md"),
chunk_idx: 1,
mention: DocMention::Path(crate::path::RelPath::from("no/such_file.rs")),
},
]
.into();
let kinds = EdgeKindSet {
documents: true,
..EdgeKindSet::none()
};
let g = built(&store, &cache, kinds);
let doc_edges: Vec<&CodeEdge> = g.edges.iter().filter(|e| e.kind == EdgeKind::Documents).collect();
assert_eq!(doc_edges.len(), 3, "one Documents edge per link: {doc_edges:?}");
let name_edge = doc_edges
.iter()
.find(|e| matches!(&e.to, NodeKey::Symbol { .. }))
.expect("a name mention resolves to a Symbol node");
assert!(
matches!(name_edge.from, NodeKey::DocChunk { chunk_idx: 0, .. }),
"the name edge originates at the citing doc chunk"
);
assert_eq!(
name_edge.provenance,
Provenance::Inferred,
"a bare name mention is name-level"
);
assert_eq!(
name_of("mod_a.rs", &name_edge.to, &cache).as_deref(),
Some("helper"),
"resolves to helper's definition"
);
let extracted = doc_edges
.iter()
.find(|e| e.provenance == Provenance::Extracted)
.expect("an indexed path citation is EXTRACTED");
assert!(
matches!(&extracted.to, NodeKey::File { path } if path.as_str() == Some("mod_a.rs")),
"the extracted edge points at the cited file node: {extracted:?}"
);
let inferred_path = doc_edges
.iter()
.find(|e| matches!(&e.to, NodeKey::Name(n) if n == "no/such_file.rs"))
.expect("an unindexed path citation resolves to a virtual Name node, not a phantom File");
assert_eq!(
inferred_path.provenance,
Provenance::Inferred,
"an unindexed citation degrades to INFERRED"
);
assert!(
!doc_edges
.iter()
.any(|e| matches!(&e.to, NodeKey::File { path } if path.as_str() == Some("no/such_file.rs"))),
"an unindexed path must NOT invent a File node"
);
let calls_only = build(
store.index_db.as_ref(),
&cache,
&BuildOpts {
kinds: EdgeKindSet {
calls: true,
..EdgeKindSet::none()
},
focus: None,
scan_cap: 1_000_000,
},
)
.expect("calls-only build");
assert!(
!calls_only.edges.iter().any(|e| e.kind == EdgeKind::Documents),
"documents lane stays off unless selected"
);
}
#[test]
fn edges_param_selects_lanes() {
let calls = EdgeKindSet::from_edges_param("calls");
assert!(calls.calls && !calls.imports && !calls.inherits);
let imports = EdgeKindSet::from_edges_param("imports");
assert!(imports.imports && !imports.calls);
let both = EdgeKindSet::from_edges_param("both");
assert!(both.calls && both.imports && !both.inherits);
let all = EdgeKindSet::from_edges_param("all");
assert!(all.calls && all.imports && all.inherits);
let dflt = EdgeKindSet::from_edges_param("bogus");
assert!(dflt.calls && !dflt.imports && !dflt.inherits);
}
#[test]
fn contains_edges_are_extracted() {
let (_d, store, cache) = provenance_fixture();
let g = built(&store, &cache, EdgeKindSet::all());
let contains: Vec<&CodeEdge> = g.edges.iter().filter(|e| e.kind == EdgeKind::Contains).collect();
assert!(!contains.is_empty(), "expected fileโsymbol contains edges");
for e in &contains {
assert_eq!(e.provenance, Provenance::Extracted, "contains edges are structural");
assert_eq!(e.provenance.confidence(), 1.0);
assert!(matches!(e.from, NodeKey::File { .. }), "contains source is a file node");
assert!(
matches!(e.to, NodeKey::Symbol { .. }),
"contains target is a symbol node"
);
}
}
#[test]
fn focus_scopes_the_build_to_files_under_the_path_prefix() {
let (_d, store, cache) = provenance_fixture();
let kinds = EdgeKindSet {
contains: true,
..EdgeKindSet::none()
};
let g = build(
store.index_db.as_ref(),
&cache,
&BuildOpts {
kinds,
focus: Some(RelPath::from("mod_a")),
scan_cap: 1_000_000,
},
)
.expect("build codegraph");
assert!(!g.edges.is_empty(), "the focused prefix still matches a file");
for edge in &g.edges {
let NodeKey::File { path } = &edge.from else {
panic!("contains edges originate at a file node, got {:?}", edge.from);
};
assert!(
path.as_bytes().starts_with(b"mod_a"),
"focus excludes files outside the prefix, got {path}"
);
}
}
#[test]
fn import_to_known_symbol_is_inferred() {
let (_d, store, cache) = provenance_fixture();
let g = built(&store, &cache, EdgeKindSet::all());
let hit = g
.edges
.iter()
.find(|e| e.kind == EdgeKind::Imports && name_of("mod_a.rs", &e.to, &cache).as_deref() == Some("gadget"));
let hit = hit.expect("import of gadget should resolve to the gadget symbol");
assert_eq!(
hit.provenance,
Provenance::Inferred,
"name-resolved import is inferred, never extracted"
);
assert!(
matches!(hit.from, NodeKey::File { .. }),
"import source is the importing file"
);
}
#[test]
fn import_to_unknown_module_is_a_virtual_name_node() {
let (_d, store, cache) = provenance_fixture();
let g = built(&store, &cache, EdgeKindSet::all());
let hit = g
.edges
.iter()
.find(|e| e.kind == EdgeKind::Imports && matches!(&e.to, NodeKey::Name(_)));
let hit = hit.expect("import of an unknown module yields a virtual Name node");
assert_ne!(
hit.provenance,
Provenance::Extracted,
"an unresolved import is never extracted"
);
}
#[test]
fn inherits_resolves_parent_and_is_inferred() {
let (_d, store, cache) = provenance_fixture();
let g = built(&store, &cache, EdgeKindSet::all());
let hit = g
.edges
.iter()
.find(|e| e.kind == EdgeKind::Inherits && name_of("mod_c.py", &e.to, &cache).as_deref() == Some("Foo"));
let hit = hit.expect("Bar(Foo) should produce an inherits edge to Foo");
assert_eq!(hit.provenance, Provenance::Inferred);
}
#[test]
fn call_to_multi_definition_name_is_ambiguous() {
let (_d, store, cache) = provenance_fixture();
let g = built(&store, &cache, EdgeKindSet::all());
let shared_calls: Vec<&CodeEdge> = g
.edges
.iter()
.filter(|e| {
e.kind == EdgeKind::Calls
&& (name_of("mod_a.rs", &e.to, &cache).as_deref() == Some("shared")
|| name_of("mod_b.rs", &e.to, &cache).as_deref() == Some("shared"))
})
.collect();
assert!(!shared_calls.is_empty(), "expected call edges to `shared`");
for e in &shared_calls {
assert_eq!(
e.provenance,
Provenance::Ambiguous,
"a name with >1 definition is ambiguous"
);
assert_eq!(e.provenance.confidence(), 0.2);
}
}
#[test]
fn output_is_deterministic() {
let (_d, store, cache) = provenance_fixture();
let a = built(&store, &cache, EdgeKindSet::all());
let b = built(&store, &cache, EdgeKindSet::all());
let key = |e: &CodeEdge| {
(
e.kind.as_str(),
format!("{:?}", e.from),
format!("{:?}", e.to),
e.provenance.as_str(),
)
};
let ka: Vec<_> = a.edges.iter().map(key).collect();
let kb: Vec<_> = b.edges.iter().map(key).collect();
assert_eq!(ka, kb, "graph build must be deterministic across calls");
}
#[test]
fn call_attributes_to_innermost_enclosing_function() {
let (_d, store, cache) = scan_repo(&[(
"nested.rs",
"pub fn helper() {}\n\
pub fn outer() {\n\
fn inner() { helper(); }\n\
}\n",
)]);
let g = built(&store, &cache, EdgeKindSet::all());
let call = g
.edges
.iter()
.find(|e| e.kind == EdgeKind::Calls && name_of("nested.rs", &e.to, &cache).as_deref() == Some("helper"))
.expect("a call edge to helper");
assert_eq!(
name_of("nested.rs", &call.from, &cache).as_deref(),
Some("inner"),
"a call in the nested fn must attribute to the innermost enclosing symbol"
);
}
#[test]
fn call_outside_every_function_attributes_to_the_file() {
let (_d, store, cache) = scan_repo(&[(
"m.py",
"def helper():\n pass\n\nhelper()\n\ndef outer():\n helper()\n",
)]);
let g = built(&store, &cache, EdgeKindSet::all());
let calls: Vec<&CodeEdge> = g
.edges
.iter()
.filter(|e| e.kind == EdgeKind::Calls && name_of("m.py", &e.to, &cache).as_deref() == Some("helper"))
.collect();
assert!(
calls.iter().any(|e| matches!(e.from, NodeKey::File { .. })),
"the module-scope call must attribute to the file node, not a function"
);
assert!(
calls
.iter()
.any(|e| name_of("m.py", &e.from, &cache).as_deref() == Some("outer")),
"the in-function call must attribute to outer"
);
}
#[cfg(any(feature = "code-intel-js", feature = "code-intel-stack"))]
#[test]
fn resolved_calls_are_extracted_with_intel() {
use std::path::Path;
crate::store::init_isolated_cache();
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/precise_resolution_py");
for entry in std::fs::read_dir(&src).expect("fixture dir") {
let entry = entry.expect("dir entry");
let name = entry.file_name().to_string_lossy().to_string();
std::fs::copy(entry.path(), root.join(&name)).expect("copy fixture");
}
let cfg = crate::config::ConfigV1::with_defaults();
let mut store = Store::open(root, crate::store::VIEW_WORKING).expect("open store");
crate::scanner::scan(
root,
&mut store,
&cfg,
crate::scanner::ScanSource::WorkingTree,
crate::scanner::EmbedMode::Inline,
)
.expect("scan");
let cache = MapCache::build(&store);
let g = built(&store, &cache, EdgeKindSet::all());
let has_extracted_call = g.edges.iter().any(|e| {
e.kind == EdgeKind::Calls
&& e.provenance == Provenance::Extracted
&& name_of("app.py", &e.from, &cache).as_deref() == Some("calls_imported_f")
&& name_of("mod.py", &e.to, &cache).as_deref() == Some("f")
});
assert!(
has_extracted_call,
"a resolved cross-file call should be EXTRACTED under intel features"
);
}
#[cfg(any(feature = "code-intel-js", feature = "code-intel-stack"))]
#[test]
fn resolved_call_prefers_innermost_same_named_definition() {
let (_dir, store, cache) = scan_repo(&[(
"nested.py",
"def target():\n def target():\n pass\n target()\n",
)]);
let path = RelPath::from("nested.py");
let symbols = cache.by_path.get(&path).expect("nested.py map");
let inner_start = symbols
.symbols
.iter()
.filter(|symbol| symbol.name == "target")
.map(|symbol| symbol.start_byte)
.max()
.expect("nested target symbol");
let graph = built(&store, &cache, EdgeKindSet::all());
let extracted = graph
.edges
.iter()
.find(|edge| edge.kind == EdgeKind::Calls && edge.provenance == Provenance::Extracted)
.expect("resolved nested call");
assert!(
matches!(extracted.to, NodeKey::Symbol { start_byte, .. } if start_byte == inner_start),
"the resolver's inner identifier must map to the innermost containing L1 symbol"
);
}
fn opts(kinds: EdgeKindSet) -> BuildOpts {
BuildOpts {
kinds,
focus: None,
scan_cap: CODEGRAPH_SCAN_CAP,
}
}
#[test]
fn memo_serves_the_same_build_for_one_snapshot() {
let (_dir, store, cache) = provenance_fixture();
let idx = store.index_db.as_ref();
let memo = Mutex::new(new_graph_memo());
let first = build_memoized(&memo, idx, &cache, &opts(EdgeKindSet::all())).expect("first build");
let second = build_memoized(&memo, idx, &cache, &opts(EdgeKindSet::all())).expect("second build");
assert!(
Arc::ptr_eq(&first, &second),
"same cache + key must return the cached Arc"
);
let fresh = built(&store, &cache, EdgeKindSet::all());
assert_eq!(
first.edges.len(),
fresh.edges.len(),
"memoized graph must equal a direct build"
);
}
#[test]
fn memo_keys_on_lanes_and_index_mode() {
let (_dir, store, cache) = provenance_fixture();
let idx = store.index_db.as_ref();
let memo = Mutex::new(new_graph_memo());
let calls_only = EdgeKindSet {
calls: true,
..EdgeKindSet::none()
};
let all = build_memoized(&memo, idx, &cache, &opts(EdgeKindSet::all())).expect("all lanes");
let calls = build_memoized(&memo, idx, &cache, &opts(calls_only)).expect("calls lane");
assert!(
!Arc::ptr_eq(&all, &calls),
"distinct lane sets are distinct cache entries"
);
let no_idx = build_memoized(&memo, None, &cache, &opts(EdgeKindSet::all())).expect("no-index build");
assert!(
!Arc::ptr_eq(&all, &no_idx),
"idx=Some and idx=None are distinct cache entries"
);
let all_again = build_memoized(&memo, idx, &cache, &opts(EdgeKindSet::all())).expect("all lanes again");
assert!(
Arc::ptr_eq(&all, &all_again),
"the all-lanes entry survives intervening distinct-key builds"
);
}
#[test]
fn memo_isolates_snapshots_by_fingerprint() {
let (_da, store_a, cache_a) = scan_repo(&[("m.rs", "pub fn a() {}\npub fn caller() { a(); }\n")]);
let (_db, store_b, cache_b) =
scan_repo(&[("m.rs", "pub fn a() {}\npub fn b() {}\npub fn caller() { a(); b(); }\n")]);
assert_ne!(
cache_a.fingerprint, cache_b.fingerprint,
"different content must fingerprint differently"
);
let memo = Mutex::new(new_graph_memo());
let ga = build_memoized(&memo, store_a.index_db.as_ref(), &cache_a, &opts(EdgeKindSet::all())).expect("a");
let gb = build_memoized(&memo, store_b.index_db.as_ref(), &cache_b, &opts(EdgeKindSet::all())).expect("b");
assert!(!Arc::ptr_eq(&ga, &gb), "distinct fingerprints are distinct entries");
let ga_again =
build_memoized(&memo, store_a.index_db.as_ref(), &cache_a, &opts(EdgeKindSet::all())).expect("a again");
assert!(
Arc::ptr_eq(&ga, &ga_again),
"snapshot A still hits its own entry after B was inserted"
);
}