xtask/tests/the_packaged_crate_names_its_commit.rs
//! **A `cargo install gunnar` binary names the commit it was published from**
//! (lane GUN-VERSION-STAMP, 2026-10-31).
//!
//! `cargo install gunnar` 1.6.0 from crates.io printed `gunnar 1.6.0 (unknown, built
//! 2026-07-13T00:00:00Z)`: a published crate has no `.git`, so `git describe` had nothing to
//! read. cargo writes `.cargo_vcs_info.json` (with `git.sha1`) into every package it makes from
//! a git checkout, and that is the commit the crate was published from.
//!
//! This test does what crates.io does: `cargo package -p gunnar-common`, the package unpacked
//! into a directory outside every git checkout, and a small binary built on the unpacked copy
//! that prints `--version` (the `Cli` of `gunnar_common::cli`, the one `gunnar --version`
//! renders). Then:
//!
//! 1. `--version` names the short sha from the package's `.cargo_vcs_info.json`, never `unknown`;
//! 2. with no `SOURCE_DATE_EPOCH`, it names no date: the package knows no commit date, and a
//! date the build made up would be a lie (and the wall clock would break reproducible builds);
//! 3. with `SOURCE_DATE_EPOCH` set, it names that date (the reproducible-builds knob);
//! 4. the same binary built on the git checkout keeps its old line: `git describe` and the
//! commit date.
use std::path::{Path, PathBuf};
use std::process::Command;
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("xtask has a parent")
.to_path_buf()
}
fn cargo() -> String {
std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
}
/// The target directory this test binary was built in (`<target>/debug/deps/<test>`), so the
/// nested builds below reuse their dependencies from one run to the next.
fn target_root() -> PathBuf {
if let Some(t) = std::env::var_os("CARGO_TARGET_DIR") {
return PathBuf::from(t);
}
let exe = std::env::current_exe().expect("the test binary has a path");
exe.ancestors()
.nth(3)
.expect("<target>/<profile>/deps/<test>")
.to_path_buf()
}
fn run(cmd: &mut Command, what: &str) -> String {
let out = cmd
.output()
.unwrap_or_else(|e| panic!("{what}: could not run: {e}"));
assert!(
out.status.success(),
"{what} failed ({}):\n{}\n{}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).into_owned()
}
/// The `git.sha1` of a `.cargo_vcs_info.json`, read by hand (xtask has no JSON crate).
fn vcs_sha1(json: &str) -> String {
let at = json.find("\"sha1\"").expect("the vcs info names git.sha1");
let rest = &json[at + "\"sha1\"".len()..];
let open = rest.find('"').expect("a quoted sha1");
let rest = &rest[open + 1..];
let sha = &rest[..rest.find('"').expect("a closed sha1")];
assert!(
sha.len() == 40 && sha.bytes().all(|b| b.is_ascii_hexdigit()),
"git.sha1 is a 40-hex commit id: {sha:?}"
);
sha.to_owned()
}
/// A small binary on `gunnar_common` at `dep`, printing what `gunnar --version` prints.
fn version_probe(dir: &Path, dep: &Path) {
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("Cargo.toml"),
format!(
"[package]\nname = \"version-probe\"\nversion = \"0.0.0\"\nedition = \"2021\"\npublish = false\n\n\
[dependencies]\ngunnar-common = {{ path = {:?}, default-features = false }}\nclap = \"4\"\n\n[workspace]\n",
dep.display().to_string()
),
)
.unwrap();
std::fs::write(
dir.join("src/main.rs"),
"use clap::Parser;\n\
fn main() {\n\
\x20 match gunnar_common::cli::Cli::try_parse_from([\"gunnar\", \"--version\"]) {\n\
\x20 Err(e) => print!(\"{e}\"),\n\
\x20 Ok(_) => panic!(\"--version parsed as a command\"),\n\
\x20 }\n\
}\n",
)
.unwrap();
// The workspace's lock: the probe builds the versions the workspace builds.
std::fs::copy(root().join("Cargo.lock"), dir.join("Cargo.lock")).unwrap();
}
/// `--version` of the probe at `dir`, built into `target` with `sde` as `SOURCE_DATE_EPOCH`.
fn probe_version(dir: &Path, target: &Path, sde: Option<&str>) -> String {
let mut cmd = Command::new(cargo());
cmd.args(["run", "--quiet", "--manifest-path"])
.arg(dir.join("Cargo.toml"))
.arg("--target-dir")
.arg(target)
.current_dir(dir)
// A builder's ref wins over everything (build_ref.rs); this test is about the package.
.env_remove("GUNNAR_BUILD_REF")
.env_remove("NORDISK_BUILD_REF")
.env_remove("SOURCE_DATE_EPOCH");
if let Some(sde) = sde {
cmd.env("SOURCE_DATE_EPOCH", sde);
}
run(&mut cmd, "the version probe").trim().to_owned()
}
#[test]
fn a_packaged_gunnar_names_the_commit_it_was_published_from() {
let work = target_root().join("packaged-version");
let pkg_target = work.join("package-target");
let probe_target = work.join("probe-target");
// 1. What crates.io gets: the package of gunnar-common, with its .cargo_vcs_info.json.
run(
Command::new(cargo())
.args([
"package",
"-p",
"gunnar-common",
"--no-verify",
"--allow-dirty",
])
.arg("--target-dir")
.arg(&pkg_target)
.current_dir(root()),
"cargo package -p gunnar-common",
);
let version = {
let manifest =
std::fs::read_to_string(root().join("crates/gunnar-common/Cargo.toml")).unwrap();
manifest
.lines()
.find_map(|l| l.trim().strip_prefix("version = \""))
.and_then(|v| v.strip_suffix('"'))
.expect("gunnar-common has a version")
.to_owned()
};
let crate_file = pkg_target.join(format!("package/gunnar-common-{version}.crate"));
// 2. Unpacked OUTSIDE every git checkout: crates.io's copy has no `.git` above it either.
let outside = std::env::temp_dir().join(format!(
"gunnar-packaged-version-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&outside).unwrap();
run(
Command::new("tar")
.arg("-xzf")
.arg(&crate_file)
.arg("-C")
.arg(&outside),
"unpacking the package",
);
let unpacked = outside.join(format!("gunnar-common-{version}"));
let in_git = Command::new("git")
.args(["rev-parse", "--git-dir"])
.current_dir(&unpacked)
.output()
.map(|o| o.status.success())
.unwrap_or(false);
assert!(
!in_git,
"{} is inside a git checkout; the test needs a temp dir outside one",
unpacked.display()
);
let vcs = std::fs::read_to_string(unpacked.join(".cargo_vcs_info.json"))
.expect("cargo package wrote .cargo_vcs_info.json");
let sha = vcs_sha1(&vcs);
let short = &sha[..7];
// 3. The packaged copy, built with no SOURCE_DATE_EPOCH: the sha, and no date at all.
let probe = outside.join("probe");
version_probe(&probe, &unpacked);
let line = probe_version(&probe, &probe_target, None);
assert!(
line.contains(short),
"a packaged gunnar must name its commit {short} (from .cargo_vcs_info.json): {line:?}"
);
assert!(
!line.contains("unknown"),
"a packaged gunnar must not say `unknown`: {line:?}"
);
assert!(
!line.contains("built"),
"with no SOURCE_DATE_EPOCH a packaged gunnar knows no date and must name none: {line:?}"
);
assert_eq!(
line,
format!(
"gunnar {version} ({}{})",
short,
if vcs.contains("\"dirty\": true") {
"-modified"
} else {
""
}
)
);
// 4. With SOURCE_DATE_EPOCH, that date and no other.
let line = probe_version(&probe, &probe_target, Some("1783900800"));
assert!(
line.contains(short) && line.ends_with(", built 2026-07-13T00:00:00Z)"),
"with SOURCE_DATE_EPOCH the packaged gunnar names that date: {line:?}"
);
// 5. The git checkout keeps its line: `git describe` and the commit's date.
let describe = run(
Command::new("git")
.args([
"-c",
"safe.directory=*",
"-c",
"core.checkStat=minimal",
"describe",
"--tags",
"--always",
"--dirty=-modified",
"--abbrev=7",
])
.current_dir(root()),
"git describe",
);
let checkout_probe = outside.join("checkout-probe");
version_probe(&checkout_probe, &root().join("crates/gunnar-common"));
let line = probe_version(&checkout_probe, &probe_target, None);
assert!(
line.starts_with(&format!("gunnar {version} ({}, built ", describe.trim())),
"a git checkout keeps `git describe` and the commit date: {line:?}"
);
assert!(!line.contains("unknown"), "{line:?}");
let _ = std::fs::remove_dir_all(&outside);
}