fix(mcp): a run point in a Cargo workspace member runs that member's tests (#3059)

execute_run_configuration with a run point in a Cargo workspace member ran the first member's tests: every member's `cargo test -p <member>` configuration runs from the workspace root, so all were equally close to the file and the first won. A Cargo configuration now covers its member's directory (extras.module) when the closest configuration is picked; its working directory stays the workspace root.

Co-authored-by: guoyucode <29471644+guoyucode@users.noreply.github.com>
This commit is contained in:
GuoYu
2026-11-13 13:04:35 +08:00
committed by GitHub
co-authored by guoyucode
parent 721f4144c1
commit 2abdac4fdb
+42 -2
View File
@@ -434,10 +434,23 @@ fn cwd_of(root: &Path, c: &RunConfig) -> String {
cwd.trim_end_matches('/').to_owned()
}
/// Configs whose working directory most specifically contains `rel` (ties in their original order).
/// The directory a config covers: a Cargo workspace member's own directory (its `cargo test -p`
/// runs from the workspace root), else its working directory.
fn scope_of(root: &Path, c: &RunConfig) -> String {
match c.extras.module.as_deref() {
Some(module) if c.kind == "cargo" => {
let module = module.replace('\\', "/");
let module = module.strip_prefix("./").unwrap_or(&module);
if module == "." { String::new() } else { module.trim_end_matches('/').to_owned() }
}
_ => cwd_of(root, c),
}
}
/// Configs whose scope ([`scope_of`]) most specifically contains `rel` (ties in their original order).
fn all_closest<'a>(root: &Path, configs: &[&'a RunConfig], rel: &str) -> Vec<&'a RunConfig> {
let score = |c: &RunConfig| -> Option<usize> {
let cwd = cwd_of(root, c);
let cwd = scope_of(root, c);
if cwd.is_empty() { Some(0) } else { (rel == cwd || rel.starts_with(&format!("{cwd}/"))).then_some(cwd.len()) }
};
let scored: Vec<(&RunConfig, usize)> = configs.iter().filter_map(|c| score(c).map(|s| (*c, s))).collect();
@@ -922,6 +935,33 @@ mod tests {
assert_eq!(c.args, ["test", "--", "math::tests::"]);
}
#[test]
fn a_workspace_member_runs_its_own_tests() {
let root = Path::new("/p");
let member = |pkg: &str, dir: &str| {
let mut c = blank(
format!("cargo:test:{pkg}"),
"test",
"cargo",
"cargo".into(),
vec!["test".into(), "-p".into(), pkg.into()],
);
c.is_test = true;
c.extras.module = Some(dir.into());
c
};
let mut workspace = blank("cargo:test".into(), "test", "cargo", "cargo".into(), vec!["test".into()]);
workspace.is_test = true;
let configs = vec![member("ide-agent", "crates/ide-agent"), member("ide-mcp", "crates/ide-mcp"), workspace];
let src = "#[cfg(test)]\nmod tests {\n #[test]\n fn adds() {}\n}\n";
let rel = "crates/ide-mcp/src/stdio.rs";
let ts = detect(src, rel);
let c = target_config(root, &ts[1], rel, &configs, src).unwrap();
assert_eq!(c.args, ["test", "-p", "ide-mcp", "--", "stdio::tests::adds", "--exact"]);
let c = target_config(root, &ts[1], "src/lib.rs", &configs, src).unwrap();
assert!(!c.args.iter().any(|a| a == "-p"), "outside every member: the workspace's tests, {:?}", c.args);
}
#[test]
fn cargo_test_args_get_one_separator() {
let root = Path::new("/p");