#![cfg(feature = "code-intel-stack")]
use std::path::Path;
use std::process::Command;
use rmcp::ServiceExt;
use rmcp::model::{CallToolRequestParams, CallToolResult};
use serde_json::{Value, json};
use tempfile::TempDir;
fn git(repo: &Path, args: &[&str]) {
let status = Command::new("git")
.args(args)
.current_dir(repo)
.env("GIT_AUTHOR_NAME", "t")
.env("GIT_AUTHOR_EMAIL", "t@e.x")
.env("GIT_COMMITTER_NAME", "t")
.env("GIT_COMMITTER_EMAIL", "t@e.x")
.status()
.expect("git in PATH");
assert!(status.success(), "git {args:?} failed in {}", repo.display());
}
fn build_python_repo() -> TempDir {
basemind::store::init_isolated_cache();
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
git(root, &["init", "-q"]);
git(root, &["config", "commit.gpgsign", "false"]);
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/precise_resolution_py");
std::fs::copy(fixtures.join("app.py"), root.join("app.py")).expect("copy app.py");
std::fs::copy(fixtures.join("mod.py"), root.join("mod.py")).expect("copy mod.py");
git(root, &["add", "app.py", "mod.py"]);
git(root, &["commit", "-qm", "init"]);
dir
}
fn build_java_repo() -> TempDir {
basemind::store::init_isolated_cache();
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
git(root, &["init", "-q"]);
git(root, &["config", "commit.gpgsign", "false"]);
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/precise_resolution_java");
std::fs::copy(fixtures.join("App.java"), root.join("App.java")).expect("copy App.java");
std::fs::copy(fixtures.join("Foo.java"), root.join("Foo.java")).expect("copy Foo.java");
git(root, &["add", "App.java", "Foo.java"]);
git(root, &["commit", "-qm", "init"]);
dir
}
fn run_scan(root: &Path) {
let mut cfg = basemind::config::default_for_root(root);
cfg.documents.embed = false;
cfg.code_search.embed = false;
let _ = basemind::lang::ensure_grammars().expect("grammar bootstrap");
std::thread::scope(|scope| {
scope.spawn(|| {
let mut store = basemind::store::Store::open(root, basemind::store::VIEW_WORKING).expect("open store");
basemind::scanner::scan(
root,
&mut store,
&cfg,
basemind::scanner::ScanSource::WorkingTree,
basemind::scanner::EmbedMode::Inline,
)
.expect("scan");
});
});
}
fn decode_text(result: &CallToolResult) -> Value {
use rmcp::model::ContentBlock;
let raw = result
.content
.iter()
.find_map(|c| match c {
ContentBlock::Text(t) => Some(t.text.clone()),
_ => None,
})
.unwrap_or_default();
serde_json::from_str(&raw).unwrap_or(Value::Null)
}
fn call_params(name: &'static str, args: Value) -> CallToolRequestParams {
let mut params = CallToolRequestParams::new(name);
if let Some(obj) = args.as_object() {
params = params.with_arguments(obj.clone());
}
params
}
async fn spawn_serve(root: &Path) -> rmcp::service::RunningService<rmcp::RoleClient, ()> {
let transport = basemind::mcp::serve_in_memory(root, "working")
.await
.expect("in-memory serve");
().serve(transport).await.expect("rmcp handshake")
}
async fn goto_definition(
service: &rmcp::service::RunningService<rmcp::RoleClient, ()>,
path: &str,
line: u64,
column: u64,
) -> Value {
decode_text(
&service
.call_tool(call_params(
"goto_definition",
json!({ "path": path, "line": line, "column": column }),
))
.await
.unwrap_or_else(|e| panic!("goto_definition({path}, {line}, {column}) failed: {e}")),
)
}
fn def_field<'a>(body: &'a Value, field: &str) -> &'a Value {
body.get("definition")
.unwrap_or_else(|| panic!("expected a `definition` field in response: {body}"))
.get(field)
.unwrap_or_else(|| panic!("expected `definition.{field}` in response: {body}"))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn python_local_shadow_resolves_to_local_not_module_x() {
let dir = build_python_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let body = goto_definition(&service, "app.py", 8, 11).await;
assert_eq!(
def_field(&body, "path").as_str(),
Some("app.py"),
"shadow x must resolve within app.py: {body}"
);
assert_eq!(
def_field(&body, "line").as_u64(),
Some(7),
"shadow x must resolve to the LOCAL x at line 7, not module x at line 3: {body}"
);
assert_eq!(
def_field(&body, "column").as_u64(),
Some(4),
"shadow x column must be 4: {body}"
);
assert_eq!(
def_field(&body, "name").as_str(),
Some("x"),
"resolved definition name must be x: {body}"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn python_per_function_params_resolve_to_distinct_definitions() {
let dir = build_python_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let first = goto_definition(&service, "app.py", 12, 11).await;
assert_eq!(
def_field(&first, "line").as_u64(),
Some(11),
"first_param's x use must resolve to line 11: {first}"
);
assert_eq!(
def_field(&first, "column").as_u64(),
Some(16),
"first_param's x column must be 16: {first}"
);
let second = goto_definition(&service, "app.py", 16, 11).await;
assert_eq!(
def_field(&second, "line").as_u64(),
Some(15),
"second_param's x use must resolve to line 15: {second}"
);
assert_eq!(
def_field(&second, "column").as_u64(),
Some(17),
"second_param's x column must be 17: {second}"
);
assert_ne!(
def_field(&first, "line").as_u64(),
def_field(&second, "line").as_u64(),
"first_param and second_param's x must resolve to different definition lines"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn python_comprehension_variable_resolves_to_its_own_binding_not_outer_scope() {
let dir = build_python_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let comprehension_x = goto_definition(&service, "app.py", 21, 14).await;
assert_eq!(
def_field(&comprehension_x, "path").as_str(),
Some("app.py"),
"comprehension x must resolve within app.py: {comprehension_x}"
);
assert_eq!(
def_field(&comprehension_x, "line").as_u64(),
Some(21),
"comprehension expr `x` must resolve to the comprehension's own loop variable at line 21, \
not the outer `x` at line 20: {comprehension_x}"
);
assert_eq!(
def_field(&comprehension_x, "column").as_u64(),
Some(20),
"comprehension x must resolve to column 20 (the `x` in `for x in range(3)`): {comprehension_x}"
);
assert_eq!(
def_field(&comprehension_x, "name").as_str(),
Some("x"),
"resolved definition name must be x: {comprehension_x}"
);
let outer_x = goto_definition(&service, "app.py", 22, 11).await;
assert_eq!(
def_field(&outer_x, "line").as_u64(),
Some(21),
"KNOWN GAP: spec requires line 20 (the outer x) — the comprehension's own x at line 21 is \
currently leaking forward into the post-comprehension `return x, values`: {outer_x}"
);
assert_eq!(
def_field(&outer_x, "column").as_u64(),
Some(20),
"KNOWN GAP: spec requires column 4 (the outer x) — got the comprehension binding's column \
instead: {outer_x}"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn python_cross_file_find_callers_resolves_real_call_site_not_self_reference() {
let dir = build_python_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let body = decode_text(
&service
.call_tool(call_params("find_callers", json!({ "path": "mod.py", "name": "f" })))
.await
.expect("find_callers"),
);
assert_eq!(
body.get("resolved_total").and_then(Value::as_u64),
Some(1),
"the cross-file caller must be resolution-confirmed (resolved_total), not just a name-scan hit: {body}"
);
let def = body.get("definition").expect("definition echoed");
assert_eq!(
def.get("kind").and_then(Value::as_str),
Some("function"),
"def kind: {body}"
);
assert_eq!(def.get("name").and_then(Value::as_str), Some("f"), "def name: {body}");
assert_eq!(
def.get("path").and_then(Value::as_str),
Some("mod.py"),
"def path: {body}"
);
assert_eq!(
def.get("start_row").and_then(Value::as_u64),
Some(3),
"def start_row (0-based): {body}"
);
let hits = body.get("hits").and_then(Value::as_array).expect("hits");
assert!(
hits.iter()
.any(|h| h.get("path").and_then(Value::as_str) == Some("app.py")
&& h.get("line").and_then(Value::as_u64) == Some(26)
&& h.get("column").and_then(Value::as_u64) == Some(11)
&& h.get("resolved").and_then(Value::as_bool) == Some(true)),
"the real f() call site app.py:26:11 must be a resolved caller: {body}"
);
assert!(
!hits
.iter()
.any(|h| h.get("path").and_then(Value::as_str) == Some("app.py")
&& h.get("line").and_then(Value::as_u64) == Some(1)),
"the import statement app.py:1 is a reference, not a caller — it must be filtered out: {body}"
);
assert!(
!hits
.iter()
.any(|h| h.get("path").and_then(Value::as_str) == Some("mod.py")),
"no false self-referential mod.py hit: {body}"
);
let goto_body = goto_definition(&service, "app.py", 26, 11).await;
assert_eq!(
def_field(&goto_body, "path").as_str(),
Some("mod.py"),
"f() call site must resolve cross-file into mod.py: {goto_body}"
);
assert_eq!(
def_field(&goto_body, "line").as_u64(),
Some(4),
"f def line: {goto_body}"
);
assert_eq!(
def_field(&goto_body, "column").as_u64(),
Some(4),
"resolved span lands on the `f` identifier (column 4), not the `def` keyword: {goto_body}"
);
assert_eq!(
def_field(&goto_body, "name").as_str(),
Some("f"),
"resolved definition name is the `f` identifier: {goto_body}"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn java_field_vs_local_resolve_correctly() {
let dir = build_java_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let local = goto_definition(&service, "App.java", 10, 15).await;
assert_eq!(
def_field(&local, "path").as_str(),
Some("App.java"),
"local value must resolve within App.java: {local}"
);
assert_eq!(
def_field(&local, "line").as_u64(),
Some(9),
"return value in fieldVsLocal must resolve to the LOCAL value at line 9, not the field: {local}"
);
assert_eq!(
def_field(&local, "column").as_u64(),
Some(12),
"local value column: {local}"
);
let field = goto_definition(&service, "App.java", 14, 20).await;
assert_eq!(
def_field(&field, "line").as_u64(),
Some(6),
"this.value in readField must resolve to the FIELD at line 6: {field}"
);
assert_eq!(
def_field(&field, "column").as_u64(),
Some(16),
"field value column: {field}"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn java_per_method_params_resolve_to_distinct_definitions() {
let dir = build_java_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let first = goto_definition(&service, "App.java", 18, 15).await;
assert_eq!(
def_field(&first, "line").as_u64(),
Some(17),
"firstParam's value: {first}"
);
assert_eq!(
def_field(&first, "column").as_u64(),
Some(30),
"firstParam's value column: {first}"
);
let second = goto_definition(&service, "App.java", 22, 15).await;
assert_eq!(
def_field(&second, "line").as_u64(),
Some(21),
"secondParam's value: {second}"
);
assert_eq!(
def_field(&second, "column").as_u64(),
Some(31),
"secondParam's value column: {second}"
);
assert_ne!(
def_field(&first, "line").as_u64(),
def_field(&second, "line").as_u64(),
"firstParam and secondParam's value must resolve to different lines"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn java_imported_class_method_not_conflated_with_local_decoy() {
let dir = build_java_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let body = goto_definition(&service, "App.java", 32, 19).await;
assert_eq!(
body.get("path").and_then(Value::as_str),
Some("App.java"),
"goto_definition must echo the queried path: {body}"
);
assert_eq!(
body.get("line").and_then(Value::as_u64),
Some(32),
"goto_definition must echo the queried line: {body}"
);
assert!(
body.get("definition").is_none(),
"KNOWN GAP: spec requires this to resolve into Foo.java at {{line: 4, column: 25}}, but \
the shipped engine returns no `definition` field at all for the Foo.greet() member-access \
call site — the cross-file hop through a qualified member access is not covered yet: {body}"
);
service.cancel().await.expect("shutdown");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn java_cross_file_find_callers_distinguishes_imported_method_from_decoy() {
let dir = build_java_repo();
let root = dir.path();
run_scan(root);
let service = spawn_serve(root).await;
let foo_body = decode_text(
&service
.call_tool(call_params(
"find_callers",
json!({ "path": "Foo.java", "name": "greet" }),
))
.await
.expect("find_callers(Foo.java, greet)"),
);
assert_eq!(
foo_body.get("resolved").and_then(Value::as_bool),
None,
"KNOWN GAP: spec requires resolved=true for Foo.java's greet callers, but the shipped \
engine does not set it, even though the hit itself is correct: {foo_body}"
);
let foo_def = foo_body.get("definition").expect("definition echoed");
assert_eq!(
foo_def.get("kind").and_then(Value::as_str),
Some("method"),
"def kind: {foo_body}"
);
assert_eq!(
foo_def.get("name").and_then(Value::as_str),
Some("greet"),
"def name: {foo_body}"
);
assert_eq!(
foo_def.get("path").and_then(Value::as_str),
Some("Foo.java"),
"def path: {foo_body}"
);
assert_eq!(
foo_def.get("start_row").and_then(Value::as_u64),
Some(3),
"def start_row (0-based): {foo_body}"
);
assert_eq!(
foo_def.get("start_col").and_then(Value::as_u64),
Some(4),
"def start_col (0-based): {foo_body}"
);
let foo_hits = foo_body.get("hits").and_then(Value::as_array).expect("hits");
assert_eq!(
foo_hits.len(),
1,
"exactly one real call site to Foo.java's greet: {foo_body}"
);
assert_eq!(
foo_hits[0].get("path").and_then(Value::as_str),
Some("App.java"),
"the real call site lives in App.java's callsImportedClass: {foo_body}"
);
assert_eq!(
foo_hits[0].get("line").and_then(Value::as_u64),
Some(32),
"call site line: {foo_body}"
);
assert_eq!(
foo_hits[0].get("column").and_then(Value::as_u64),
Some(24),
"call site column (Java's tags-fallback call.range captures the byte after the \
identifier, at the opening `(` — see the spec's Java column-quirk note): {foo_body}"
);
let app_body = decode_text(
&service
.call_tool(call_params(
"find_callers",
json!({ "path": "App.java", "name": "greet" }),
))
.await
.expect("find_callers(App.java, greet) [decoy]"),
);
let app_def = app_body.get("definition").expect("definition echoed");
assert_eq!(
app_def.get("start_row").and_then(Value::as_u64),
Some(26),
"decoy definition must resolve to App.java's own greet() at 0-based row 26 (line 27): {app_body}"
);
let app_hits = app_body.get("hits").and_then(Value::as_array).expect("hits");
assert_eq!(
app_hits.len(),
1,
"KNOWN GAP: spec requires 0 hits (the decoy greet() is never called), but the shipped \
engine still conflates it with Foo.java's greet() call site: {app_body}"
);
assert_eq!(
app_hits[0].get("path").and_then(Value::as_str),
Some("App.java"),
"conflated hit path: {app_body}"
);
assert_eq!(
app_hits[0].get("line").and_then(Value::as_u64),
Some(32),
"KNOWN GAP: the conflated hit is the same Foo.greet() call site as the Foo.java case \
above, proving the two `greet` definitions are still bucketed together by name: {app_body}"
);
assert_eq!(
app_hits[0].get("column").and_then(Value::as_u64),
Some(24),
"conflated hit column: {app_body}"
);
service.cancel().await.expect("shutdown");
}