use super::{ensure_generated_header, finalize_hashes, stampable_output_paths};
use crate::bin_cli::helpers::verify_walk;
use crate::core::backend::GeneratedFile;
use crate::core::hash;
use std::path::{Path, PathBuf};
const SOURCES_HASH_BEFORE: &str = "1111111111111111111111111111111111111111111111111111111111111111";
const SOURCES_HASH_AFTER: &str = "2222222222222222222222222222222222222222222222222222222222222222";
const ALEF_TOML: &[u8] = b"[workspace]\nlanguages = [\"go\"]\n";
const ALEF_TOML_ALT: &[u8] = b"[workspace]\nlanguages = [\"go\", \"python\"]\n";
const SEED_BODY: &str = "module example.invalid/fixture\n\ngo 1.26\n";
fn join(root: &Path, relative: &str) -> PathBuf {
relative.split('/').fold(root.to_path_buf(), |acc, part| acc.join(part))
}
fn seed_stamped_by_an_earlier_run(root: &Path, relative: &str, body: &str, _sources_hash: &str) -> PathBuf {
let path = join(root, relative);
std::fs::create_dir_all(path.parent().expect("fixture path has a parent")).expect("create parent dirs");
let headered = ensure_generated_header(&path, body);
let stamped = hash::inject_hash_line(&headered, &hash::compute_file_hash(&headered));
std::fs::write(&path, &stamped).expect("write seeded file");
path
}
fn stale_paths(root: &Path) -> Vec<String> {
verify_walk(root)
.expect("verify walk")
.into_iter()
.map(|mismatch| mismatch.path)
.collect()
}
#[test]
fn create_once_seed_marked_on_disk_is_not_restamped_when_only_generation_inputs_move() {
struct Case {
name: &'static str,
sources_hash_after: &'static str,
alef_toml_after: &'static [u8],
}
let cases = [
Case {
name: "an unrelated source file changes (sources_hash moves, alef.toml does not)",
sources_hash_after: SOURCES_HASH_AFTER,
alef_toml_after: ALEF_TOML,
},
Case {
name: "an unrelated alef.toml key changes (alef.toml moves, sources_hash does not)",
sources_hash_after: SOURCES_HASH_BEFORE,
alef_toml_after: ALEF_TOML_ALT,
},
];
for case in cases {
let root = tempfile::tempdir().expect("tempdir");
let path = seed_stamped_by_an_earlier_run(root.path(), "packages/go/go.mod", SEED_BODY, SOURCES_HASH_BEFORE);
assert_eq!(
stale_paths(root.path()),
Vec::<String>::new(),
"case `{}`: fixture must start verify-clean, or the assertion below proves nothing",
case.name
);
let files = vec![GeneratedFile {
path: PathBuf::from("packages/go").join("go.mod"),
content: SEED_BODY.to_owned(),
generated_header: false,
}];
let before = std::fs::read_to_string(&path).expect("read seeded file");
let paths = stampable_output_paths(&files, root.path());
let updated = finalize_hashes(&paths, case.sources_hash_after, case.alef_toml_after).expect("finalize hashes");
assert_eq!(
updated, 0,
"case `{}`: a file whose emitted content did not change must not be rewritten just \
because generation inputs moved -- rewriting it is exactly the whole-tree \
provenance churn this fix exists to stop",
case.name
);
assert_eq!(
stale_paths(root.path()),
Vec::<String>::new(),
"case `{}`: the file must still verify clean after the no-op finalize",
case.name
);
let after = std::fs::read_to_string(&path).expect("read file after finalize");
assert_eq!(
after, before,
"case `{}`: an unrelated generation-inputs change must not touch this file's bytes \
at all, not even its hash line",
case.name
);
}
}
#[test]
fn unmarked_output_with_no_marker_on_disk_stays_out_of_the_stamp_scope() {
let root = tempfile::tempdir().expect("tempdir");
let relative = "packages/go/unmarked.mod";
let path = join(root.path(), relative);
std::fs::create_dir_all(path.parent().expect("fixture path has a parent")).expect("create parent dirs");
std::fs::write(&path, SEED_BODY).expect("write unmarked file");
let files = vec![GeneratedFile {
path: PathBuf::from("packages/go").join("unmarked.mod"),
content: SEED_BODY.to_owned(),
generated_header: false,
}];
assert!(
stampable_output_paths(&files, root.path()).is_empty(),
"a file that carries no marker in memory and none on disk is not stamped output"
);
}
#[test]
fn in_memory_marked_output_stays_in_the_stamp_scope_even_when_absent_from_disk() {
let root = tempfile::tempdir().expect("tempdir");
let files = vec![GeneratedFile {
path: PathBuf::from("packages/go").join("binding.go"),
content: "package fixture\n".to_owned(),
generated_header: true,
}];
let paths = stampable_output_paths(&files, root.path());
assert_eq!(
paths,
std::iter::once(join(root.path(), "packages/go/binding.go")).collect(),
"the in-memory marker must still put a path in scope on its own"
);
}