use super::super::*;
use crate::compiler::{CompilerFamily, SourceMode};
#[test]
fn injects_macro_prefix_map_for_clang() {
let tmp = tempfile::tempdir().unwrap();
let root_path = tmp.path().join("workspace");
let root = NormalizedPath::new(&root_path);
let env = vec![(PATH_REMAP_ENV.to_string(), "auto".to_string())];
let args = vec!["-c".to_string(), "src/main.cc".to_string()];
let effective = effective_compile_args(
&args,
Path::new("/usr/bin/clang++"),
&root_path,
Some(&root),
Some(&env),
);
assert!(
effective.contains(&format!("-fmacro-prefix-map={}=.", root_path.display())),
"expected -fmacro-prefix-map=<root>=. in {:?}",
effective
);
}
#[test]
fn injects_debug_prefix_map_for_clang() {
let tmp = tempfile::tempdir().unwrap();
let root_path = tmp.path().join("workspace");
let root = NormalizedPath::new(&root_path);
let env = vec![(PATH_REMAP_ENV.to_string(), "auto".to_string())];
let args = vec!["-c".to_string(), "src/main.cc".to_string()];
let effective = effective_compile_args(
&args,
Path::new("/usr/bin/clang++"),
&root_path,
Some(&root),
Some(&env),
);
assert!(
effective.contains(&format!("-fdebug-prefix-map={}=.", root_path.display())),
"expected -fdebug-prefix-map=<root>=. in {:?}",
effective
);
}
#[test]
fn injects_macro_prefix_map_for_gcc() {
let tmp = tempfile::tempdir().unwrap();
let root_path = tmp.path().join("workspace");
let root = NormalizedPath::new(&root_path);
let env = vec![(PATH_REMAP_ENV.to_string(), "auto".to_string())];
let args = vec!["-c".to_string(), "src/main.cc".to_string()];
let effective = effective_compile_args(
&args,
Path::new("/usr/bin/g++"),
&root_path,
Some(&root),
Some(&env),
);
assert!(
effective.contains(&format!("-fmacro-prefix-map={}=.", root_path.display())),
"expected -fmacro-prefix-map=<root>=. for gcc in {:?}",
effective
);
}
#[test]
fn does_not_inject_redundant_macro_prefix_map_for_clang() {
let tmp = tempfile::tempdir().unwrap();
let root_path = tmp.path().join("workspace");
let root = NormalizedPath::new(&root_path);
let env = vec![(PATH_REMAP_ENV.to_string(), "auto".to_string())];
let user_map = format!("-fmacro-prefix-map={}=/source", root_path.display());
let args = vec![
user_map.clone(),
"-c".to_string(),
"src/main.cc".to_string(),
];
let effective = effective_compile_args(
&args,
Path::new("/usr/bin/clang++"),
&root_path,
Some(&root),
Some(&env),
);
let macro_maps: Vec<&String> = effective
.iter()
.filter(|a| a.starts_with("-fmacro-prefix-map="))
.collect();
assert_eq!(
macro_maps.len(),
1,
"expected one -fmacro-prefix-map (the user's), got {:?}",
macro_maps
);
assert_eq!(macro_maps[0], &user_map);
}
#[test]
fn does_not_inject_for_msvc() {
let tmp = tempfile::tempdir().unwrap();
let root_path = tmp.path().join("workspace");
let root = NormalizedPath::new(&root_path);
let env = vec![(PATH_REMAP_ENV.to_string(), "auto".to_string())];
let args = vec!["/c".to_string(), "src\\main.cpp".to_string()];
let effective = effective_compile_args(
&args,
Path::new("cl.exe"),
&root_path,
Some(&root),
Some(&env),
);
for arg in &effective {
assert!(
!arg.starts_with("-fmacro-prefix-map=")
&& !arg.starts_with("-fdebug-prefix-map=")
&& !arg.starts_with("-ffile-prefix-map="),
"MSVC argv must not contain prefix-map flags, found {:?} in {:?}",
arg,
effective
);
}
}
#[test]
fn injects_remap_path_prefix_for_rustc() {
let tmp = tempfile::tempdir().unwrap();
let root_path = tmp.path().join("workspace");
let root = NormalizedPath::new(&root_path);
let env = vec![(PATH_REMAP_ENV.to_string(), "auto".to_string())];
let args = vec![
"--crate-type".to_string(),
"lib".to_string(),
"src/lib.rs".to_string(),
];
let effective = effective_compile_args(
&args,
Path::new("rustc"),
&root_path,
Some(&root),
Some(&env),
);
assert_eq!(
&effective[..2],
&[
"--remap-path-prefix".to_string(),
format!("{}=.", root_path.display())
],
"rustc should still get --remap-path-prefix=<root>=. as its first arg pair"
);
}
#[test]
fn requires_worktree_in_key_for_pch_clang() {
assert!(requires_worktree_in_key(
CompilerFamily::Clang,
SourceMode::Header
));
}
#[test]
fn requires_worktree_in_key_for_pch_gcc() {
assert!(requires_worktree_in_key(
CompilerFamily::Gcc,
SourceMode::Header
));
}
#[test]
fn requires_worktree_in_key_for_msvc_regardless_of_mode() {
assert!(requires_worktree_in_key(
CompilerFamily::Msvc,
SourceMode::Normal
));
assert!(requires_worktree_in_key(
CompilerFamily::Msvc,
SourceMode::Header
));
}
#[test]
fn requires_worktree_in_key_false_for_normal_clang() {
assert!(!requires_worktree_in_key(
CompilerFamily::Clang,
SourceMode::Normal
));
}
#[test]
fn requires_worktree_in_key_false_for_normal_gcc() {
assert!(!requires_worktree_in_key(
CompilerFamily::Gcc,
SourceMode::Normal
));
}
#[test]
fn requires_worktree_in_key_false_for_rustc() {
assert!(!requires_worktree_in_key(
CompilerFamily::Rustc,
SourceMode::Normal
));
}
mod context_key_salt {
use crate::core::NormalizedPath;
use crate::depgraph::context::{compute_context_key, CompileContext};
use crate::depgraph::search_paths::IncludeSearchPaths;
fn minimal_context() -> CompileContext {
CompileContext {
source_file: NormalizedPath::from("src/main.cpp"),
include_search: IncludeSearchPaths::default(),
defines: Vec::new(),
flags: Vec::new(),
force_includes: Vec::new(),
unknown_flags: Vec::new(),
}
}
#[test]
fn context_key_differs_across_worktrees_when_salt_supplied() {
let tmp = tempfile::tempdir().unwrap();
let root_a = tmp.path().join("worktree-a");
let root_b = tmp.path().join("worktree-b");
let ctx = minimal_context();
let key_a = compute_context_key(&ctx, None, Some(&root_a));
let key_b = compute_context_key(&ctx, None, Some(&root_b));
assert_ne!(
key_a, key_b,
"PCH / MSVC: per-worktree salt must yield distinct context keys"
);
}
#[test]
fn context_key_matches_across_worktrees_when_no_salt() {
let ctx = minimal_context();
let key_a = compute_context_key(&ctx, None, None);
let key_b = compute_context_key(&ctx, None, None);
assert_eq!(
key_a, key_b,
"no salt → identical keys; cross-worktree sharing must work"
);
}
#[test]
fn context_key_stable_for_same_worktree_salt() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("worktree-a");
let ctx = minimal_context();
let key_1 = compute_context_key(&ctx, None, Some(&root));
let key_2 = compute_context_key(&ctx, None, Some(&root));
assert_eq!(key_1, key_2, "salt must be deterministic across calls");
}
#[test]
fn context_key_with_salt_differs_from_no_salt() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("worktree");
let ctx = minimal_context();
let key_with_salt = compute_context_key(&ctx, None, Some(&root));
let key_no_salt = compute_context_key(&ctx, None, None);
assert_ne!(
key_with_salt, key_no_salt,
"Some(salt) must produce a different key from None — \
otherwise the salt branch is a no-op"
);
}
}