use super::*;
use crate::core::config::Language;
fn resolved_test_config() -> crate::core::config::ResolvedCrateConfig {
let cfg: crate::core::config::NewAlefConfig = toml::from_str(
r#"
[workspace]
languages = ["python"]
[[crates]]
name = "test-lib"
sources = ["src/lib.rs"]
[crates.test.python]
command = "pytest"
[crates.test.rust]
e2e = "cargo test"
"#,
)
.unwrap();
cfg.resolve().unwrap().remove(0)
}
fn scanned_names(names: &[&str]) -> Vec<String> {
let directory = tempfile::tempdir().expect("temporary project");
for name in names {
let path = directory.path().join(name);
let marker = crate::core::hash::header(crate::core::hash::CommentStyle::Hash);
std::fs::write(&path, format!("{marker}\nseeded = true\n")).expect("seed stamped file");
}
let mut found: Vec<String> = collect_alef_hashes(directory.path())
.into_iter()
.filter_map(|(path, _, _)| path.file_name()?.to_str().map(str::to_owned))
.collect();
found.sort();
found
}
fn scanned_relative_paths(relative_paths: &[&str]) -> Vec<String> {
let directory = tempfile::tempdir().expect("temporary project");
for relative in relative_paths {
let path = directory.path().join(relative);
std::fs::create_dir_all(path.parent().expect("seeded path has a parent")).expect("seed parent directory");
let marker = crate::core::hash::header(crate::core::hash::CommentStyle::Hash);
std::fs::write(&path, format!("{marker}\nseeded = true\n")).expect("seed stamped file");
}
let mut found: Vec<String> = collect_alef_hashes(directory.path())
.into_iter()
.filter_map(|(path, _, _)| {
path.strip_prefix(directory.path())
.ok()?
.to_str()
.map(|value| value.replace('\\', "/"))
})
.collect();
found.sort();
found
}
#[test]
fn the_ownership_walk_reaches_the_dot_directories_alef_stamps() {
let found = scanned_relative_paths(&[
"packages/reachable.toml",
".cargo/config.toml",
".github/skills/api/SKILL.md",
".venv/lib/cached.toml",
]);
assert!(
found.contains(&"packages/reachable.toml".to_string()),
"control: a stamped file outside every dot-directory must be found, else this test \
proves nothing about the dot-directory cases; walk returned {found:?}"
);
assert!(
found.contains(&".cargo/config.toml".to_string()),
"alef writes and stamps `.cargo/config.toml` itself; a stamp nothing ever reads back \
is not a freshness check. Walk returned {found:?}"
);
assert!(
found.contains(&".github/skills/api/SKILL.md".to_string()),
"generated agent skills are stamped alef output and must be verifiable; walk returned \
{found:?}"
);
assert!(
!found.contains(&".venv/lib/cached.toml".to_string()),
"the dot-directory prune must still keep the walk out of tool caches -- the fix is an \
allowlist of the dot-directories alef writes into, not a removal of the prune. Walk \
returned {found:?}"
);
}
#[test]
fn the_ownership_walk_does_not_descend_into_a_nested_worktree() {
let found = scanned_relative_paths(&[".claude/skills/api/SKILL.md", ".claude/worktrees/other/config.toml"]);
assert!(
found.contains(&".claude/skills/api/SKILL.md".to_string()),
"control: `.claude` must be walked, else the exclusion below is vacuous; walk \
returned {found:?}"
);
assert!(
!found.contains(&".claude/worktrees/other/config.toml".to_string()),
"a nested worktree is a different checkout of this repository; its stamps are not \
this tree's. Walk returned {found:?}"
);
}
#[test]
fn both_consumers_build_their_managed_set_only_from_the_shared_surface() {
let stage_calls = [
"pipeline::generate(",
"pipeline::generate_stubs(",
"pipeline::generate_service_api(",
"pipeline::generate_public_api(",
"pipeline::scaffold(",
"pipeline::readme(",
"e2e::generate_e2e(",
"docs::generate_docs_stage(",
];
let regions = [
(
"alef verify's frozen report",
include_str!("../helpers.rs")
.split("pub(crate) fn collect_managed_surface")
.next()
.expect("helpers splits on the shared collector"),
),
(
"alef adopt's candidate set",
include_str!("../aux_commands.rs")
.split("Commands::Adopt {")
.nth(1)
.and_then(|rest| rest.split("Commands::Migrate {").next())
.expect("aux_commands splits on the adopt arm"),
),
];
for (name, region) in regions {
for call in stage_calls {
assert!(
!region.contains(call),
"{name} calls {call} directly -- the frozen report and the candidate set \
must not enumerate generation stages separately, or they disagree again"
);
}
assert!(
region.contains("collect_managed_surface("),
"{name} must derive its managed set from the shared surface"
);
}
}
#[test]
fn ownership_walk_opens_every_extension_the_emit_side_stamps() {
assert_eq!(
scanned_names(&[
"foo-config.cmake",
"app.csproj",
"gem.gemspec",
"build.zig.zon",
"pom.xml"
]),
vec![
"app.csproj",
"build.zig.zon",
"foo-config.cmake",
"gem.gemspec",
"pom.xml"
],
);
}
#[test]
fn ownership_walk_opens_the_filename_keyed_files_the_emit_side_stamps() {
assert_eq!(scanned_names(&["makefile"]), vec!["makefile"]);
assert_eq!(
scanned_names(&[
"Makefile",
"GNUmakefile",
"Rakefile",
"Makevars",
"Makevars.in",
"Makevars.win.in",
"go.mod"
]),
vec![
"GNUmakefile",
"Makefile",
"Makevars",
"Makevars.in",
"Makevars.win.in",
"Rakefile",
"go.mod"
],
);
}
#[test]
fn ownership_walk_still_skips_an_extension_alef_never_stamps() {
assert!(scanned_names(&["notes.rtf", "archive.tar"]).is_empty());
}
#[test]
fn default_log_level_maps_verbosity_to_levels() {
assert_eq!(default_log_level(0, false), "info");
assert_eq!(default_log_level(1, false), "debug");
assert_eq!(default_log_level(2, false), "trace");
assert_eq!(default_log_level(9, false), "trace");
assert_eq!(default_log_level(0, true), "error");
assert_eq!(default_log_level(3, true), "error");
}
#[test]
fn resolve_test_languages_allows_explicit_test_only_language() {
let config = resolved_test_config();
let langs = resolve_test_languages(&config, Some(&["rust".to_string()]), true).unwrap();
assert_eq!(langs, vec![Language::Rust]);
}
#[test]
fn resolve_test_languages_appends_e2e_only_languages() {
let config = resolved_test_config();
let langs = resolve_test_languages(&config, None, true).unwrap();
assert_eq!(langs, vec![Language::Python, Language::Rust]);
}
#[test]
fn resolve_test_languages_omits_e2e_only_languages_without_e2e() {
let config = resolved_test_config();
let langs = resolve_test_languages(&config, None, false).unwrap();
assert_eq!(langs, vec![Language::Python]);
}
fn gen_file(rel: &str, content: &str) -> crate::core::backend::GeneratedFile {
crate::core::backend::GeneratedFile {
path: std::path::PathBuf::from(rel),
content: content.to_string(),
generated_header: true,
}
}
#[test]
fn generated_files_match_disk_true_when_bodies_match() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("binding.go"), "package x\n\nvar a = 1\n").unwrap();
let files = vec![gen_file("binding.go", "package x\n\nvar a = 1\n")];
assert!(generated_files_match_disk(&files, dir.path()));
}
#[test]
fn generated_files_match_disk_ignores_embedded_hash_line() {
let dir = tempfile::tempdir().unwrap();
let generated = "// This file is auto-generated by alef — DO NOT EDIT.\npackage x\n\nvar a = 1\n";
std::fs::write(
dir.path().join("binding.go"),
"// This file is auto-generated by alef — DO NOT EDIT.\n// alef:hash:deadbeef\npackage x\n\nvar a = 1\n",
)
.unwrap();
let files = vec![gen_file("binding.go", generated)];
assert!(generated_files_match_disk(&files, dir.path()));
}
#[test]
fn generated_files_match_disk_false_when_body_differs() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("binding.go"), "package x\n\nvar a = 1\n").unwrap();
let files = vec![gen_file("binding.go", "package x\n\nimport \"fmt\"\n\nvar a = 1\n")];
assert!(!generated_files_match_disk(&files, dir.path()));
}
#[test]
fn generated_files_match_disk_false_when_file_missing() {
let dir = tempfile::tempdir().unwrap();
let files = vec![gen_file("binding.go", "package x\n")];
assert!(!generated_files_match_disk(&files, dir.path()));
}
fn gen_file_unheadered(rel: &str, content: &str) -> crate::core::backend::GeneratedFile {
crate::core::backend::GeneratedFile {
path: std::path::PathBuf::from(rel),
content: content.to_string(),
generated_header: false,
}
}
#[test]
fn missing_managed_paths_reports_an_absent_headered_file() {
let dir = tempfile::tempdir().expect("tempdir");
let files = vec![gen_file("SomeType.java", "final class SomeType {}\n")];
let missing = missing_managed_paths(&files, dir.path());
assert_eq!(missing, vec![dir.path().join("SomeType.java").display().to_string()]);
}
#[test]
fn missing_managed_paths_reports_nothing_when_every_headered_file_exists() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(
dir.path().join("SomeType.java"),
"final class SomeType { /* stale */ }\n",
)
.unwrap();
let files = vec![gen_file("SomeType.java", "final class SomeType {}\n")];
assert!(missing_managed_paths(&files, dir.path()).is_empty());
}
#[test]
fn missing_managed_paths_ignores_an_absent_unheadered_scaffold_file() {
let dir = tempfile::tempdir().expect("tempdir");
let files = vec![gen_file_unheadered("Cargo.toml", "[package]\nname = \"demo\"\n")];
assert!(missing_managed_paths(&files, dir.path()).is_empty());
}
#[test]
fn marker_line_finds_the_line_carrying_the_provenance_marker() {
let header = crate::core::hash::header(crate::core::hash::CommentStyle::DoubleSlash);
assert_eq!(
marker_line(&header),
Some("// This file is auto-generated by alef — DO NOT EDIT.")
);
}
#[test]
fn marker_line_finds_nothing_in_content_without_a_marker() {
assert_eq!(marker_line("final class SomeType {}\n"), None);
}
#[test]
fn verify_walk_detects_an_edited_generated_file() {
let directory = tempfile::tempdir().expect("tempdir");
let path = directory.path().join("binding.rs");
let inputs_hash = "generation-inputs";
let original = "// This file is auto-generated by alef — DO NOT EDIT.\nfn value() -> u8 { 1 }\n";
let hash = crate::core::hash::compute_file_hash(inputs_hash, original);
let finalized = crate::core::hash::inject_hash_line(original, &hash);
std::fs::write(&path, finalized.replace("{ 1 }", "{ 2 }")).expect("edit generated file");
let stale = verify_walk(directory.path(), inputs_hash).expect("verify generated files");
assert_eq!(stale.len(), 1);
assert_eq!(stale[0].path, path.display().to_string());
}
#[test]
fn verify_walk_detects_a_hand_edited_dependency_version_in_a_generated_manifest() {
let directory = tempfile::tempdir().expect("tempdir");
let path = directory.path().join("Cargo.toml");
let inputs_hash = "generation-inputs";
let original = "# This file is auto-generated by alef — DO NOT EDIT.\n\
[dependencies]\n\
base64 = \"0.22\"\n";
let hash = crate::core::hash::compute_file_hash(inputs_hash, original);
let finalized = crate::core::hash::inject_hash_line(original, &hash);
std::fs::write(&path, finalized.replace("0.22", "0.23")).expect("hand-edit generated manifest");
let stale = verify_walk(directory.path(), inputs_hash).expect("verify generated files");
assert_eq!(
stale.len(),
1,
"a hand-edited dependency version must be reported stale even though inputs_hash is unchanged"
);
assert_eq!(stale[0].path, path.display().to_string());
}
#[test]
fn verify_walk_detects_a_mixed_stamped_and_unstamped_generated_tree() {
let directory = tempfile::tempdir().expect("tempdir");
let inputs_hash = "generation-inputs";
let path = directory.path().join("unstamped.rs");
std::fs::write(
&path,
"// This file is auto-generated by alef — DO NOT EDIT.\nfn generated() {}\n",
)
.expect("write generated file");
let stamped_path = directory.path().join("stamped.rs");
let stamped_body = "// This file is auto-generated by alef — DO NOT EDIT.\nfn stamped() {}\n";
let stamped_hash = crate::core::hash::compute_file_hash(inputs_hash, stamped_body);
std::fs::write(
&stamped_path,
crate::core::hash::inject_hash_line(stamped_body, &stamped_hash),
)
.expect("write stamped generated file");
let stale = verify_walk(directory.path(), inputs_hash).expect("verify generated files");
assert_eq!(stale.len(), 1);
assert_eq!(stale[0].path, path.display().to_string());
assert_eq!(stale[0].embedded, "<missing>");
}
fn write_stamped(dir: &std::path::Path, name: &str, key: &str, value: &str) -> std::path::PathBuf {
let path = dir.join(name);
let body = "// This file is auto-generated by alef — DO NOT EDIT.\nfn generated() {}\n";
let stamped = crate::core::hash::inject_stamp_line(body, key, value);
let hash = crate::core::hash::compute_file_hash("test-inputs-hash", &stamped);
std::fs::write(&path, crate::core::hash::inject_hash_line(&stamped, &hash)).expect("write stamped file");
path
}
#[test]
fn write_stamped_produces_a_file_the_hash_walk_actually_collects() {
let dir = tempfile::tempdir().expect("tempdir");
write_stamped(dir.path(), "header.h", "handle-abi", "1");
let collected = collect_alef_hashes(dir.path());
assert_eq!(
collected.len(),
1,
"the stamped fixture must be visible to the hash walk"
);
assert_eq!(
crate::core::hash::extract_stamp(&collected[0].2, "handle-abi").as_deref(),
Some("1"),
"the stamp must survive alongside the hash line"
);
}
#[test]
fn find_stamp_disagreement_reports_two_distinct_values() {
let dir = tempfile::tempdir().expect("tempdir");
write_stamped(dir.path(), "header.h", "handle-abi", "1");
write_stamped(dir.path(), "binding.zig", "handle-abi", "2");
let disagreement =
find_stamp_disagreement(dir.path(), "handle-abi").expect("two distinct stamp values must be reported");
assert_eq!(disagreement.key, "handle-abi");
assert_eq!(disagreement.examples.len(), 2);
let values: Vec<&str> = disagreement.examples.iter().map(|(_, v)| v.as_str()).collect();
assert!(values.contains(&"1"));
assert!(values.contains(&"2"));
}
#[test]
fn find_stamp_disagreement_is_none_when_every_stamped_file_agrees() {
let dir = tempfile::tempdir().expect("tempdir");
write_stamped(dir.path(), "header.h", "handle-abi", "2");
write_stamped(dir.path(), "binding.zig", "handle-abi", "2");
assert!(find_stamp_disagreement(dir.path(), "handle-abi").is_none());
}
#[test]
fn find_stamp_disagreement_is_none_when_nothing_is_stamped() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::write(
dir.path().join("header.h"),
"// This file is auto-generated by alef — DO NOT EDIT.\nfn generated() {}\n",
)
.expect("write unstamped file");
assert!(find_stamp_disagreement(dir.path(), "handle-abi").is_none());
}
#[test]
fn find_stamp_disagreement_ignores_a_different_key() {
let dir = tempfile::tempdir().expect("tempdir");
write_stamped(dir.path(), "header.h", "some-other-marker", "1");
write_stamped(dir.path(), "binding.zig", "some-other-marker", "2");
assert!(find_stamp_disagreement(dir.path(), "handle-abi").is_none());
}
fn stage_failure(paths: &[&str]) -> StageFailure {
StageFailure {
stage: "e2e",
message: "56 e2e assertion(s) reference a field the availability oracle cannot resolve".to_owned(),
paths: paths.iter().map(std::path::PathBuf::from).collect(),
}
}
#[test]
fn a_stage_failure_confined_to_e2e_paths_does_not_affect_an_unrelated_ownership_target() {
let failure = stage_failure(&["e2e/python/test_smoke.py", "e2e/go/smoke_test.go"]);
assert!(!failure.affects_any(&["packages/dart/rust/Cargo.toml".to_owned()]));
assert!(!failure.affects_any(&["packages/**/*.gemspec".to_owned()]));
}
#[test]
fn a_stage_failure_that_rendered_the_requested_target_does_affect_it() {
let failure = stage_failure(&["e2e/python/test_smoke.py", "e2e/go/smoke_test.go"]);
assert!(failure.affects_any(&["e2e/python/test_smoke.py".to_owned()]));
assert!(failure.affects_any(&["e2e/python/*.py".to_owned()]));
assert!(failure.affects_any(&[
"packages/dart/rust/Cargo.toml".to_owned(),
"e2e/go/smoke_test.go".to_owned(),
]));
}
#[test]
fn a_stage_failure_never_affects_an_empty_target_list() {
let failure = stage_failure(&["e2e/python/test_smoke.py"]);
assert!(!failure.affects_any(&[]));
}
fn swift_only_config() -> crate::core::config::ResolvedCrateConfig {
let cfg: crate::core::config::NewAlefConfig = toml::from_str(
r#"
[workspace]
languages = ["swift"]
[[crates]]
name = "toolkit"
sources = ["src/lib.rs"]
"#,
)
.unwrap();
cfg.resolve().unwrap().remove(0)
}
#[test]
fn a_post_build_owned_path_not_produced_in_band_is_not_reported_as_an_orphan() {
let dir = tempfile::tempdir().expect("tempdir");
let config = swift_only_config();
let api = crate::core::ir::ApiSurface::default();
let config_path = dir.path().join("alef.toml");
let owned_path = dir
.path()
.join("packages/swift/Sources/RustBridge/SwiftBridgeCore.swift");
std::fs::create_dir_all(owned_path.parent().unwrap()).expect("create Sources/RustBridge");
let header = crate::core::hash::header(crate::core::hash::CommentStyle::DoubleSlash);
let marked = crate::core::hash::inject_hash_line(&header, &"0".repeat(64));
std::fs::write(&owned_path, marked).expect("write post-build-owned file");
let found = find_missing_and_frozen_generated_files(&[Language::Swift], &api, &config, &config_path, dir.path())
.expect("collect_managed_surface must succeed over a swift-only crate");
assert!(
found.managed_paths.contains(&owned_path),
"post-build-owned paths must be folded into the managed surface: {:?}",
found.managed_paths
);
let orphans = super::super::verify_orphans::find_orphaned_generated_files(dir.path(), &found.managed_paths);
assert!(
orphans.is_empty(),
"a path a post-build step owns unguarded must never be reported as an orphan just \
because `alef verify` cannot run that step itself: {orphans:?}"
);
}
#[test]
fn find_missing_and_frozen_generated_files_splits_out_a_gitignored_managed_path() {
let dir = tempfile::tempdir().expect("tempdir");
let config = swift_only_config();
let api = crate::core::ir::ApiSurface::default();
let config_path = dir.path().join("alef.toml");
let baseline = find_missing_and_frozen_generated_files(&[Language::Swift], &api, &config, &config_path, dir.path())
.expect("collect_managed_surface must succeed over a swift-only crate");
assert!(
!baseline.missing.is_empty(),
"fixture precondition: a fresh directory with no generated output must have at least one \
managed path outstanding, or this test cannot prove anything -- got: {:?}",
baseline.missing
);
assert!(
baseline.missing_gitignored.is_empty(),
"no .gitignore exists yet, so nothing should split out: {:?}",
baseline.missing_gitignored
);
let target = baseline.missing.first().cloned().expect("checked non-empty above");
let target_relative = std::path::Path::new(&target)
.strip_prefix(dir.path())
.expect("missing paths are joined onto base_dir")
.to_owned();
let git_init_status = std::process::Command::new("git")
.arg("-C")
.arg(dir.path())
.args(["init", "--quiet"])
.status()
.expect("git init must run");
if !git_init_status.success() {
return;
}
let ignore_parent = dir
.path()
.join(target_relative.parent().unwrap_or_else(|| std::path::Path::new(".")));
std::fs::create_dir_all(&ignore_parent).expect("create the target's parent directory");
std::fs::write(
ignore_parent.join(".gitignore"),
format!(
"{}\n",
target_relative
.file_name()
.expect("a managed path has a file name")
.to_string_lossy()
),
)
.expect("write a .gitignore excluding exactly the chosen target");
let found = find_missing_and_frozen_generated_files(&[Language::Swift], &api, &config, &config_path, dir.path())
.expect("collect_managed_surface must succeed over a swift-only crate");
assert!(
found.missing_gitignored.contains(&target),
"the gitignored managed path must move to missing_gitignored, got: {:?}",
found.missing_gitignored
);
assert!(
!found.missing.contains(&target),
"the gitignored managed path must not remain in plain missing (its remedy differs -- \
`alef generate` cannot fix it), got: {:?}",
found.missing
);
}