use super::*;
fn test_config(cache_dir: &Path) -> Config {
Config {
cache_dir: cache_dir.to_path_buf(),
stats_report: None,
verify: false,
incremental: false,
share_out_dir: false,
events: false,
cc: false,
remote: Default::default(),
http: Default::default(),
gc: Default::default(),
target: crate::config::TargetSettings {
views: false,
root: cache_dir.join("targets"),
},
}
}
#[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())]);
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("CARGO_INCREMENTAL").unwrap(), "0");
assert_eq!(values.get(VERIFY_ENV).unwrap(), "0");
session.finish().await.unwrap();
}
#[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_ne!(
build_identity(first.path(), &command),
build_identity(first.path(), &["test".to_string()]),
);
}
#[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"
);
}
#[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;
});
assert_eq!(cache_misses(&stats), 1);
}
#[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 writes_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 = 5;
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;
});
write_stats_report(&path, &stats).unwrap();
let report: serde_json::Value = serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap();
assert_eq!(report["version"], 2);
assert_eq!(report["session_duration_ns"], 42);
assert_eq!(report["hits"], 2);
assert_eq!(report["misses"], 2);
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);
}
#[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-cc"),
PathBuf::from("/usr/bin/cc"),
)),
cxx: None,
};
let mut environment = BTreeMap::new();
shims.apply_to(&mut environment);
assert_eq!(
environment.get("HOST_CC").map(String::as_str),
Some("/session/mbx-cc")
);
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-cc"),
PathBuf::from("/usr/bin/cc"),
)),
cxx: Some((
PathBuf::from("/session/mbx-cxx"),
PathBuf::from("/usr/bin/c++"),
)),
};
let mut environment = BTreeMap::new();
shims.apply_to(&mut environment);
for name in ["HOST_CC", "HOST_CXX", "MBX_REAL_CC", "MBX_REAL_CXX"] {
assert!(environment.contains_key(name), "{name} should be set");
}
}