use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
const HASH_MARKER: &str = concat!("alef:", "hash:");
const OWNERSHIP_MARKER: &str = "generated by alef";
const PROTECTED_PATHS: &[&str] = &[
"packages/swift/Sources/RustBridgeC/RustBridgeC.c",
"packages/swift/Sources/RustBridge/RustBridge.swift",
"packages/swift/rust/Cargo.toml",
"packages/swift/rust/build.rs",
];
fn alef_binary() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_alef"))
}
fn write_fixture(root: &Path) {
fs::create_dir_all(root.join("src")).expect("create fixture source directory");
fs::write(
root.join("Cargo.toml"),
"[package]\nname = \"atomicity-fixture\"\nversion = \"0.1.0\"\nedition = \"2024\"\n",
)
.expect("write fixture Cargo.toml");
fs::write(
root.join("src/lib.rs"),
"pub struct Record { pub value: String }\n\npub fn record_value(record: Record) -> String { record.value }\n",
)
.expect("write fixture source");
fs::write(
root.join("alef.toml"),
format!(
"[workspace]\nalef_version = \"{}\"\nlanguages = [\"swift\", \"go\", \"zig\"]\n\n\
[[crates]]\nname = \"atomicity-fixture\"\nsources = [\"src/lib.rs\"]\n\
version_from = \"Cargo.toml\"\n\n[crates.generate]\npublic_api = false\n",
env!("CARGO_PKG_VERSION")
),
)
.expect("write alef config");
}
fn seed_owned_files(root: &Path) {
for relative in PROTECTED_PATHS {
let path = root.join(relative);
fs::create_dir_all(path.parent().expect("protected file parent")).expect("create protected parent");
let content = fs::read_to_string(&path).unwrap_or_else(|_| format!("protected {relative}\n"));
fs::write(path, format!("// {HASH_MARKER}deadbeef\n{content}")).expect("mark protected file as prior-owned");
}
let swift_orphan = root.join("packages/swift/rust/src/obsolete.rs");
let go_orphan = root.join("packages/go/obsolete.go");
let unselected_orphan = root.join("packages/python/obsolete.py");
for orphan in [&swift_orphan, &go_orphan, &unselected_orphan] {
fs::create_dir_all(orphan.parent().expect("orphan parent")).expect("create orphan parent");
fs::write(orphan, format!("// {HASH_MARKER}deadbeef\n")).expect("seed orphan");
}
let crate_cache = fs::read_dir(root.join(".alef"))
.expect("read Alef cache")
.filter_map(Result::ok)
.map(|entry| entry.path())
.find(|path| path.is_dir() && path.join("ir.json").is_file())
.expect("find crate-scoped Alef cache");
let hashes = crate_cache.join("hashes");
fs::create_dir_all(&hashes).expect("create manifest directory");
let mut swift_manifest = PROTECTED_PATHS
.iter()
.map(|path| root.join(path).display().to_string())
.collect::<Vec<_>>();
swift_manifest.push(swift_orphan.display().to_string());
fs::write(
hashes.join("swift.manifest"),
format!("{}\n", swift_manifest.join("\n")),
)
.expect("write Swift manifest");
fs::write(hashes.join("go.manifest"), format!("{}\n", go_orphan.display())).expect("write Go manifest");
fs::write(
hashes.join("python.manifest"),
format!("{}\n", unselected_orphan.display()),
)
.expect("write unselected manifest");
}
fn fixture_command(root: &Path, program: &Path, fail_swift_post_build: bool) -> Command {
let fake_bin = root.join("fake-bin");
fs::create_dir_all(&fake_bin).expect("create fixture binary directory");
let fake_cargo = fake_bin.join(format!("cargo{}", std::env::consts::EXE_SUFFIX));
if !fake_cargo.exists() {
let source = root.join("cargo_shim.rs");
fs::write(&source, include_str!("cli_generate_atomicity/cargo_shim.rs")).expect("write Cargo shim source");
let compiled = Command::new("rustc")
.args(["--edition=2024", "--crate-name", "atomicity_cargo_shim"])
.arg(&source)
.arg("-o")
.arg(&fake_cargo)
.output()
.expect("compile native Cargo shim");
assert!(
compiled.status.success(),
"compile native Cargo shim: {}",
String::from_utf8_lossy(&compiled.stderr)
);
}
let path = std::env::var_os("PATH").unwrap_or_default();
let mut command = Command::new(program);
command.current_dir(root).env(
"PATH",
std::env::join_paths(std::iter::once(fake_bin).chain(std::env::split_paths(&path))).unwrap(),
);
command.env("REAL_CARGO", env!("CARGO"));
command.env("FAIL_SWIFT_POST_BUILD", if fail_swift_post_build { "1" } else { "0" });
command
}
fn run_generate(root: &Path, fail_swift_post_build: bool) -> Output {
fixture_command(root, &alef_binary(), fail_swift_post_build)
.args(["generate", "--lang", "swift,go,zig"])
.output()
.expect("run alef generate")
}
#[test]
fn native_cargo_shim_intercepts_both_path_styles_and_delegates_other_commands() {
let fixture = tempfile::tempdir().expect("create shim fixture");
let root = fixture.path();
for manifest in ["packages/swift/rust/Cargo.toml", r"packages\swift\rust\Cargo.toml"] {
for (fail, status) in [(true, 17), (false, 0)] {
let output = fixture_command(root, Path::new("cargo"), fail)
.args(["build", "--manifest-path", manifest])
.output()
.expect("invoke Cargo shim through PATH");
assert_eq!(output.status.code(), Some(status));
assert_eq!(
String::from_utf8(output.stderr).unwrap().trim(),
format!("atomicity fixture cargo: Swift post-build exit {status}")
);
}
}
let expected = Command::new(env!("CARGO")).arg("--version").output().unwrap();
let delegated = fixture_command(root, Path::new("cargo"), true)
.arg("--version")
.output()
.unwrap();
assert!(expected.status.success());
assert_eq!(delegated.status.code(), expected.status.code());
assert_eq!(delegated.stdout, expected.stdout);
assert_eq!(delegated.stderr, expected.stderr);
}
fn generated_hashed_files(root: &Path) -> Vec<PathBuf> {
walkdir::WalkDir::new(root.join("packages"))
.into_iter()
.filter_map(Result::ok)
.filter(|entry| entry.file_type().is_file())
.map(walkdir::DirEntry::into_path)
.filter(|path| fs::read_to_string(path).is_ok_and(|content| content.contains(HASH_MARKER)))
.collect()
}
fn generated_marked_files(root: &Path) -> Vec<PathBuf> {
walkdir::WalkDir::new(root.join("packages"))
.into_iter()
.filter_map(Result::ok)
.filter(|entry| entry.file_type().is_file())
.map(walkdir::DirEntry::into_path)
.filter(|path| {
fs::read_to_string(path).is_ok_and(|content| content.to_ascii_lowercase().contains(OWNERSHIP_MARKER))
})
.collect()
}
fn generated_ownership_paths(root: &Path) -> Vec<PathBuf> {
walkdir::WalkDir::new(root.join(".alef"))
.into_iter()
.filter_map(Result::ok)
.filter(|entry| {
entry.file_type().is_file()
&& entry.file_name().to_string_lossy().starts_with("generate-")
&& entry.file_name().to_string_lossy().ends_with("-ownership.manifest")
})
.flat_map(|entry| {
fs::read_to_string(entry.path())
.expect("read generation ownership manifest")
.lines()
.filter(|line| !line.is_empty())
.map(PathBuf::from)
.collect::<Vec<_>>()
})
.collect()
}
#[test]
fn failed_swift_post_build_preserves_owned_files_and_written_outputs() {
let fixture = tempfile::tempdir().expect("create fixture directory");
let root = fixture.path();
write_fixture(root);
let first_failure = run_generate(root, true);
assert!(
!first_failure.status.success(),
"required Swift post-build unexpectedly succeeded"
);
let first_stderr = String::from_utf8_lossy(&first_failure.stderr);
assert!(
first_stderr.contains("atomicity fixture cargo: Swift post-build exit 17")
&& first_stderr.contains("status 17"),
"unexpected first failure:\n{first_stderr}"
);
let generated = generated_marked_files(root);
assert!(
!generated.is_empty(),
"outputs written before the first required post-build failure must stay on disk; stderr:\n{first_stderr}"
);
assert!(
generated.iter().any(|path| path.starts_with(root.join("packages/go"))),
"Go output written before the later Swift post-build failure must stay on disk"
);
seed_owned_files(root);
let failed = run_generate(root, true);
assert!(
!failed.status.success(),
"required Swift cargo post-build unexpectedly succeeded"
);
let stderr = String::from_utf8_lossy(&failed.stderr);
assert!(
stderr.contains("atomicity fixture cargo: Swift post-build exit 17") && stderr.contains("status 17"),
"unexpected failure:\n{stderr}"
);
for relative in PROTECTED_PATHS {
assert!(
root.join(relative).is_file(),
"failed generation removed protected {relative}"
);
}
assert!(root.join("packages/swift/rust/src/obsolete.rs").is_file());
assert!(root.join("packages/go/obsolete.go").is_file());
assert!(root.join("packages/python/obsolete.py").is_file());
let first_success = run_generate(root, false);
assert!(
first_success.status.success(),
"generation after the post-build failure must recover: {}",
String::from_utf8_lossy(&first_success.stderr)
);
assert!(
String::from_utf8_lossy(&first_success.stderr).contains("atomicity fixture cargo: Swift post-build exit 0"),
"recovery must execute the successful fixture post-build"
);
assert!(
!generated_hashed_files(root).is_empty(),
"a successful generation must stamp its outputs once post-build and formatting have settled"
);
let owned_paths = generated_ownership_paths(root);
assert!(
!owned_paths.is_empty(),
"successful generation must record owned outputs"
);
assert!(
owned_paths.iter().all(|path| path.is_file()),
"the first successful generation must leave every owned output on disk"
);
let cached_success = run_generate(root, false);
assert!(
cached_success.status.success(),
"content-identical generation must succeed: {}",
String::from_utf8_lossy(&cached_success.stderr)
);
assert!(
owned_paths.iter().all(|path| path.is_file()),
"a cache-hit generation must not sweep valid outputs from its ownership manifest"
);
}