use super::*;
use crate::config::SummaryStyle;
#[test]
fn clippy_workspace_wrapper_is_peeled_before_rustc_parsing() {
let arguments = vec![
Path::new("toolchain")
.join(format!("rustc{}", std::env::consts::EXE_SUFFIX))
.into_os_string(),
"--crate-name".into(),
"fixture".into(),
"src/lib.rs".into(),
];
let driver =
Path::new("toolchain").join(format!("clippy-driver{}", std::env::consts::EXE_SUFFIX));
let (wrapper_argument, compiler_arguments) =
workspace_wrapper_arguments(driver.as_os_str(), &arguments);
assert_eq!(wrapper_argument, Some(arguments[0].as_os_str()));
assert_eq!(compiler_arguments, &arguments[1..]);
}
#[test]
fn an_unrecognized_workspace_wrapper_argument_is_left_for_transparent_execution() {
let arguments = vec!["custom-compiler".into(), "src/lib.rs".into()];
let (wrapper_argument, compiler_arguments) =
workspace_wrapper_arguments(OsStr::new("custom-driver"), &arguments);
assert_eq!(wrapper_argument, None);
assert_eq!(compiler_arguments, arguments);
}
#[test]
fn ambiguous_build_script_sidecars_are_refused() {
let directory = tempfile::tempdir().unwrap();
let invoked = directory.path().join(format!(
"build-script-build{}",
std::env::consts::EXE_SUFFIX
));
std::fs::write(&invoked, "shim").unwrap();
for hash in ["one", "two"] {
let name = format!(
"build_script_build-{hash}{}{}",
std::env::consts::EXE_SUFFIX,
BUILD_SCRIPT_REAL_SUFFIX
);
std::fs::write(directory.path().join(name), "real").unwrap();
}
assert_eq!(find_build_script_real_path(&invoked), None);
}
#[test]
fn cargo_build_script_executable_names_are_recognized() {
assert!(is_build_script_executable(Path::new("build-script-build")));
assert!(is_build_script_executable(Path::new("build_script_build")));
assert!(!is_build_script_executable(Path::new("build-script")));
}
fn test_config(cache_dir: &Path) -> Config {
Config {
cache_dir: cache_dir.to_path_buf(),
stats_report: None,
verify: false,
verify_sample_rate: 0,
incremental: false,
share_out_dir: false,
share_workspace_root: false,
build_script_execution: false,
events: false,
cc: false,
cc_store_path_specific: true,
remote: Default::default(),
http: Default::default(),
gc: Default::default(),
scheduler: Default::default(),
linker: Default::default(),
target: crate::config::TargetSettings {
views: false,
root: cache_dir.join("targets"),
},
}
}
#[tokio::test]
async fn cmake_selection_uses_the_final_build_environment() {
let cache = tempfile::tempdir().unwrap();
let session_dir = tempfile::tempdir().unwrap();
let mut session = CacheSession::start(session_dir.path(), &test_config(cache.path()))
.await
.unwrap();
session.cc_shims = Some(CcShims {
cc: None,
cxx: None,
targeted: Vec::new(),
});
let workspace = tempfile::tempdir().unwrap();
let selected = BTreeMap::from([
("CMAKE".to_string(), "/per-build/cmake".to_string()),
("HOST_CMAKE".into(), "/per-build/host-cmake".into()),
("TARGET_CMAKE".into(), "/per-build/target-cmake".into()),
(
"CMAKE_aarch64_unknown_linux_gnu".into(),
"/per-build/arm-cmake".into(),
),
]);
let mut environment = selected.clone();
session
.begin(
workspace.path(),
&workspace.path().join("target"),
&["build".into()],
&mut environment,
)
.await;
let programs: BTreeMap<String, PathBuf> =
serde_json::from_str(&environment["MBX_CMAKE_PROGRAMS"]).unwrap();
for (variable, program) in selected {
let shim = Path::new(&environment[&variable]);
assert!(shim.is_file());
assert_eq!(
programs[shim.file_stem().unwrap().to_str().unwrap()],
PathBuf::from(program)
);
}
session.finish().await.unwrap();
}
#[tokio::test]
async fn session_environment_directs_cargo_at_the_shim() {
let cache = tempfile::tempdir().unwrap();
let session_dir = tempfile::tempdir().unwrap();
let session = CacheSession::start(session_dir.path(), &test_config(cache.path()))
.await
.unwrap();
let workspace = tempfile::tempdir().unwrap();
let mut values = BTreeMap::from([
("RUSTC_WRAPPER".into(), "existing".into()),
(
"RUSTC_WORKSPACE_WRAPPER".into(),
"workspace-existing".into(),
),
("RUSTDOC".into(), "custom-rustdoc".into()),
]);
let run = session
.begin(
workspace.path(),
&workspace.path().join("target"),
&["build".to_string()],
&mut values,
)
.await;
assert!(run.is_some());
assert!(values.contains_key(SOCKET_ENV));
assert!(values.contains_key(STAGING_ENV));
assert_eq!(values.get(BUILD_ENV).unwrap().len(), 64);
let wrapper = Path::new(values.get("RUSTC_WRAPPER").unwrap());
assert_eq!(wrapper.file_stem().unwrap(), RUSTC_SHIM_STEM);
assert_eq!(values.get(PREVIOUS_RUSTC_WRAPPER_ENV).unwrap(), "existing");
assert_eq!(
values.get(PREVIOUS_RUSTC_WORKSPACE_WRAPPER_ENV).unwrap(),
"workspace-existing"
);
assert_eq!(values.get(REAL_RUSTDOC_ENV).unwrap(), "custom-rustdoc");
assert_eq!(
Path::new(values.get("RUSTDOC").unwrap())
.file_stem()
.unwrap(),
RUSTDOC_SHIM_STEM
);
assert_eq!(values.get("CARGO_INCREMENTAL").unwrap(), "0");
assert_eq!(values.get(VERIFY_ENV).unwrap(), "0");
assert_eq!(values.get(BUILD_SCRIPT_EXECUTION_ENV).unwrap(), "0");
session.finish().await.unwrap();
}
#[tokio::test]
async fn failed_incremental_recording_replaces_an_inherited_root() {
let cache = tempfile::tempdir().unwrap();
std::fs::write(cache.path().join("incremental"), b"not a directory").unwrap();
let session_dir = tempfile::tempdir().unwrap();
let session = CacheSession::start(session_dir.path(), &test_config(cache.path()))
.await
.unwrap();
let workspace = tempfile::tempdir().unwrap();
let target = workspace.path().join("target");
let mut values = BTreeMap::from([(
INCREMENTAL_ROOT_ENV.into(),
"/another/checkout/incremental".into(),
)]);
session
.begin(
workspace.path(),
&target,
&["build".to_string()],
&mut values,
)
.await;
assert_eq!(
values.get(INCREMENTAL_ROOT_ENV).map(PathBuf::from),
Some(target.join("mbx-incremental"))
);
session.finish().await.unwrap();
}
#[tokio::test]
async fn rustc_shim_path_survives_and_is_reused_across_sessions() {
let cache = tempfile::tempdir().unwrap();
let config = test_config(cache.path());
let first_path = {
let session_dir = tempfile::tempdir().unwrap();
let session = CacheSession::start(session_dir.path(), &config)
.await
.unwrap();
let path = session.rustc_shim.clone();
session.finish().await.unwrap();
path
};
assert!(
first_path.is_file(),
"the wrapper path cached by Cargo must survive its mbx session"
);
let second_dir = tempfile::tempdir().unwrap();
let second = CacheSession::start(second_dir.path(), &config)
.await
.unwrap();
assert_eq!(second.rustc_shim, first_path);
second.finish().await.unwrap();
}
#[cfg(windows)]
#[test]
fn concurrent_persistent_shim_installation_keeps_the_binary_intact() {
let directory = tempfile::tempdir().unwrap();
let executable = directory.path().join("mbx.exe");
let destination = directory.path().join("mbx-rustc.exe");
let contents = vec![0x5a; 1024 * 1024];
std::fs::write(&executable, &contents).unwrap();
let barrier = std::sync::Arc::new(std::sync::Barrier::new(32));
let mut installers = Vec::new();
for _ in 0..32 {
let executable = executable.clone();
let destination = destination.clone();
let barrier = std::sync::Arc::clone(&barrier);
installers.push(std::thread::spawn(move || {
barrier.wait();
link_path_shim(&executable, &destination)
}));
}
for installer in installers {
installer.join().unwrap().unwrap();
}
assert_eq!(std::fs::read(&executable).unwrap(), contents);
assert_eq!(std::fs::read(&destination).unwrap(), contents);
}
#[tokio::test]
async fn a_session_with_no_shim_connection_does_not_load_a_manifest() {
let cache = tempfile::tempdir().unwrap();
let session_dir = tempfile::tempdir().unwrap();
let session = CacheSession::start(session_dir.path(), &test_config(cache.path()))
.await
.unwrap();
let workspace = tempfile::tempdir().unwrap();
let mut environment = BTreeMap::new();
let run = session
.begin(
workspace.path(),
&workspace.path().join("target"),
&["build".to_string()],
&mut environment,
)
.await
.unwrap();
assert!(session.task.initialized.get().is_none());
run.commit().await.unwrap();
assert!(session.task.initialized.get().is_none());
assert_eq!(session.finish().await.unwrap().predictions_loaded, 0);
}
#[test]
fn nested_sessions_unwrap_the_outer_rustdoc_shim() {
let values = BTreeMap::from([
(
"RUSTDOC".into(),
Path::new("outer-session")
.join(shim_file_name(RUSTDOC_SHIM_STEM))
.to_string_lossy()
.into_owned(),
),
(REAL_RUSTDOC_ENV.into(), "custom-rustdoc".into()),
]);
assert_eq!(configured_rustdoc(&values), "custom-rustdoc");
}
#[cfg(unix)]
#[tokio::test]
async fn cc_shim_path_survives_and_is_reused_across_sessions() {
if resolve_on_path(CcLanguage::C.default_driver()).is_none() {
return;
}
let cache = tempfile::tempdir().unwrap();
let mut config = test_config(cache.path());
config.cc = true;
let first_path = {
let session_dir = tempfile::tempdir().unwrap();
let session = CacheSession::start(session_dir.path(), &config)
.await
.unwrap();
let path = session
.cc_shims
.as_ref()
.and_then(|shims| shims.cc.as_ref())
.map(|(shim, _)| shim.clone())
.unwrap();
session.finish().await.unwrap();
path
};
assert!(
first_path.is_file(),
"the compiler path cached by a build system must survive its mbx session"
);
let second_dir = tempfile::tempdir().unwrap();
let second = CacheSession::start(second_dir.path(), &config)
.await
.unwrap();
let second_path = second
.cc_shims
.as_ref()
.and_then(|shims| shims.cc.as_ref())
.map(|(shim, _)| shim.as_path())
.unwrap();
assert_eq!(
second_path, first_path,
"later sessions must reuse the compiler path a build system cached"
);
second.finish().await.unwrap();
}
#[cfg(unix)]
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn concurrent_sessions_can_install_shared_cc_shims() {
if resolve_on_path(CcLanguage::C.default_driver()).is_none() {
return;
}
let cache = tempfile::tempdir().unwrap();
let mut config = test_config(cache.path());
config.cc = true;
let barrier = Arc::new(tokio::sync::Barrier::new(8));
let mut starts = tokio::task::JoinSet::new();
for _ in 0..8 {
let config = config.clone();
let barrier = Arc::clone(&barrier);
starts.spawn(async move {
let session_dir = tempfile::tempdir().unwrap();
barrier.wait().await;
let session = CacheSession::start(session_dir.path(), &config).await?;
let shim = session
.cc_shims
.as_ref()
.and_then(|shims| shims.cc.as_ref())
.map(|(shim, _)| shim.clone())
.unwrap();
session.finish().await?;
Ok::<_, eyre::Report>(shim)
});
}
let mut installed = Vec::new();
while let Some(result) = starts.join_next().await {
installed.push(result.unwrap().unwrap());
}
assert_eq!(installed.len(), 8);
assert!(installed.iter().all(|shim| shim == &installed[0]));
assert!(installed[0].is_file());
}
#[tokio::test]
async fn incremental_builds_leave_cargo_incremental_alone() {
let cache = tempfile::tempdir().unwrap();
let session_dir = tempfile::tempdir().unwrap();
let mut config = test_config(cache.path());
config.incremental = true;
let session = CacheSession::start(session_dir.path(), &config)
.await
.unwrap();
let workspace = tempfile::tempdir().unwrap();
let mut values = BTreeMap::new();
session
.begin(
workspace.path(),
&workspace.path().join("target"),
&["build".to_string()],
&mut values,
)
.await;
assert!(!values.contains_key("CARGO_INCREMENTAL"));
session.finish().await.unwrap();
}
#[tokio::test]
async fn verify_mode_is_passed_to_the_shim() {
let cache = tempfile::tempdir().unwrap();
let session_dir = tempfile::tempdir().unwrap();
let mut config = test_config(cache.path());
config.verify = true;
let session = CacheSession::start(session_dir.path(), &config)
.await
.unwrap();
let workspace = tempfile::tempdir().unwrap();
let mut values = BTreeMap::new();
session
.begin(
workspace.path(),
&workspace.path().join("target"),
&["build".to_string()],
&mut values,
)
.await;
assert_eq!(values.get(VERIFY_ENV).unwrap(), "1");
session.finish().await.unwrap();
}
#[test]
fn identity_follows_the_dependency_graph_not_the_path() {
let first = tempfile::tempdir().unwrap();
let second = tempfile::tempdir().unwrap();
std::fs::write(first.path().join("Cargo.lock"), "version = 4\n").unwrap();
std::fs::write(second.path().join("Cargo.lock"), "version = 4\n").unwrap();
let command = ["build".to_string()];
assert_eq!(
build_identity(first.path(), &command),
build_identity(second.path(), &command),
"separate worktrees of one project must share a manifest"
);
std::fs::write(second.path().join("Cargo.lock"), "version = 3\n").unwrap();
assert_ne!(
build_identity(first.path(), &command),
build_identity(second.path(), &command),
);
assert_eq!(
build_identity(first.path(), &command),
build_identity(first.path(), &["test".to_string()]),
"Cargo commands in one dependency graph should share predictions",
);
}
#[test]
fn identity_falls_back_to_the_directory_name() {
let directory = tempfile::tempdir().unwrap();
let command = ["build".to_string()];
assert_eq!(
build_identity(directory.path(), &command).len(),
64,
"a project without a lockfile still gets an identity"
);
}
#[test]
fn path_shim_names_select_their_language() {
assert_eq!(path_shim_language("cc"), Some(CcLanguage::C));
assert_eq!(path_shim_language("gcc"), Some(CcLanguage::C));
assert_eq!(path_shim_language("clang"), Some(CcLanguage::C));
assert_eq!(path_shim_language("c++"), Some(CcLanguage::Cxx));
assert_eq!(path_shim_language("g++"), Some(CcLanguage::Cxx));
assert_eq!(path_shim_language("clang++"), Some(CcLanguage::Cxx));
assert_eq!(path_shim_language("gcc-13"), None);
assert_eq!(path_shim_language("mbx"), None);
}
#[test]
fn exec_identity_is_shared_across_checkouts_with_one_lockfile() {
let first = tempfile::tempdir().unwrap();
let second = tempfile::tempdir().unwrap();
std::fs::write(first.path().join("Cargo.lock"), "version = 4\n").unwrap();
std::fs::write(second.path().join("Cargo.lock"), "version = 4\n").unwrap();
let command = ["make".to_string()];
assert_eq!(
exec_identity(first.path(), &command),
exec_identity(second.path(), &command),
"worktrees of one project must share a manifest for predictions to travel"
);
assert_ne!(
exec_identity(first.path(), &command),
exec_identity(first.path(), &["make".to_string(), "-j8".to_string()]),
);
}
#[test]
fn exec_identity_falls_back_to_the_directory_name() {
let directory = tempfile::tempdir().unwrap();
let command = ["make".to_string()];
assert_eq!(
exec_identity(directory.path(), &command).len(),
64,
"a project with no lockfile and no git origin still gets an identity"
);
}
#[test]
fn reads_the_origin_from_jujutsu_remote_output() {
let remotes = "backup ssh://example.com/backup\norigin https://example.com/project.git\n";
assert_eq!(
jj_origin_url(remotes),
Some("https://example.com/project.git")
);
assert_eq!(
jj_origin_url("upstream https://example.com/project.git\n"),
None
);
}
#[test]
fn reads_mercurial_and_sapling_default_paths_as_origins() {
assert_eq!(
origin_marker_from_output(b"https://example.com/project\n"),
Some("origin\0https://example.com/project".to_string())
);
assert_eq!(origin_marker_from_output(b"\n"), None);
}
#[test]
fn a_nested_git_checkout_does_not_query_jujutsu() {
let directory = tempfile::tempdir().unwrap();
let outer = directory.path();
let inner = outer.join("vendor");
std::fs::create_dir(&inner).unwrap();
std::fs::create_dir(outer.join(".jj")).unwrap();
std::fs::create_dir(inner.join(".git")).unwrap();
assert_eq!(jj_origin_marker(&inner), None);
}
#[test]
fn native_checkouts_do_not_inherit_an_enclosing_git_origin() {
for marker in [".hg", ".sl"] {
let directory = tempfile::tempdir().unwrap();
let outer = directory.path();
let inner = outer.join("vendor");
std::fs::create_dir(&inner).unwrap();
std::fs::create_dir(inner.join(marker)).unwrap();
assert!(
Command::new("git")
.arg("init")
.arg("--quiet")
.arg(outer)
.status()
.unwrap()
.success()
);
assert!(
Command::new("git")
.arg("-C")
.arg(outer)
.args(["remote", "add", "origin", "https://example.com/outer.git"])
.status()
.unwrap()
.success()
);
assert_eq!(project_origin_marker(&inner), None, "marker: {marker}");
}
}
#[cfg(unix)]
#[test]
fn a_shim_directory_never_supplies_the_real_compiler() {
let directory = tempfile::tempdir().unwrap();
let shims = directory.path().join("shims");
let real_dir = directory.path().join("bin");
std::fs::create_dir(&shims).unwrap();
std::fs::create_dir(&real_dir).unwrap();
let other_mbx = directory.path().join("other-mbx");
std::fs::write(&other_mbx, b"#!/bin/sh\n").unwrap();
std::os::unix::fs::symlink(&other_mbx, shims.join("cc")).unwrap();
let real_cc = real_dir.join("cc");
std::fs::write(&real_cc, b"#!/bin/sh\n").unwrap();
let running = directory.path().join("mbx");
std::fs::write(&running, b"#!/bin/sh\n").unwrap();
let path = std::env::join_paths([shims.as_path(), real_dir.as_path()]).unwrap();
let resolved = resolve_in_path(&path, "cc", &running, &shims);
assert_eq!(
resolved.map(|path| std::fs::canonicalize(path).unwrap()),
Some(std::fs::canonicalize(&real_cc).unwrap()),
"a shim must never be chosen as the compiler it stands in for"
);
}
#[cfg(unix)]
#[test]
fn a_hard_linked_shim_is_recognized_as_the_same_binary() {
let directory = tempfile::tempdir().unwrap();
let binary = directory.path().join("mbx");
std::fs::write(&binary, b"#!/bin/sh\n").unwrap();
let shim_dir = directory.path().join("cc-path");
std::fs::create_dir(&shim_dir).unwrap();
let linked = shim_dir.join("cc");
std::fs::hard_link(&binary, &linked).unwrap();
assert!(is_same_binary(&linked, Some(&binary)));
let copied = shim_dir.join("c++");
std::fs::copy(&binary, &copied).unwrap();
assert!(!is_same_binary(&copied, Some(&binary)));
}
#[test]
fn handshake_rejects_version_skew() {
let response = serde_json::to_string(&AgentResponse::Hello {
protocol: AGENT_PROTOCOL_VERSION,
agent_version: "another-version".into(),
})
.unwrap();
assert!(validate_handshake_response(&response).is_err());
}
fn agent_stats(fill: impl FnOnce(&mut AgentStats)) -> AgentStats {
let mut stats = AgentStats::default();
fill(&mut stats);
stats
}
#[test]
fn qualification_results_are_not_reported_as_misses() {
let stats = agent_stats(|stats| {
stats.lookups = 5;
stats.hits = 2;
stats.verifications = 2;
stats.compiler =
BTreeMap::from([("miss".into(), mbx_cache_core::CompilerStats::new(1, 4_000))]);
});
assert_eq!(cache_misses(&stats), 1);
}
#[test]
fn a_compilation_that_probed_two_action_keys_is_one_miss() {
let stats = agent_stats(|stats| {
stats.lookups = 2;
stats.hits = 0;
stats.compiler =
BTreeMap::from([("miss".into(), mbx_cache_core::CompilerStats::new(1, 4_000))]);
});
assert_eq!(cache_misses(&stats), 1);
}
#[test]
fn an_incremental_compilation_is_counted_by_what_its_lookup_did() {
let stats = agent_stats(|stats| {
stats.lookups = 1;
stats.unconsulted = 1;
stats.incremental_compilations = 2;
stats.compiler = BTreeMap::from([
("miss".into(), mbx_cache_core::CompilerStats::new(1, 4_000)),
(
"unconsulted".into(),
mbx_cache_core::CompilerStats::new(1, 4_000),
),
]);
});
assert_eq!(cache_misses(&stats), 1);
let summary = short_summary(&stats);
assert!(
summary.contains("0 hits, 1 misses, 1 not looked up, 2 incremental"),
"{summary}"
);
}
#[test]
fn a_build_that_was_only_incremental_still_reports() {
let stats = agent_stats(|stats| {
stats.incremental_compilations = 3;
stats.compiler = BTreeMap::from([(
"unconsulted".into(),
mbx_cache_core::CompilerStats::new(3, 4_000),
)]);
});
assert!(should_display_short_stats(&stats));
assert!(short_summary(&stats).contains("3 incremental"));
}
#[test]
fn bypassed_and_unconsulted_compilations_are_not_misses() {
let stats = agent_stats(|stats| {
stats.unconsulted = 4;
stats.compiler = BTreeMap::from([
(
"bypass".into(),
mbx_cache_core::CompilerStats::new(7, 4_000),
),
(
"unconsulted".into(),
mbx_cache_core::CompilerStats::new(4, 4_000),
),
]);
});
assert_eq!(cache_misses(&stats), 0);
}
#[test]
fn a_hit_on_the_literal_key_is_not_also_a_miss() {
let stats = agent_stats(|stats| {
stats.lookups = 2;
stats.hits = 1;
});
assert_eq!(cache_misses(&stats), 0);
}
#[test]
fn a_loaded_manifest_that_matched_nothing_is_called_out() {
let unmatched = agent_stats(|stats| {
stats.unconsulted = 255;
stats.predictions_loaded = 257;
});
assert!(stale_manifest_note(&unmatched).unwrap().contains("257"));
let cold = agent_stats(|stats| stats.unconsulted = 255);
assert_eq!(stale_manifest_note(&cold), None);
let live = agent_stats(|stats| {
stats.unconsulted = 2;
stats.lookups = 253;
stats.predictions_loaded = 257;
});
assert_eq!(stale_manifest_note(&live), None);
let new_shape = agent_stats(|stats| {
stats.unconsulted = 2;
stats.predictions_loaded = 816;
});
assert_eq!(stale_manifest_note(&new_shape), None);
}
#[test]
fn compiler_only_sessions_are_reportable() {
let stats = agent_stats(|stats| {
stats.compiler =
BTreeMap::from([("bypass".into(), mbx_cache_core::CompilerStats::new(1, 42))]);
});
assert!(should_display_stats(&stats));
}
#[test]
fn a_session_that_only_failed_its_remote_is_reportable() {
let stats = agent_stats(|stats| stats.remote_failures = 3);
assert!(should_display_stats(&stats));
}
#[test]
fn short_summary_omits_routine_compiler_probe_bypasses() {
let routine = agent_stats(|stats| {
stats.bypasses = BTreeMap::from([
("compiler-query".into(), 2),
("standard-input".into(), 1),
("cc-compiler-query".into(), 3),
("cc-standard-input".into(), 4),
]);
});
assert!(!should_display_short_stats(&routine));
let mixed = agent_stats(|stats| {
stats.lookups = 4;
stats.hits = 3;
stats.compiler =
BTreeMap::from([("miss".into(), mbx_cache_core::CompilerStats::new(1, 4_000))]);
stats.bypasses = BTreeMap::from([
("compiler-query".into(), 2),
("standard-input".into(), 1),
("cc-compiler-query".into(), 3),
("cc-standard-input".into(), 4),
("native-library".into(), 5),
]);
});
let summary = short_summary(&mixed);
assert!(
summary.contains("3 hits, 1 misses, 5 bypassed"),
"{summary}"
);
assert!(!summary.contains("compiler-query"), "{summary}");
assert!(!summary.contains("standard-input"), "{summary}");
}
#[test]
fn ci_summary_explains_a_cold_object_cache_and_real_bypasses() {
let stats = agent_stats(|stats| {
stats.unconsulted = 669;
stats.session_duration_ns = 202_000_000_000;
stats.bypasses = BTreeMap::from([
("compiler-query".into(), 7),
("standard-input".into(), 3),
("native-library".into(), 4),
("linking-disabled".into(), 160),
]);
});
let summary = ci_summary(&stats);
assert!(
summary.contains("object cache: 0 hits, 0 misses, 669 not looked up, 164 bypassed"),
"{summary}"
);
assert!(
summary.contains("no usable prior inputs or matching prediction"),
"{summary}"
);
assert!(
summary.contains("160 linking-disabled, 4 native-library"),
"{summary}"
);
assert!(!summary.contains("compiler-query"), "{summary}");
assert!(!summary.contains("standard-input"), "{summary}");
assert!(
summary.contains("Cargo artifact reuse and CI cache archive transfers are not included"),
"{summary}"
);
}
#[test]
fn ci_summary_preserves_verification_and_failure_diagnostics() {
let stats = agent_stats(|stats| {
stats.lookups = 10;
stats.hits = 6;
stats.compiler =
BTreeMap::from([("miss".into(), mbx_cache_core::CompilerStats::new(2, 4_000))]);
stats.verifications = 2;
stats.divergences = 1;
stats.remote_failures = 3;
stats.background_upload_failures = 4;
stats.avoided_compiler_duration_ns = 10_000_000_000;
});
let summary = ci_summary(&stats);
assert!(summary.contains("6 hits, 2 misses"), "{summary}");
assert!(summary.contains("2 verified, 1 diverged"), "{summary}");
assert!(summary.contains("3 remote failures"), "{summary}");
assert!(summary.contains("4 background uploads failed"), "{summary}");
assert!(
summary.contains("estimated compiler time avoided (summed across compilations)"),
"{summary}"
);
let upload_only = agent_stats(|stats| stats.background_upload_failures = 1);
assert!(should_display_short_stats(&upload_only));
}
#[test]
fn ci_summary_explains_an_unmatched_manifest() {
let stats = agent_stats(|stats| {
stats.unconsulted = 20;
stats.predictions_loaded = 20;
});
assert!(ci_summary(&stats).contains("none matched this build"));
}
#[test]
fn an_off_summary_still_writes_the_versioned_stats_report() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("nested").join("stats.json");
let stats = agent_stats(|stats| {
stats.session_duration_ns = 42;
stats.lookups = 6;
stats.hits = 2;
stats.verifications = 1;
stats.prefetched_actions = 3;
stats.downloaded_bytes = 1024;
stats.restored_output_files = 7;
stats.restored_output_bytes = 2048;
stats.reflinked_output_files = 5;
stats.reflinked_output_bytes = 1536;
stats.copied_output_files = 2;
stats.copied_output_bytes = 512;
stats.avoided_compiler_duration_ns = 2_000;
stats.compiler =
BTreeMap::from([("miss".into(), mbx_cache_core::CompilerStats::new(3, 4_000))]);
stats.slow_compilations = BTreeMap::from([("slow_crate".into(), 3_000)]);
stats.remote_blob_requests = 4;
stats.remote_blob_pack_requests = 2;
stats.remote_blob_pack_blobs = 100;
stats.materialization_duration_ns = 9;
stats.predictions_loaded = 11;
});
let mut config = Config::for_test(directory.path());
config.stats_report = Some(path.clone());
display_stats(&stats, &config, SummaryStyle::Off);
let report: serde_json::Value = serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap();
assert_eq!(report["version"], 5);
assert_eq!(report["predictions_loaded"], 11);
assert_eq!(report["session_duration_ns"], 42);
assert_eq!(report["hits"], 2);
assert_eq!(report["misses"], 3);
assert_eq!(report["compiler_invocations_avoided"], 2);
assert_eq!(report["estimated_compiler_duration_avoided_ns"], 2_000);
assert_eq!(report["compiler"]["miss"]["invocations"], 3);
assert_eq!(report["compiler"]["miss"]["duration_ns"], 4_000);
assert_eq!(report["slow_compilations"][0]["crate_name"], "slow_crate");
assert_eq!(report["slow_compilations"][0]["duration_ns"], 3_000);
assert_eq!(report["prefetched_actions"], 3);
assert_eq!(report["downloaded_bytes"], 1024);
assert_eq!(report["restored_output_files"], 7);
assert_eq!(report["restored_output_bytes"], 2048);
assert_eq!(report["reflinked_output_files"], 5);
assert_eq!(report["reflinked_output_bytes"], 1536);
assert_eq!(report["copied_output_files"], 2);
assert_eq!(report["copied_output_bytes"], 512);
assert_eq!(report["remote_blob_requests"], 4);
assert_eq!(report["remote_blob_pack_requests"], 2);
assert_eq!(report["remote_blob_pack_blobs"], 100);
assert_eq!(report["materialization_duration_ns"], 9);
}
#[test]
fn finds_crate_names_in_transparent_invocations() {
assert_eq!(
crate_name_argument(&["--crate-name".into(), "fixture".into()]),
Some("fixture".into())
);
assert_eq!(
crate_name_argument(&["--crate-name=attached".into()]),
Some("attached".into())
);
assert_eq!(crate_name_argument(&["--version".into()]), None);
}
#[test]
fn recognizes_the_bypassed_invocations_that_run_a_linker() {
for arguments in [
vec!["--crate-type", "bin"],
vec!["--crate-type=cdylib"],
vec!["--crate-type", "lib,dylib"],
vec!["--crate-type=proc-macro"],
vec!["--crate-type=staticlib"],
vec!["--test", "--crate-type=lib"],
vec!["--crate-type=bin", "--emit=dep-info,link"],
vec!["--test", "--emit", "link=/tmp/out"],
] {
let arguments: Vec<OsString> = arguments.iter().map(OsString::from).collect();
assert!(links_natively(&arguments), "{arguments:?} links");
}
for arguments in [
vec!["--crate-type", "lib"],
vec!["--crate-type=rlib"],
vec!["--crate-type", "lib,rlib"],
vec!["--emit=metadata"],
vec!["--crate-name", "bin"],
vec!["--crate-type=bin", "--emit=metadata"],
vec!["--test", "--emit", "dep-info,metadata"],
vec!["--crate-type=cdylib", "--emit=dep-info,metadata"],
] {
let arguments: Vec<OsString> = arguments.iter().map(OsString::from).collect();
assert!(!links_natively(&arguments), "{arguments:?} does not link");
}
}
#[test]
fn managed_linkers_are_added_only_to_native_links() {
let mut link = vec!["--crate-type=bin".into(), "--emit=link".into()];
use_managed_linker(&mut link, OsStr::new("/tools/mold"));
assert!(link.contains(&OsString::from("-Clinker=clang")));
assert!(link.contains(&OsString::from("-Clink-arg=-fuse-ld=/tools/mold")));
let mut check = vec!["--crate-type=bin".into(), "--emit=metadata".into()];
use_managed_linker(&mut check, OsStr::new("/tools/mold"));
assert_eq!(check.len(), 2);
}
#[cfg(unix)]
#[test]
fn the_session_shim_tracks_the_binary_it_was_installed_from() {
let directory = tempfile::tempdir().unwrap();
let executable = std::env::current_exe().unwrap();
let shim = install_shim(&executable, directory.path(), ShimLink::Tracking).unwrap();
let metadata = std::fs::symlink_metadata(&shim).unwrap();
assert!(
metadata.file_type().is_symlink(),
"the session shim should be a symlink, found {metadata:?}"
);
assert_eq!(std::fs::read_link(&shim).unwrap(), executable);
}
#[cfg(unix)]
#[test]
fn the_installed_wrapper_pins_the_bytes_it_was_made_from() {
let directory = tempfile::tempdir().unwrap();
let executable = std::env::current_exe().unwrap();
let shim = install_shim(&executable, directory.path(), ShimLink::Pinned).unwrap();
assert!(
!std::fs::symlink_metadata(&shim)
.unwrap()
.file_type()
.is_symlink()
);
assert_eq!(
std::fs::metadata(&shim).unwrap().len(),
std::fs::metadata(&executable).unwrap().len()
);
}
#[cfg(unix)]
#[test]
fn a_shim_is_never_installed_as_a_link_to_nothing() {
let directory = tempfile::tempdir().unwrap();
for missing in [directory.path().join("gone"), PathBuf::from("relative/mbx")] {
assert!(
install_shim(&missing, directory.path(), ShimLink::Tracking).is_err(),
"{} should not install a shim",
missing.display()
);
}
}
#[cfg(unix)]
#[test]
fn a_relative_target_is_resolved_before_it_is_linked() {
let directory = tempfile::tempdir().unwrap();
let shim = directory.path().join(RUSTC_SHIM_STEM);
assert!(symlink_shim(Path::new("Cargo.toml"), &shim));
let target = std::fs::read_link(&shim).unwrap();
assert!(
target.is_absolute(),
"{} should be absolute",
target.display()
);
assert_eq!(
std::fs::canonicalize(&shim).unwrap(),
std::fs::canonicalize("Cargo.toml").unwrap()
);
}
#[test]
fn a_missing_cpp_compiler_still_leaves_c_compilations_cached() {
let shims = CcShims {
cc: Some((
PathBuf::from("/session/mbx-c"),
PathBuf::from("/usr/bin/cc"),
)),
cxx: None,
targeted: Vec::new(),
};
let mut environment = BTreeMap::new();
shims.apply_host(&mut environment);
assert_eq!(
environment.get("HOST_CC").map(String::as_str),
Some("/session/mbx-c")
);
assert_eq!(
environment.get("MBX_REAL_CC").map(String::as_str),
Some("/usr/bin/cc")
);
assert!(!environment.contains_key("HOST_CXX"));
assert!(!environment.contains_key("MBX_REAL_CXX"));
}
#[test]
fn both_compilers_present_are_both_redirected() {
let shims = CcShims {
cc: Some((
PathBuf::from("/session/mbx-c"),
PathBuf::from("/usr/bin/cc"),
)),
cxx: Some((
PathBuf::from("/session/mbx-cxx"),
PathBuf::from("/usr/bin/c++"),
)),
targeted: Vec::new(),
};
let mut environment = BTreeMap::new();
shims.apply_host(&mut environment);
for name in ["HOST_CC", "HOST_CXX", "MBX_REAL_CC", "MBX_REAL_CXX"] {
assert!(environment.contains_key(name), "{name} should be set");
}
}
#[test]
fn only_the_cc_crates_target_variables_name_a_cross_compiler() {
use CcLanguage::{C, Cxx};
for (variable, expected) in [
("TARGET_CC", Some(C)),
("TARGET_CXX", Some(Cxx)),
("CC_aarch64-unknown-linux-musl", Some(C)),
("CC_aarch64_unknown_linux_musl", Some(C)),
("CXX_aarch64-unknown-linux-musl", Some(Cxx)),
("CC", None),
("CXX", None),
("HOST_CC", None),
("CC_", None),
("CCACHE_DIR", None),
("CXXFLAGS", None),
("CC_FORCE_DISABLE", None),
("CC_KNOWN_WRAPPER_CUSTOM", None),
("CC_ENABLE_DEBUG_OUTPUT", None),
("CC_FOR_BUILD", None),
("CXX_FOR_BUILD", None),
("CC_gcc", None),
] {
assert_eq!(
targeted_compiler_language(variable).map(|l| format!("{l:?}")),
expected.map(|l| format!("{l:?}")),
"{variable}"
);
}
}
#[test]
fn a_compiler_that_is_already_a_shim_is_not_wrapped_again() {
let executable = std::env::current_exe().expect("current exe");
let shims = tempfile::tempdir().expect("tempdir");
let planted = shims.path().join("aarch64-linux-musl-gcc");
std::fs::write(&planted, "#!/bin/sh\nexit 0\n").expect("write shim");
assert_eq!(
resolve_named_compiler(&planted.display().to_string(), &executable, shims.path()),
None,
"a compiler inside the shim directory is a shim, not a compiler"
);
}
#[test]
fn a_cross_only_image_still_gets_its_named_compiler_wrapped() {
let shims = CcShims {
cc: None,
cxx: None,
targeted: vec![TargetedCompiler {
variable: "CC_aarch64-unknown-linux-musl".into(),
shim_name: "mbx-c-cc_aarch64-unknown-linux-musl".into(),
shim: PathBuf::from("/session/mbx-c-cc_aarch64-unknown-linux-musl"),
real: PathBuf::from("/usr/bin/aarch64-linux-musl-gcc"),
}],
};
let mut environment = BTreeMap::new();
shims.apply_host(&mut environment);
shims.apply_targeted(&mut environment);
assert!(!environment.contains_key("HOST_CC"));
assert_eq!(
environment
.get("CC_aarch64-unknown-linux-musl")
.map(String::as_str),
Some("/session/mbx-c-cc_aarch64-unknown-linux-musl")
);
assert!(!shims.pins().is_empty());
}
#[test]
fn a_compiler_named_as_a_command_is_not_wrapped() {
let executable = std::env::current_exe().expect("current exe");
let shims = tempfile::tempdir().expect("tempdir");
for value in ["ccache gcc", "", " ", "cc -m32"] {
assert_eq!(
resolve_named_compiler(value, &executable, shims.path()),
None,
"{value:?} is not a single executable"
);
}
}
#[test]
fn naming_a_host_compiler_does_not_cost_the_cross_one_its_shim() {
let shims = CcShims {
cc: Some((
PathBuf::from("/session/mbx-c"),
PathBuf::from("/usr/bin/cc"),
)),
cxx: None,
targeted: vec![TargetedCompiler {
variable: "CC_aarch64-unknown-linux-musl".into(),
shim_name: "mbx-c-cc_aarch64-unknown-linux-musl".into(),
shim: PathBuf::from("/session/mbx-c-cc_aarch64-unknown-linux-musl"),
real: PathBuf::from("/usr/bin/aarch64-linux-musl-gcc"),
}],
};
let mut environment = BTreeMap::new();
shims.apply_targeted(&mut environment);
assert_eq!(
environment
.get("CC_aarch64-unknown-linux-musl")
.map(String::as_str),
Some("/session/mbx-c-cc_aarch64-unknown-linux-musl")
);
assert!(!environment.contains_key("HOST_CC"));
assert_eq!(
shims.pins().get("mbx-c-cc_aarch64-unknown-linux-musl"),
Some(&PathBuf::from("/usr/bin/aarch64-linux-musl-gcc"))
);
}
#[test]
fn targeted_shim_names_dispatch_to_their_language() {
for (stem, expected) in [
("mbx-c", Some(CcLanguage::C)),
("mbx-cxx", Some(CcLanguage::Cxx)),
("mbx-c-cc_aarch64-unknown-linux-musl", Some(CcLanguage::C)),
(
"mbx-cxx-cxx_aarch64-unknown-linux-musl",
Some(CcLanguage::Cxx),
),
("mbx-c-target_cc", Some(CcLanguage::C)),
("mbx-cxx-target_cxx", Some(CcLanguage::Cxx)),
("mbx-rustc", None),
("gcc", Some(CcLanguage::C)),
("clang++", Some(CcLanguage::Cxx)),
] {
assert_eq!(cc_shim_language(stem), expected, "{stem}");
}
}
#[test]
fn the_shim_name_used_before_the_rename_is_still_recognised() {
assert_eq!(cc_shim_language("mbx-cc"), Some(CcLanguage::C));
assert_eq!(
cc_shim_language("mbx-cc-cc_aarch64-unknown-linux-musl"),
Some(CcLanguage::C)
);
}
#[test]
fn a_cross_compiler_is_pinned_under_its_pre_rename_name_too() {
let shims = CcShims {
cc: Some((
PathBuf::from("/session/mbx-c"),
PathBuf::from("/usr/bin/cc"),
)),
cxx: Some((
PathBuf::from("/session/mbx-cxx"),
PathBuf::from("/usr/bin/c++"),
)),
targeted: vec![
TargetedCompiler {
variable: "CC_aarch64-unknown-linux-musl".into(),
shim_name: "mbx-c-cc_aarch64-unknown-linux-musl".into(),
shim: PathBuf::from("/session/mbx-c-cc_aarch64-unknown-linux-musl"),
real: PathBuf::from("/usr/bin/aarch64-linux-musl-gcc"),
},
TargetedCompiler {
variable: "CXX_aarch64-unknown-linux-musl".into(),
shim_name: "mbx-cxx-cxx_aarch64-unknown-linux-musl".into(),
shim: PathBuf::from("/session/mbx-cxx-cxx_aarch64-unknown-linux-musl"),
real: PathBuf::from("/usr/bin/aarch64-linux-musl-g++"),
},
],
};
let pins = shims.pins();
let cross_cc = PathBuf::from("/usr/bin/aarch64-linux-musl-gcc");
assert_eq!(
pins.get("mbx-c-cc_aarch64-unknown-linux-musl"),
Some(&cross_cc)
);
assert_eq!(
pins.get("mbx-cc-cc_aarch64-unknown-linux-musl"),
Some(&cross_cc),
"the pre-rename name must reach the cross compiler, not the host one"
);
assert_eq!(
pins.keys().filter(|name| name.contains("cxx")).count(),
1,
"{pins:?}"
);
}
#[test]
fn no_shim_name_can_be_read_as_a_cross_compiler_prefix() {
let targeted = |variable: &str, language: CcLanguage| {
format!(
"{}-{}",
language.shim_stem(),
variable.to_ascii_lowercase().replace(['.', '/'], "_")
)
};
let names = [
CC_SHIM_STEM.to_string(),
CXX_SHIM_STEM.to_string(),
targeted("TARGET_CC", CcLanguage::C),
targeted("TARGET_CXX", CcLanguage::Cxx),
targeted("CC_aarch64-unknown-linux-musl", CcLanguage::C),
targeted("CXX_aarch64-unknown-linux-musl", CcLanguage::Cxx),
]
.into_iter()
.chain(PATH_SHIM_NAMES.iter().map(|(name, _)| (*name).to_string()));
for name in names {
let installed = format!("/var/cache/mbx/shims/{name}");
for suffix in ["-cc", "-gcc"] {
assert!(
!installed.ends_with(suffix),
"{installed} ends in {suffix}, which autoconf tooling reads as a machine triple"
);
}
}
}
#[test]
fn managed_target_linkers_follow_rustc_target_and_leave_host_alone() {
let selections = BTreeMap::from([
(
"aarch64-apple-darwin".into(),
PathBuf::from("/tools/arm/ld64.lld"),
),
(
"x86_64-apple-darwin".into(),
PathBuf::from("/tools/intel/ld64.lld"),
),
]);
for (argument, expected) in [
(
vec!["--target", "aarch64-apple-darwin"],
Some("/tools/arm/ld64.lld"),
),
(
vec!["--target=x86_64-apple-darwin"],
Some("/tools/intel/ld64.lld"),
),
(vec!["--crate-name", "build_script_build"], None),
(vec!["--target=other"], None),
] {
let arguments = argument.into_iter().map(OsString::from).collect::<Vec<_>>();
assert_eq!(
target_linker(&arguments, &selections).map(|p| p.as_path()),
expected.map(Path::new)
);
}
}
#[test]
fn json_target_linkers_match_equivalent_paths() {
let directory = tempfile::tempdir().unwrap();
let target = directory.path().join("custom.json");
std::fs::write(&target, "{}").unwrap();
let canonical = std::fs::canonicalize(&target).unwrap();
let linker = PathBuf::from("/tools/linker");
let selections = BTreeMap::from([(canonical.to_str().unwrap().to_owned(), linker.clone())]);
assert_eq!(
target_linker(&["--target".into(), target.into_os_string()], &selections),
Some(&linker)
);
#[cfg(unix)]
{
let alias = directory.path().join("alias.json");
std::os::unix::fs::symlink(&canonical, &alias).unwrap();
assert_eq!(
target_linker(&["--target".into(), alias.into_os_string()], &selections),
Some(&linker)
);
}
}
#[test]
fn routine_bypasses_are_debug_but_failed_cache_paths_are_warnings() {
assert!(super::expected_rustc_bypass(Some(
&mbx_cache_rustc::BypassReason::CompilerQuery
)));
assert!(super::expected_cc_bypass(Some(
&mbx_cache_cc::CcBypassReason::NotACompile
)));
assert!(!super::expected_rustc_bypass(Some(
&mbx_cache_rustc::BypassReason::InputRead {
path: "input.rs".into(),
message: "permission denied".into(),
}
)));
assert!(!super::expected_cc_bypass(Some(
&mbx_cache_cc::CcBypassReason::CompilerIdentityUnavailable("probe failed".into())
)));
assert!(!super::expected_rustc_bypass(None));
assert!(!super::expected_cc_bypass(None));
assert!(super::expected_cc_bypass(Some(
&mbx_cache_cc::CcBypassReason::SearchPathModifiedDuringCompilation("include".into())
)));
assert!(
matches!(super::bypass_diagnostic(true, "routine"), AgentRequest::RecordDebug { target, .. } if target == "mbx::session")
);
assert!(matches!(
super::bypass_diagnostic(false, "failed"),
AgentRequest::RecordWarning { .. }
));
}