use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn alef_binary() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_alef"))
}
const FIXTURE_SOURCE: &str = "pub fn greet(name: String) -> String {\n name\n}\n";
const FIXTURE_CARGO_TOML: &str = "[package]\nname = \"diff-orphan-fixture\"\nversion = \"0.1.0\"\nedition = \"2024\"\n";
const FIXTURE_ALEF_TOML: &str = r#"
[workspace]
languages = ["python"]
[[crates]]
name = "diff-orphan-fixture"
sources = ["src/lib.rs"]
version_from = "Cargo.toml"
[crates.python]
module_name = "diff_orphan_fixture"
[crates.python.stubs]
output = "packages/python/diff_orphan_fixture"
"#;
fn write_fixture(root: &Path) {
fs::create_dir_all(root.join("src")).expect("create fixture src directory");
fs::write(root.join("src/lib.rs"), FIXTURE_SOURCE).expect("write fixture source");
fs::write(root.join("Cargo.toml"), FIXTURE_CARGO_TOML).expect("write fixture Cargo.toml");
fs::write(root.join("alef.toml"), FIXTURE_ALEF_TOML).expect("write fixture alef.toml");
}
fn run_alef(root: &Path, args: &[&str]) -> Output {
Command::new(alef_binary())
.current_dir(root)
.args(args)
.output()
.unwrap_or_else(|error| panic!("run alef {args:?}: {error}"))
}
#[test]
fn diff_reports_an_orphaned_generated_file() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path().canonicalize().unwrap_or_else(|_| dir.path().to_path_buf());
write_fixture(&root);
let generated = run_alef(&root, &["all", "--skip-frb"]);
assert!(
generated.status.success(),
"alef all must succeed against the fixture: {}",
String::from_utf8_lossy(&generated.stderr)
);
let baseline_diff = run_alef(&root, &["diff"]);
assert!(
baseline_diff.status.success(),
"alef diff must succeed (exit 0) without --exit-code even when scaffold entries are \
reported: {}",
String::from_utf8_lossy(&baseline_diff.stderr)
);
let baseline_stdout = String::from_utf8_lossy(&baseline_diff.stdout);
assert!(
!baseline_stdout.contains("legacy_visitor.py"),
"sanity: the orphan path must not appear before it is planted, got:\n{baseline_stdout}"
);
assert!(
!baseline_stdout.contains("would be removed"),
"sanity: no orphan section should be present before any orphan is planted, got:\n{baseline_stdout}"
);
let current = root.join("packages/python/diff_orphan_fixture/api.py");
let stale = root.join("packages/python/diff_orphan_fixture/legacy_visitor.py");
assert!(
current.is_file(),
"sanity: fixture must have produced api.py for the orphan copy source"
);
fs::copy(¤t, &stale).expect("plant a stale alef-marked file");
let diff_with_orphan = run_alef(&root, &["diff"]);
assert!(
diff_with_orphan.status.success(),
"alef diff without --exit-code must still exit 0 even when it finds an orphan: {}",
String::from_utf8_lossy(&diff_with_orphan.stderr)
);
let stdout = String::from_utf8_lossy(&diff_with_orphan.stdout);
assert!(
stdout.contains("legacy_visitor.py"),
"alef diff must name the orphaned generated file it found, got:\n{stdout}"
);
assert!(
stdout.contains("would be removed"),
"alef diff must report the orphan under its own section, distinct from \"Files that \
would change\", got:\n{stdout}"
);
let diff_exit_code = run_alef(&root, &["diff", "--exit-code"]);
assert_eq!(
diff_exit_code.status.code(),
Some(1),
"alef diff --exit-code must fail when an orphan is present: stdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&diff_exit_code.stdout),
String::from_utf8_lossy(&diff_exit_code.stderr)
);
let exit_code_stdout = String::from_utf8_lossy(&diff_exit_code.stdout);
assert!(
exit_code_stdout.contains("legacy_visitor.py"),
"the --exit-code path must print the same orphan finding before exiting nonzero, got:\n{exit_code_stdout}"
);
fs::remove_file(&stale).expect("remove the planted orphan");
let diff_after_cleanup = run_alef(&root, &["diff"]);
assert!(
diff_after_cleanup.status.success(),
"alef diff must succeed once the orphan is removed: {}",
String::from_utf8_lossy(&diff_after_cleanup.stderr)
);
let cleanup_stdout = String::from_utf8_lossy(&diff_after_cleanup.stdout);
assert!(
!cleanup_stdout.contains("legacy_visitor.py"),
"alef diff must stop reporting the orphan once it is removed from disk, got:\n{cleanup_stdout}"
);
assert!(
!cleanup_stdout.contains("would be removed"),
"alef diff must drop the orphan section entirely once no orphan remains, got:\n{cleanup_stdout}"
);
}