use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
const HAZ_BIN: &str = env!("CARGO_BIN_EXE_haz");
fn fixture_tempdir(name: &str) -> TempDir {
let src = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/why")
.join(name);
let dst = TempDir::new().expect("tempdir creation failed");
copy_dir_recursive(&src, dst.path());
dst
}
fn copy_dir_recursive(src: &Path, dst: &Path) {
fs::create_dir_all(dst).unwrap();
for entry in fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if entry.file_type().unwrap().is_dir() {
copy_dir_recursive(&src_path, &dst_path);
} else {
fs::copy(&src_path, &dst_path).unwrap();
}
}
}
fn haz_why(cwd: &Path, target: &str) -> (String, String, Option<i32>) {
let output = Command::new(HAZ_BIN)
.args(["why", target])
.current_dir(cwd)
.output()
.expect("failed to spawn haz");
(
String::from_utf8(output.stdout).expect("haz stdout was not utf-8"),
String::from_utf8(output.stderr).expect("haz stderr was not utf-8"),
output.status.code(),
)
}
#[test]
fn aux_016_exit_zero_on_isolated_task_cache_miss() {
let tmp = fixture_tempdir("isolated_task");
let cwd = tmp.path().join("lib");
let (stdout, stderr, code) = haz_why(&cwd, "~:build");
assert_eq!(code, Some(0), "expected exit 0; stderr: {stderr}");
assert!(
stdout.starts_with("lib:build\n"),
"missing resolved identity line: {stdout}",
);
assert!(
stdout.contains("command: [\"true\"]"),
"missing command action line: {stdout}",
);
assert!(
stdout.contains("cache_key: "),
"missing cache_key line: {stdout}",
);
assert!(
stdout.contains("cache_lookup: miss: no entry"),
"missing cache_lookup line: {stdout}",
);
for header in [
"inputs:",
"outputs:",
"deps:",
"weakDeps:",
"mutex:",
"env.from_host:",
"env.override:",
] {
assert!(
!stdout.contains(header),
"section `{header}` must be omitted for an isolated task: {stdout}",
);
}
}
#[test]
fn aux_015_step3_annotates_producer_match() {
let tmp = fixture_tempdir("with_producer_match");
let generated = tmp.path().join("lib/generated.txt");
fs::write(&generated, b"placeholder bytes").unwrap();
let cwd = tmp.path().join("lib");
let (stdout, stderr, code) = haz_why(&cwd, "~:consumer");
assert_eq!(code, Some(0), "expected exit 0; stderr: {stderr}");
assert!(
stdout.contains("inputs:\n /lib/generated.txt (produced by lib:gen)"),
"missing produced-by annotation: {stdout}",
);
}
#[test]
fn aux_013_multi_match_reference_exits_nonzero() {
let tmp = fixture_tempdir("multi_project");
let cwd = tmp.path().join("lib_a");
let (_, stderr, code) = haz_why(&cwd, "^:build");
assert_ne!(
code,
Some(0),
"multi-match reference must exit non-zero; stderr: {stderr}",
);
assert!(
stderr.contains("resolves to") && stderr.contains("use a more specific form"),
"missing multi-match diagnostic: {stderr}",
);
}
#[test]
fn aux_013_unresolved_reference_exits_nonzero() {
let tmp = fixture_tempdir("isolated_task");
let cwd = tmp.path().join("lib");
let (_, stderr, code) = haz_why(&cwd, "~:nonexistent");
assert_ne!(
code,
Some(0),
"unresolved reference must exit non-zero; stderr: {stderr}",
);
assert!(
stderr.contains("could not resolve"),
"missing resolution diagnostic: {stderr}",
);
}
#[test]
fn aux_014_predecessor_not_cached_exits_nonzero_with_diagnostic() {
let tmp = fixture_tempdir("with_hard_predecessor");
let cwd = tmp.path().join("lib");
let (_, stderr, code) = haz_why(&cwd, "~:build");
assert_ne!(
code,
Some(0),
"missing predecessor cache must exit non-zero; stderr: {stderr}",
);
assert!(
stderr.contains("predecessor `lib:gen`") && stderr.contains("miss: no entry"),
"missing predecessor-not-cached diagnostic: {stderr}",
);
}