use kglite::api::session::{execute_read, ExecuteOptions};
use kglite::api::{DirGraph, Value};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
fn corpus(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("tests")
.join("corpus")
.join(name)
}
fn build(name: &str) -> std::sync::Arc<DirGraph> {
codingest::builder::run_with_options(&corpus(name), false, true, None, None, true)
.unwrap_or_else(|e| panic!("[{name}] codingest build failed: {e}"))
}
fn string_rows(graph: &DirGraph, query: &str) -> Vec<String> {
let params: HashMap<String, Value> = HashMap::new();
let out = execute_read(graph, query, &ExecuteOptions::eager(¶ms))
.unwrap_or_else(|e| panic!("query failed: {e}\n {query}"));
let mut rows: Vec<String> = out
.result
.rows
.iter()
.map(|r| match &r[0] {
Value::String(s) => s.clone(),
other => format!("{other:?}"),
})
.collect();
rows.sort();
rows
}
fn callers_1_3(graph: &DirGraph, target: &str) -> Vec<String> {
string_rows(
graph,
&format!(
"MATCH (f:Function {{qualified_name: '{target}'}})<-[:CALLS*1..3]-(caller:Function) \
RETURN DISTINCT caller.qualified_name"
),
)
}
const BETA: &str = "crate::src::beta::beta_uses_renamed";
const DEEP: &str = "crate::src::nested::inner::deep_fn";
const ALPHA: &str = "crate::src::alpha::alpha_helper";
#[test]
fn varlen_callers_use_trail_reachability() {
let g = build("rust_import");
let all = string_rows(&g, "MATCH (f:Function) RETURN f.qualified_name");
for qn in [BETA, DEEP, ALPHA] {
assert!(
all.iter().any(|n| n == qn),
"expected function {qn} not in graph; functions present: {all:?}"
);
}
let both = vec![BETA.to_string(), DEEP.to_string()];
assert_eq!(
callers_1_3(&g, BETA),
both,
"beta must be its own reachable caller via beta -> deep_fn -> beta"
);
assert_eq!(
callers_1_3(&g, DEEP),
both,
"deep_fn must be its own reachable caller via deep_fn -> beta -> deep_fn"
);
assert_eq!(callers_1_3(&g, ALPHA), both);
assert!(callers_1_3(&g, "crate::src::root_uses_alpha").is_empty());
assert!(callers_1_3(&g, "crate::src::nested::nested_uses_super").is_empty());
}
#[test]
fn recursion_is_a_property_not_a_self_edge() {
let g = build("r_basic");
let recursive = string_rows(
&g,
"MATCH (f:Function) WHERE f.is_recursive = true RETURN f.qualified_name",
);
assert!(
recursive.iter().any(|n| n.ends_with("deep_count")),
"deep_count should be flagged is_recursive; got {recursive:?}"
);
let self_edges = string_rows(
&g,
"MATCH (f:Function)-[:CALLS]->(f2:Function) WHERE f.qualified_name = f2.qualified_name \
RETURN f.qualified_name",
);
assert!(
self_edges.is_empty(),
"a self-call must not produce a :CALLS self-edge; got {self_edges:?}"
);
}