use std::{
path::{Path, PathBuf},
process::Command,
};
#[test]
fn showcase_example_matches_golden() {
check_example("showcase");
}
#[test]
fn invalid_example_matches_golden() {
check_example("invalid");
}
#[test]
fn the_invalid_example_demonstrates_every_check() {
let output = run_example("invalid");
let missing: Vec<&str> = kotlin_codegen::Check::ALL
.iter()
.map(|c| c.name())
.filter(|name| !output.contains(&format!("[{name}]")))
.collect();
assert!(
missing.is_empty(),
"examples/invalid.rs demonstrates no case for: {missing:?}\n\
Add one so the example stays a complete catalogue of the checks."
);
}
fn check_example(name: &str) {
let actual = run_example(name);
let golden = golden_dir().join(format!("{name}.txt"));
if std::env::var_os("UPDATE_GOLDEN").is_some() {
std::fs::create_dir_all(golden.parent().expect("golden dir has a parent"))
.expect("create golden dir");
std::fs::write(&golden, &actual).expect("write golden");
return;
}
let expected = std::fs::read_to_string(&golden).unwrap_or_else(|e| {
panic!(
"cannot read {}: {e}\nRun `UPDATE_GOLDEN=1 cargo test --test examples` to create it.",
golden.display()
)
});
if actual != expected {
panic!(
"`cargo run --example {name}` no longer matches {}\n\n{}\n\n\
If the change is intended, run `UPDATE_GOLDEN=1 cargo test --test examples` \
and review the diff.",
golden.display(),
first_difference(&expected, &actual),
);
}
}
fn golden_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("golden")
}
fn run_example(name: &str) -> String {
let cargo = option_env!("CARGO").unwrap_or("cargo");
let mut cmd = Command::new(cargo);
cmd.current_dir(env!("CARGO_MANIFEST_DIR"))
.args(["run", "--quiet", "--example", name]);
if !cfg!(debug_assertions) {
cmd.arg("--release");
}
let output = cmd
.output()
.unwrap_or_else(|e| panic!("cannot run `{cargo} run --example {name}`: {e}"));
assert!(
output.status.success(),
"example `{name}` exited with {}:\n{}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
String::from_utf8(output.stdout).expect("example output is UTF-8")
}
fn first_difference(expected: &str, actual: &str) -> String {
let exp: Vec<&str> = expected.lines().collect();
let act: Vec<&str> = actual.lines().collect();
for (i, (e, a)) in exp.iter().zip(act.iter()).enumerate() {
if e != a {
return format!(
"first difference at line {}:\n expected: {e}\n actual: {a}",
i + 1
);
}
}
format!(
"identical for {} lines, then the length differs: expected {} lines, actual {}",
exp.len().min(act.len()),
exp.len(),
act.len()
)
}