use super::*;
#[test]
fn the_probe_describes_this_host() {
let Ok(identity) = identity_without_agent() else {
return;
};
assert!(Path::new(&identity.driver).is_absolute());
assert!(!identity.driver_version.is_empty());
assert!(!identity.linker_version.is_empty());
assert!(
identity.crt_objects.values().all(|d| !d.key().is_empty()),
"{identity:?}"
);
assert!(!identity.driver_version.contains('\n') || identity.driver_version.lines().count() > 1);
for (required, what) in [
(file_probes().startup, "what the link starts with"),
(file_probes().libc, "the libc"),
] {
assert!(
required.is_empty()
|| required
.iter()
.any(|name| identity.crt_objects.contains_key(*name)),
"a resolved identity must pin {what}: {identity:?}"
);
}
if cfg!(target_os = "macos") {
assert!(
identity.sdk.is_some(),
"a macOS host should identify its SDK: {identity:?}"
);
}
}
#[test]
fn the_identity_round_trips_through_its_recorded_form() {
let identity = LinkerIdentity {
driver: "/usr/bin/cc".into(),
driver_version: "cc version 14".into(),
linker_version: "GNU ld 2.42".into(),
crt_objects: BTreeMap::from([("crt1.o".into(), CacheDigest::blake3(b"crt"))]),
sdk: None,
deployment_target: Some("11.0".into()),
};
let recorded = canonical_json(&identity).unwrap();
assert_eq!(
serde_json::from_slice::<LinkerIdentity>(&recorded).unwrap(),
identity
);
}
fn identity_without_agent() -> Result<LinkerIdentity> {
let driver = which::which("cc")?;
let search = search_dirs(&driver);
probe(&driver, None, search.as_ref()).map(|(identity, _)| identity)
}
#[test]
fn search_dirs_are_read_in_the_order_the_driver_looks() {
let separator = if cfg!(windows) { ';' } else { ':' };
let text = format!(
"install: /usr/lib/gcc/x86_64-linux-gnu/13/\nprograms: =/opt/x/{separator}/usr/libexec/gcc/x86_64-linux-gnu/13/\nlibraries: =/usr/lib/gcc/x86_64-linux-gnu/13/{separator}/lib/x86_64-linux-gnu/\n"
);
let dirs = parse_search_dirs(&text).expect("both lists are present");
assert_eq!(
dirs.programs,
vec![
PathBuf::from("/opt/x/"),
PathBuf::from("/usr/libexec/gcc/x86_64-linux-gnu/13/")
]
);
assert_eq!(
dirs.libraries,
vec![
PathBuf::from("/usr/lib/gcc/x86_64-linux-gnu/13/"),
PathBuf::from("/lib/x86_64-linux-gnu/")
]
);
assert_eq!(parse_search_dirs("nothing here"), None);
assert_eq!(
parse_search_dirs("Programme: =/opt/x/\nBibliotheken: =/lib/\n"),
None
);
assert_eq!(
parse_search_dirs("programs: =/opt/x/\nlibraries: =\n"),
None
);
}
#[test]
fn rootless_search_dirs_are_placed_under_the_working_directory() {
let separator = if cfg!(windows) { ';' } else { ':' };
let text = format!(
"install: /usr/lib/gcc/x86_64-linux-gnu/15/\nprograms: =/usr/libexec/gcc/x86_64-linux-gnu/15/\nlibraries: =/usr/lib/wsl/lib/x86_64-linux-gnu/15/{separator}./x86_64-linux-gnu/15/{separator}./{separator}/usr/lib/gcc/x86_64-linux-gnu/15/\n"
);
let dirs = parse_search_dirs(&text).expect("both lists are present");
let here = std::env::current_dir().unwrap();
assert_eq!(
dirs.libraries,
vec![
PathBuf::from("/usr/lib/wsl/lib/x86_64-linux-gnu/15/"),
here.join("x86_64-linux-gnu").join("15"),
here.clone(),
PathBuf::from("/usr/lib/gcc/x86_64-linux-gnu/15/"),
]
);
assert!(dirs.libraries.iter().all(|directory| directory.has_root()));
assert_eq!(
dirs.programs,
vec![PathBuf::from("/usr/libexec/gcc/x86_64-linux-gnu/15/")]
);
}
#[test]
fn a_search_through_the_working_directory_is_told_from_the_environment() {
let environment = |library_path: Option<&str>| {
BTreeMap::from([("LIBRARY_PATH".to_string(), library_path.map(str::to_owned))])
};
let search_depends_on_working_directory = |environment: &BTreeMap<String, Option<String>>| {
super::search_depends_on_working_directory(|name| {
environment
.get(name)
.and_then(|value| value.as_deref())
.map(std::ffi::OsString::from)
})
};
let separator = if cfg!(windows) { ";" } else { ":" };
assert!(search_depends_on_working_directory(&environment(Some(
&format!("/usr/lib/wsl/lib{separator}")
))));
assert!(search_depends_on_working_directory(&environment(Some(
&format!("/usr/lib/wsl/lib{separator}lib")
))));
assert!(!search_depends_on_working_directory(&environment(Some(
"/usr/lib/wsl/lib"
))));
assert!(!search_depends_on_working_directory(&environment(None)));
assert!(!search_depends_on_working_directory(&BTreeMap::new()));
assert!(search_depends_on_working_directory(&BTreeMap::from([(
"COMPILER_PATH".to_string(),
Some("tools".to_string())
)])));
let prefix =
|value: &str| BTreeMap::from([("GCC_EXEC_PREFIX".to_string(), Some(value.to_string()))]);
assert!(search_depends_on_working_directory(&prefix("gcc-")));
assert!(!search_depends_on_working_directory(&prefix(
"/usr/lib/gcc/"
)));
}
#[test]
#[cfg(unix)]
fn working_directories_key_apart_even_when_not_utf8() {
use std::os::unix::ffi::OsStrExt as _;
assert_eq!(working_directory_key(Path::new("/work/app")), "/work/app");
let first = PathBuf::from(OsStr::from_bytes(b"/work/\xff"));
let second = PathBuf::from(OsStr::from_bytes(b"/work/\xfe"));
assert_eq!(
first.to_string_lossy(),
second.to_string_lossy(),
"the lossy spellings collide, which is the point"
);
assert_ne!(
working_directory_key(&first),
working_directory_key(&second)
);
assert!(working_directory_key(&first).starts_with("hex:"));
}
#[test]
#[cfg(unix)]
fn a_rootless_element_is_found_in_a_search_variable_that_is_not_utf8() {
use std::os::unix::ffi::OsStrExt as _;
let value = |bytes: &[u8]| {
let value = std::ffi::OsString::from(OsStr::from_bytes(bytes));
move |name: &str| (name == "LIBRARY_PATH").then(|| value.clone())
};
assert!(search_depends_on_working_directory(value(
b"/usr/lib/\xff:"
)));
assert!(search_depends_on_working_directory(value(
b"/usr/lib/\xff:lib"
)));
assert!(!search_depends_on_working_directory(value(
b"/usr/lib/\xff:/usr/lib"
)));
}
#[test]
#[cfg(unix)]
fn a_candidate_made_executable_breaks_its_pin() {
use std::os::unix::fs::PermissionsExt as _;
let directory = tempfile::tempdir().unwrap();
let candidate = directory.path().join("ld");
std::fs::write(&candidate, "not yet a linker").unwrap();
std::fs::set_permissions(&candidate, std::fs::Permissions::from_mode(0o644)).unwrap();
let pin = PinnedFile::describe(&candidate).unwrap();
assert!(pin.holds());
std::fs::set_permissions(&candidate, std::fs::Permissions::from_mode(0o755)).unwrap();
assert!(!pin.holds());
}
#[test]
#[cfg(unix)]
fn a_driver_named_program_pins_the_directories_searched_before_it() {
let directory = tempfile::tempdir().unwrap();
let (first, second, third) = (
directory.path().join("first"),
directory.path().join("second"),
directory.path().join("third"),
);
for dir in [&first, &second, &third] {
std::fs::create_dir(dir).unwrap();
}
std::fs::write(second.join("ld"), "linker").unwrap();
let search = SearchDirs {
programs: vec![first.clone(), second.clone(), third.clone()],
libraries: Vec::new(),
};
let located = Located::program(OsStr::new("ld"), &second.join("ld"), Some(&search)).unwrap();
let pins = located.pins.clone().into_vec();
assert_eq!(
pins.iter().map(|pin| pin.path.clone()).collect::<Vec<_>>(),
vec![first.join("ld"), second.join("ld")]
);
assert!(pins[0].state.is_none() && pins[1].state.is_some());
std::fs::write(first.join("ld"), "another linker").unwrap();
assert!(!pins[0].holds() && pins[1].holds());
let unmapped = Located::program(OsStr::new("ld"), &second.join("ld"), None).unwrap();
assert_eq!(unmapped.path, second.join("ld"));
assert!(unmapped.pins.into_vec().is_empty());
}
#[test]
#[cfg(unix)]
fn a_bare_program_name_pins_the_driver_directories_and_the_path_search() {
let directory = tempfile::tempdir().unwrap();
let search = SearchDirs {
programs: vec![directory.path().to_path_buf()],
libraries: Vec::new(),
};
let shell = which::which("sh").unwrap();
let located = Located::program(OsStr::new("sh"), Path::new("sh"), Some(&search)).unwrap();
assert_eq!(located.path, shell);
let pins = located.pins.into_vec();
assert_eq!(
pins.first().map(|pin| pin.path.clone()),
Some(directory.path().join("sh"))
);
assert!(pins.first().is_some_and(|pin| pin.state.is_none()));
assert_eq!(pins.last().map(|pin| pin.path.clone()), Some(shell));
}
#[test]
fn a_found_object_pins_the_library_directories_searched_before_it() {
let directory = tempfile::tempdir().unwrap();
let (early, late) = (
directory.path().join("early"),
directory.path().join("late"),
);
std::fs::create_dir_all(&late).unwrap();
std::fs::create_dir_all(&early).unwrap();
std::fs::write(late.join("crt1.o"), "startup").unwrap();
let mut pins = Pins::default();
pin_search(
&mut pins,
OsStr::new("crt1.o"),
&late.join("crt1.o"),
&[early.clone(), late.clone(), directory.path().join("never")],
);
assert_eq!(
pins.into_vec()
.iter()
.map(|pin| (pin.path.clone(), pin.state.is_some()))
.collect::<Vec<_>>(),
vec![(early.join("crt1.o"), false), (late.join("crt1.o"), true)]
);
}
#[test]
#[cfg(unix)]
fn an_undescribable_file_unpins_without_failing() {
use std::os::unix::fs::PermissionsExt as _;
let directory = tempfile::tempdir().unwrap();
let closed = directory.path().join("closed");
std::fs::create_dir(&closed).unwrap();
std::fs::set_permissions(&closed, std::fs::Permissions::from_mode(0o000)).unwrap();
let inside = closed.join("ld");
let describable = std::fs::metadata(&inside)
.map(|_| true)
.unwrap_or_else(|error| error.kind() == std::io::ErrorKind::NotFound);
let mut pins = Pins::default();
pins.add(directory.path());
pins.add(&inside);
std::fs::set_permissions(&closed, std::fs::Permissions::from_mode(0o755)).unwrap();
if describable {
assert_eq!(pins.into_vec().len(), 2);
} else {
assert!(pins.clone().into_vec().is_empty(), "{pins:?}");
let mut more = Pins::default();
more.add(directory.path());
more.extend(pins);
assert!(more.into_vec().is_empty());
}
}
#[test]
#[cfg(target_os = "linux")]
fn a_probe_pins_the_files_it_read() {
let driver = which::which("cc").unwrap();
let search = search_dirs(&driver).expect("the driver reports its search directories");
let (identity, pins) = probe(&driver, None, Some(&search)).unwrap();
assert!(pins.iter().all(|pin| pin.path.is_absolute()), "{pins:?}");
assert!(pins.iter().all(PinnedFile::holds), "{pins:?}");
let present = pins.iter().filter(|pin| pin.state.is_some()).count();
assert!(present >= 2 + identity.crt_objects.len(), "{pins:?}");
assert_eq!(pins[0].path, driver);
let first_program_dir = search.programs.first().expect("a program directory");
assert!(
pins.iter()
.any(|pin| pin.path == first_program_dir.join("ld")),
"{pins:?}"
);
}
#[test]
fn a_fuse_ld_selection_resolves_or_is_refused() {
let Ok(driver) = which::which("cc") else {
return;
};
let bogus = resolve_fuse_ld(
&driver,
"definitely-not-a-real-linker",
search_dirs(&driver).as_ref(),
);
assert!(
bogus.is_err(),
"a nonexistent linker must not resolve: {bogus:?}"
);
let direct = Path::new(if cfg!(windows) { "C:/ld" } else { "/opt/ld" });
assert_eq!(
resolve_fuse_ld(
&driver,
direct.to_str().unwrap(),
search_dirs(&driver).as_ref()
)
.unwrap()
.path,
direct
);
for name in ["mold", "lld", "bfd", "gold"] {
if let Ok(located) = resolve_fuse_ld(&driver, name, search_dirs(&driver).as_ref()) {
let program = &located.path;
assert!(
program.is_absolute() && program.exists(),
"{name} resolved to {program:?}"
);
let pins = located.pins.clone().into_vec();
assert_eq!(pins.last().map(|pin| &pin.path), Some(program));
}
}
}
#[test]
fn a_link_is_only_identified_once_its_essentials_resolve() {
let directory = tempfile::tempdir().unwrap();
let placed = |name: &str| {
let path = directory.path().join(name);
std::fs::write(&path, name).unwrap();
path
};
let probes = FileProbes {
startup: &["Scrt1.o", "crt1.o"],
libc: &["libc.so.6", "libc.a"],
rest: &["crti.o"],
};
let resolved = probe_files(&probes, |name| {
matches!(name, "crt1.o" | "libc.a").then(|| placed(name))
})
.unwrap();
assert_eq!(
resolved.keys().collect::<Vec<_>>(),
["crt1.o", "libc.a"],
"only what resolved belongs in the key"
);
assert!(
probe_files(&probes, |name| (name == "crt1.o").then(|| placed(name))).is_err(),
"no libc should refuse"
);
assert!(
probe_files(&probes, |name| (name == "libc.a").then(|| placed(name))).is_err(),
"no startup object should refuse"
);
assert!(
probe_files(&probes, |_| None).is_err(),
"nothing should refuse"
);
let glibc = probe_files(&probes, |name| {
matches!(name, "crt1.o" | "libc.so.6").then(|| placed(name))
})
.unwrap();
assert_ne!(glibc, resolved);
}
#[test]
fn a_platform_that_places_nothing_is_still_identified() {
let probes = FileProbes {
startup: &[],
libc: &[],
rest: &[],
};
assert!(probe_files(&probes, |_| None).unwrap().is_empty());
}
#[test]
fn a_windows_identity_requires_toolset_sdk_and_both_crts() {
let version = windows_sdk_identity_for(|name| match name {
"VCToolsVersion" => Some("14.40".into()),
"WindowsSDKVersion" => Some("10.0.26100.0".into()),
_ => None,
})
.unwrap();
assert!(version.contains("VCToolsVersion=14.40"));
assert!(windows_sdk_identity_for(|_| None).is_err());
let directory = tempfile::tempdir().unwrap();
std::fs::write(directory.path().join("vcruntime.lib"), b"vc runtime").unwrap();
std::fs::write(directory.path().join("ucrt.lib"), b"universal crt").unwrap();
let identity = windows_crt_objects_in(&[directory.path().to_path_buf()]).unwrap();
assert_eq!(identity.len(), 2);
std::fs::remove_file(directory.path().join("ucrt.lib")).unwrap();
assert!(windows_crt_objects_in(&[directory.path().to_path_buf()]).is_err());
}
#[cfg(target_os = "macos")]
#[test]
fn the_sdk_identity_follows_sdkroot() {
let Ok(Some(_)) = sdk_identity_for(None) else {
return;
};
let overridden = sdk_identity_for(Some("/nonexistent.sdk".into()));
assert!(
overridden.is_err(),
"an unusable SDKROOT must not report the default SDK: {overridden:?}"
);
}
#[test]
fn the_platform_gate_does_not_depend_on_who_set_the_variable() {
assert_eq!(
crate::session::cache_links_supported(),
cfg!(any(target_os = "linux", target_os = "macos", windows))
);
if !crate::session::cache_links_supported() {
assert!(!crate::session::cache_links_requested());
}
}