use std::fs;
use std::sync::Arc;
use harn_hostlib::{
code_index::CodeIndexCapability, BuiltinRegistry, HostlibCapability, RegisteredBuiltin,
};
use harn_vm::VmValue;
fn dict(entries: &[(&str, VmValue)]) -> VmValue {
let mut map: harn_vm::value::DictMap = Default::default();
for (k, v) in entries {
map.insert(harn_vm::value::intern_key(k), v.clone());
}
VmValue::dict(map)
}
fn call(registry: &BuiltinRegistry, name: &str, payload: VmValue) -> VmValue {
let entry: &RegisteredBuiltin = registry
.find(name)
.unwrap_or_else(|| panic!("builtin {name} not registered"));
(entry.handler)(&[payload]).unwrap_or_else(|err| panic!("builtin {name} failed: {err:?}"))
}
fn extract_dict(value: &VmValue) -> Arc<harn_vm::value::DictMap> {
match value {
VmValue::Dict(d) => d.clone(),
other => panic!("expected dict, got {other:?}"),
}
}
fn string_field(dict: &harn_vm::value::DictMap, key: &str) -> String {
match dict
.get(key)
.unwrap_or_else(|| panic!("missing field {key}"))
{
VmValue::String(s) => s.to_string(),
other => panic!("expected string field {key}, got {other:?}"),
}
}
fn bool_field(dict: &harn_vm::value::DictMap, key: &str) -> bool {
match dict
.get(key)
.unwrap_or_else(|| panic!("missing field {key}"))
{
VmValue::Bool(value) => *value,
other => panic!("expected bool field {key}, got {other:?}"),
}
}
fn list_field(dict: &harn_vm::value::DictMap, key: &str) -> Arc<Vec<VmValue>> {
match dict
.get(key)
.unwrap_or_else(|| panic!("missing field {key}"))
{
VmValue::List(value) => value.clone(),
other => panic!("expected list field {key}, got {other:?}"),
}
}
fn build_workspace() -> tempfile::TempDir {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
fs::create_dir_all(root.join("src")).unwrap();
fs::write(
root.join("src/a.rs"),
"pub fn alpha() {}\npub fn beta() { alpha(); }\n",
)
.unwrap();
fs::write(
root.join("src/b.rs"),
"pub fn gamma() {}\npub fn driver() { gamma(); }\n",
)
.unwrap();
dir
}
fn registry() -> (BuiltinRegistry, CodeIndexCapability) {
let cap = CodeIndexCapability::new();
let mut registry = BuiltinRegistry::new();
cap.register_builtins(&mut registry);
(registry, cap)
}
fn rebuild(registry: &BuiltinRegistry, root: &std::path::Path) {
call(
registry,
"hostlib_code_index_rebuild",
dict(&[(
"root",
VmValue::String(arcstr::ArcStr::from(root.to_string_lossy().as_ref())),
)]),
);
}
#[test]
fn cypher_returns_function_by_name() {
let dir = build_workspace();
let (reg, _cap) = registry();
rebuild(®, dir.path());
let result = call(
®,
"hostlib_code_index_cypher",
dict(&[(
"query",
VmValue::String(arcstr::ArcStr::from(
"MATCH (f:Function {name: 'alpha'}) RETURN f.path AS path",
)),
)]),
);
let outer = extract_dict(&result);
let rows = match outer.get("rows").unwrap() {
VmValue::List(l) => l.clone(),
other => panic!("expected list of rows, got {other:?}"),
};
assert_eq!(rows.len(), 1, "expected one match for fn alpha");
let row = extract_dict(&rows[0]);
let path = match row.get("path").unwrap() {
VmValue::String(s) => s.to_string(),
other => panic!("expected string path, got {other:?}"),
};
assert_eq!(path, "src/a.rs");
}
#[test]
fn repo_map_prioritizes_task_named_symbols() {
let dir = build_workspace();
let (reg, _cap) = registry();
rebuild(®, dir.path());
let result = call(
®,
"hostlib_code_index_repo_map",
dict(&[
("task", VmValue::String(arcstr::ArcStr::from("Fix alpha"))),
("max_entries", VmValue::Int(4)),
("token_budget", VmValue::Int(200)),
]),
);
let outer = extract_dict(&result);
let rendered = string_field(&outer, "rendered");
assert!(rendered.contains("src/a.rs:"), "rendered map: {rendered}");
assert!(rendered.contains("alpha"), "rendered map: {rendered}");
let entries = list_field(&outer, "entries");
assert!(!entries.is_empty(), "repo map should return ranked entries");
let first = extract_dict(&entries[0]);
assert_eq!(string_field(&first, "name"), "alpha");
let reasons = list_field(&first, "reasons");
assert!(
reasons.iter().any(
|value| matches!(value, VmValue::String(reason) if reason.as_str() == "task_symbol")
),
"task-named symbol should carry task_symbol reason"
);
}
#[test]
fn repo_map_boosts_context_files_and_respects_budget() {
let dir = build_workspace();
let (reg, _cap) = registry();
rebuild(®, dir.path());
let result = call(
®,
"hostlib_code_index_repo_map",
dict(&[
(
"context_files",
VmValue::List(Arc::new(vec![VmValue::String(arcstr::ArcStr::from(
"src/b.rs",
))])),
),
("max_entries", VmValue::Int(4)),
("token_budget", VmValue::Int(200)),
]),
);
let outer = extract_dict(&result);
let entries = list_field(&outer, "entries");
assert!(!entries.is_empty(), "repo map should return ranked entries");
let first = extract_dict(&entries[0]);
assert_eq!(string_field(&first, "path"), "src/b.rs");
let reasons = list_field(&first, "reasons");
assert!(
reasons.iter().any(
|value| matches!(value, VmValue::String(reason) if reason.as_str() == "context_file")
),
"context-file symbol should carry context_file reason"
);
let tiny = call(
®,
"hostlib_code_index_repo_map",
dict(&[
("max_entries", VmValue::Int(4)),
("token_budget", VmValue::Int(1)),
]),
);
let tiny_outer = extract_dict(&tiny);
let rendered = string_field(&tiny_outer, "rendered");
assert!(rendered.len() <= 4, "rendered map must honor char budget");
assert!(
bool_field(&tiny_outer, "truncated"),
"tiny budget should truncate"
);
}
#[test]
fn branch_overlay_create_then_query_reports_reuse() {
let dir = build_workspace();
let (reg, _cap) = registry();
rebuild(®, dir.path());
let result = call(
®,
"hostlib_code_index_branch_overlay",
dict(&[
("action", VmValue::String(arcstr::ArcStr::from("create"))),
(
"branch",
VmValue::String(arcstr::ArcStr::from("topic/test")),
),
]),
);
let d = extract_dict(&result);
match d.get("active").unwrap() {
VmValue::String(s) => assert_eq!(s.as_str(), "topic/test"),
other => panic!("expected active branch string, got {other:?}"),
}
let reuse = match d.get("reuse_fraction").unwrap() {
VmValue::Float(f) => *f,
other => panic!("expected float, got {other:?}"),
};
assert!(reuse >= 0.999, "expected ≥95% reuse, got {reuse}");
let result = call(
®,
"hostlib_code_index_branch_overlay",
dict(&[(
"action",
VmValue::String(arcstr::ArcStr::from("deactivate")),
)]),
);
let d = extract_dict(&result);
assert!(matches!(d.get("active").unwrap(), VmValue::Nil));
}
#[test]
fn freshness_detects_post_index_edits() {
let dir = build_workspace();
let (reg, _cap) = registry();
rebuild(®, dir.path());
let result = call(
®,
"hostlib_code_index_freshness",
dict(&[("path", VmValue::String(arcstr::ArcStr::from("src/a.rs")))]),
);
let d = extract_dict(&result);
assert!(matches!(d.get("known").unwrap(), VmValue::Bool(true)));
assert!(matches!(d.get("stale").unwrap(), VmValue::Bool(false)));
fs::write(
dir.path().join("src/a.rs"),
"pub fn alpha() {}\npub fn beta() {}\npub fn omega() {}\n",
)
.unwrap();
let result = call(
®,
"hostlib_code_index_freshness",
dict(&[("path", VmValue::String(arcstr::ArcStr::from("src/a.rs")))]),
);
let d = extract_dict(&result);
assert!(matches!(d.get("stale").unwrap(), VmValue::Bool(true)));
}
#[test]
fn freshness_reports_unknown_for_unindexed_paths() {
let dir = build_workspace();
let (reg, _cap) = registry();
rebuild(®, dir.path());
let result = call(
®,
"hostlib_code_index_freshness",
dict(&[(
"path",
VmValue::String(arcstr::ArcStr::from("src/does-not-exist.rs")),
)]),
);
let d = extract_dict(&result);
assert!(matches!(d.get("known").unwrap(), VmValue::Bool(false)));
assert!(matches!(d.get("stale").unwrap(), VmValue::Bool(true)));
}