#![cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::Command;
const CRATE_NAME: &str = "c013_fingerprint_fixture";
fn write_project(dir: &Path) {
std::fs::create_dir_all(dir.join("src")).expect("src dir");
std::fs::write(
dir.join("Cargo.toml"),
format!("[package]\nname = \"{CRATE_NAME}\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[workspace]\n"),
)
.expect("Cargo.toml");
std::fs::write(dir.join("src/main.rs"), "fn main() {}\n").expect("main.rs");
}
fn write_passthrough_wrapper(path: &Path) {
std::fs::write(path, "#!/bin/sh\nexec \"$@\"\n").expect("shim script");
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755)).expect("chmod shim");
}
fn build(dir: &Path, cargo_home: &Path, var: &str, wrapper: Option<&Path>) -> (bool, bool) {
let mut cmd = Command::new(env!("CARGO"));
cmd.arg("build")
.arg("--offline")
.arg("--message-format=json")
.arg("--manifest-path")
.arg(dir.join("Cargo.toml"))
.arg("--target-dir")
.arg(dir.join("target"))
.current_dir(dir)
.env("CARGO_HOME", cargo_home)
.env("CARGO_TERM_COLOR", "always")
.env("CARGO_BUILD_BUILD_DIR", dir.join("target"))
.env_remove("CARGO_TARGET_DIR")
.env_remove("CARGO_BUILD_TARGET_DIR")
.env_remove("CARGO_BUILD_TARGET")
.env("RUSTFLAGS", "")
.env_remove("CARGO_ENCODED_RUSTFLAGS")
.env_remove("CARGO_BUILD_RUSTFLAGS")
.env_remove("CARGO_BUILD_RUSTC")
.env("RUSTC", Path::new(env!("CARGO")).with_file_name("rustc"))
.env_remove("RUSTUP_TOOLCHAIN")
.env("RUSTUP_AUTO_INSTALL", "0")
.env_remove("CARGO_BUILD_RUSTC_WRAPPER")
.env_remove("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER")
.env_remove("RUSTC_WRAPPER")
.env_remove("RUSTC_WORKSPACE_WRAPPER");
if let Some(path) = wrapper {
cmd.env(var, path);
}
let output = cmd.output().expect("spawn cargo");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Cargo {var}={wrapper:?} failed ({}):\nstdout:\n{stdout}\nstderr:\n{stderr}",
output.status
);
let messages: Vec<serde_json::Value> = stdout
.lines()
.map(|line| serde_json::from_str(line).expect("Cargo emits valid JSON messages"))
.collect();
let artifacts: Vec<_> = messages
.iter()
.filter(|message| {
message["reason"] == "compiler-artifact" && message["target"]["name"] == CRATE_NAME
})
.collect();
assert_eq!(
artifacts.len(),
1,
"each build must report exactly our binary artifact:\n{stdout}\n{stderr}"
);
let artifact = artifacts[0];
let manifest = artifact["manifest_path"]
.as_str()
.expect("artifact manifest");
assert_eq!(
Path::new(manifest)
.canonicalize()
.expect("artifact manifest exists"),
dir.join("Cargo.toml")
.canonicalize()
.expect("fixture manifest"),
"the artifact must belong to this isolated fixture"
);
let fresh = artifact["fresh"].as_bool().expect("artifact freshness");
let executable = artifact["executable"]
.as_str()
.expect("binary artifact path");
assert!(
Path::new(executable).is_file(),
"Cargo must produce the binary"
);
assert!(
Command::new(executable)
.status()
.expect("run fixture binary")
.success(),
"the real compiled fixture must execute successfully"
);
(true, !fresh)
}
#[test]
fn workspace_wrapper_is_fingerprinted_and_plain_wrapper_is_not() {
let project = tempfile::tempdir().expect("project dir");
let cargo_home_tmp = tempfile::tempdir().expect("cargo home");
let shims = tempfile::tempdir().expect("shim dir");
write_project(project.path());
let plain_a = shims.path().join("plain-a");
let ws_a = shims.path().join("ws-a");
write_passthrough_wrapper(&plain_a);
write_passthrough_wrapper(&ws_a);
let (ok, compiled) = build(project.path(), cargo_home_tmp.path(), "RUSTC_WRAPPER", None);
assert!(ok && compiled, "baseline build must compile the crate");
let (ok, compiled) = build(
project.path(),
cargo_home_tmp.path(),
"RUSTC_WORKSPACE_WRAPPER",
None,
);
assert!(ok, "baseline repeat must succeed");
assert!(!compiled, "baseline repeat must be fresh");
let (ok, compiled) = build(
project.path(),
cargo_home_tmp.path(),
"RUSTC_WRAPPER",
Some(&plain_a),
);
assert!(ok);
assert!(
!compiled,
"plain RUSTC_WRAPPER is not fingerprinted: enabling it must \
NOT force a rebuild"
);
let (ok, compiled) = build(
project.path(),
cargo_home_tmp.path(),
"RUSTC_WORKSPACE_WRAPPER",
Some(&ws_a),
);
assert!(ok);
assert!(
compiled,
"first enablement of RUSTC_WORKSPACE_WRAPPER must force a \
one-time rebuild (its value is fingerprinted)"
);
let (ok, compiled) = build(
project.path(),
cargo_home_tmp.path(),
"RUSTC_WORKSPACE_WRAPPER",
Some(&ws_a),
);
assert!(ok);
assert!(
!compiled,
"second build under the same workspace wrapper must be fresh"
);
let (ok, compiled) = build(
project.path(),
cargo_home_tmp.path(),
"RUSTC_WORKSPACE_WRAPPER",
None,
);
assert!(ok);
assert!(
!compiled,
"removing the workspace wrapper restores prior artifacts \
without recompiling"
);
}