use std::fs;
use std::path::Path;
use tempfile::tempdir;
use crate::code_index::{EdgeKind, IndexState, NodeKind};
fn write(root: &Path, path: &str, body: &str) {
let abs = root.join(path);
fs::create_dir_all(abs.parent().unwrap()).unwrap();
fs::write(abs, body).unwrap();
}
fn calls(state: &IndexState, name: &str) -> Vec<String> {
let mut out = Vec::new();
for id in state.symbols.nodes_named(name) {
let Some(decl) = state.symbols.node(*id) else {
continue;
};
if decl.kind != NodeKind::Function {
continue;
}
for edge in state.symbols.incoming(*id) {
if edge.kind != EdgeKind::Calls {
continue;
}
let site = state.symbols.node(edge.from).unwrap();
out.push(format!("{} -> {}", site.path, decl.path));
}
}
out.sort();
out
}
const DECL: &str = "def fetch_rows():\n return []\n";
fn call_importing(module: &str) -> String {
format!("from {module} import fetch_rows\n\nfetch_rows()\n")
}
const BARE_CALL: &str = "fetch_rows()\n";
#[test]
fn a_call_reaches_a_declaration_indexed_before_it() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_decl.py", DECL);
write(root, "z_use.py", &call_importing("a_decl"));
let (state, _) = IndexState::build_from_root(root);
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> a_decl.py"]);
}
#[test]
fn a_call_reaches_a_declaration_indexed_after_it() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_use.py", &call_importing("z_decl"));
write(root, "z_decl.py", DECL);
let (state, _) = IndexState::build_from_root(root);
assert_eq!(calls(&state, "fetch_rows"), vec!["a_use.py -> z_decl.py"]);
}
#[test]
fn a_unique_declaration_indexed_after_an_unimporting_call_reaches_it() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_use.py", BARE_CALL);
write(root, "z_decl.py", DECL);
let (state, _) = IndexState::build_from_root(root);
assert_eq!(calls(&state, "fetch_rows"), vec!["a_use.py -> z_decl.py"]);
}
#[test]
fn a_later_duplicate_declaration_removes_the_unique_name_edge() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_decl.py", DECL);
write(root, "z_use.py", BARE_CALL);
let (mut state, _) = IndexState::build_from_root(root);
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> a_decl.py"]);
write(root, "b_decl.py", DECL);
state.reindex_file(&root.join("b_decl.py")).unwrap();
assert!(calls(&state, "fetch_rows").is_empty());
}
#[test]
fn a_later_unique_declaration_adds_the_edge() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "z_use.py", BARE_CALL);
let (mut state, _) = IndexState::build_from_root(root);
assert!(calls(&state, "fetch_rows").is_empty());
write(root, "a_decl.py", DECL);
state.refresh_from_root(None);
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> a_decl.py"]);
}
#[test]
fn removing_a_duplicate_makes_the_survivor_unique_again() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_decl.py", DECL);
write(root, "b_decl.py", DECL);
write(root, "z_use.py", BARE_CALL);
let (mut state, _) = IndexState::build_from_root(root);
assert!(calls(&state, "fetch_rows").is_empty());
write(root, "b_decl.py", "def other():\n return []\n");
state.reindex_file(&root.join("b_decl.py")).unwrap();
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> a_decl.py"]);
}
#[test]
fn reindexing_the_declaring_file_keeps_its_callers() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_decl.py", DECL);
write(root, "z_use.py", &call_importing("a_decl"));
let (mut state, _) = IndexState::build_from_root(root);
write(root, "a_decl.py", "def fetch_rows():\n return [1]\n");
state.reindex_file(&root.join("a_decl.py")).unwrap();
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> a_decl.py"]);
}
#[test]
fn restored_module_imports_keep_callers_when_only_the_declaration_changes() {
use harn_vm::VmValue;
use crate::code_index::CodeIndexCapability;
use crate::registry::{BuiltinRegistry, HostlibCapability};
let dir = tempdir().unwrap();
let root = dir.path();
write(
root,
"Sources/Core/Decl.swift",
"func fetchRows() -> Int { 1 }\n",
);
write(
root,
"Sources/Other/Decl.swift",
"func fetchRows() -> Int { 9 }\n",
);
write(
root,
"Sources/App/Main.swift",
"import Core\nfunc main() { _ = fetchRows() }\n",
);
let (state, outcome) = IndexState::build_from_root(root);
assert_eq!(outcome.files_indexed, 3);
let app = state.lookup_path("Sources/App/Main.swift").unwrap();
let core = state.lookup_path("Sources/Core/Decl.swift").unwrap();
assert_eq!(
state
.symbols
.nodes_named("fetchRows")
.iter()
.filter(|id| state.symbols.node(**id).unwrap().kind == NodeKind::Function)
.count(),
2,
"the competing declaration must defeat unique-name fallback"
);
assert_eq!(state.imports_of(app), vec![core]);
assert_eq!(state.importers_of(core), vec![app]);
assert_eq!(
calls(&state, "fetchRows"),
vec!["Sources/App/Main.swift -> Sources/Core/Decl.swift"]
);
state.snapshot().save(root).unwrap();
let capability = CodeIndexCapability::new();
assert!(capability.restore_from_disk(root).unwrap());
let shared = capability.shared();
{
let guard = shared.lock().unwrap();
let restored = guard.as_ref().unwrap();
assert_eq!(restored.imports_of(app), vec![core]);
assert_eq!(restored.importers_of(core), vec![app]);
assert_eq!(
calls(restored, "fetchRows"),
vec!["Sources/App/Main.swift -> Sources/Core/Decl.swift"]
);
}
write(
root,
"Sources/Core/Decl.swift",
"func fetchRows() -> Int { 2 }\n",
);
let mut registry = BuiltinRegistry::new();
capability.register_builtins(&mut registry);
let mut request = harn_vm::value::DictMap::new();
request.insert(
harn_vm::value::intern_key("path"),
VmValue::String(arcstr::ArcStr::from("Sources/Core/Decl.swift")),
);
let result = (registry
.find("hostlib_code_index_reindex_file")
.unwrap()
.handler)(&[VmValue::dict(request)])
.unwrap();
let VmValue::Dict(result) = result else {
panic!("reindex must return a record");
};
assert!(matches!(result.get("indexed"), Some(VmValue::Bool(true))));
assert!(matches!(result.get("file_id"), Some(VmValue::Int(id)) if *id == i64::from(core)));
let guard = shared.lock().unwrap();
let restored = guard.as_ref().unwrap();
assert_eq!(restored.imports_of(app), vec![core]);
assert_eq!(restored.importers_of(core), vec![app]);
assert_eq!(
calls(restored, "fetchRows"),
vec!["Sources/App/Main.swift -> Sources/Core/Decl.swift"]
);
}
#[test]
fn a_local_declaration_still_shadows_an_imported_one() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "a_decl.py", DECL);
write(
root,
"z_use.py",
"from a_decl import fetch_rows\n\n\ndef fetch_rows():\n return [2]\n\n\nfetch_rows()\n",
);
let (state, _) = IndexState::build_from_root(root);
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> z_use.py"]);
}
#[test]
fn reindexing_a_new_file_resolves_an_import_that_was_dangling() {
let dir = tempdir().unwrap();
let root = dir.path();
write(root, "other.py", DECL);
write(root, "z_use.py", &call_importing("a_new"));
let (mut state, _) = IndexState::build_from_root(root);
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> other.py"]);
write(root, "a_new.py", DECL);
state.reindex_file(&root.join("a_new.py")).unwrap();
assert_eq!(calls(&state, "fetch_rows"), vec!["z_use.py -> a_new.py"]);
}