use std::path::{Path, PathBuf};
use std::process::Command;
const TARGET: &str = "x86_64-unknown-linux-gnu";
const STD: &str = "gnu23";
const STD_DIRECTIVE: &str = "// std: ";
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.ancestors()
.nth(2)
.expect("the crate is two levels under the repository root")
.to_path_buf()
}
fn golden_dir() -> PathBuf {
repo_root().join("tests").join("golden")
}
fn cases() -> Vec<String> {
let dir = golden_dir();
let mut names: Vec<String> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("{}: {e}", dir.display()))
.map(|entry| entry.expect("a readable directory entry").path())
.filter(|path| path.extension().is_some_and(|ext| ext == "c"))
.map(|path| path.file_name().expect("a file has a name").to_string_lossy().into_owned())
.collect();
names.sort();
assert!(!names.is_empty(), "{}: no cases", dir.display());
names
}
fn emit(case: &str, kind: &str) -> Result<String, String> {
let out = Command::new(env!("CARGO_BIN_EXE_rucc"))
.current_dir(repo_root())
.args([
format!("--target={TARGET}"),
format!("-std={}", dialect(case)),
format!("--emit={kind}"),
])
.arg(format!("tests/golden/{case}"))
.args(["-o", "-"])
.output()
.expect("the compiler is built before its own tests run");
let said = String::from_utf8_lossy(&out.stderr).into_owned();
if !out.status.success() {
return Err(said);
}
assert!(
said.is_empty(),
"{case} compiled but said something, which a golden case must not:\n{said}"
);
Ok(String::from_utf8(out.stdout).expect("what the compiler writes is text"))
}
fn dialect(case: &str) -> String {
let text = std::fs::read_to_string(golden_dir().join(case)).unwrap_or_default();
for line in text.lines() {
if let Some(named) = line.strip_prefix(STD_DIRECTIVE) {
return named.trim().to_owned();
}
}
STD.to_owned()
}
fn blessed(case: &str, kind: &str) -> Option<String> {
std::fs::read_to_string(golden_dir().join(case).with_extension(kind)).ok()
}
#[test]
fn every_case_produces_the_typed_tree_that_was_blessed() {
let mut stale = Vec::new();
for case in cases() {
let expected = blessed(&case, "tast")
.unwrap_or_else(|| panic!("{case}: no typed tree beside it; run `cargo xtask bless`"));
let actual =
emit(&case, "tast").unwrap_or_else(|said| panic!("{case} did not compile:\n{said}"));
if actual != expected {
stale.push(format!("{case}:\n--- blessed\n{expected}--- produced\n{actual}"));
}
}
report(&stale);
}
#[test]
fn every_case_the_walk_can_lower_produces_the_ir_that_was_blessed() {
let mut stale = Vec::new();
for case in cases() {
match (emit(&case, "ir"), blessed(&case, "ir")) {
(Ok(actual), Some(expected)) if actual == expected => {}
(Ok(actual), Some(expected)) => {
stale.push(format!("{case}:\n--- blessed\n{expected}--- produced\n{actual}"));
}
(Ok(_), None) => stale.push(format!("{case}: lowers now and has no `.ir` beside it")),
(Err(said), Some(_)) => {
stale.push(format!("{case}: has a blessed `.ir` and no longer lowers:\n{said}"));
}
(Err(said), None) => assert!(
said.contains("[E0519]"),
"{case} has no `.ir` beside it because the walk refuses it, but what it said is \
not that something is unsupported:\n{said}"
),
}
}
report(&stale);
}
fn report(stale: &[String]) {
assert!(
stale.is_empty(),
"{} case(s) no longer produce what was blessed. Read the diff, and if every change is \
one you meant, run `cargo xtask bless`.\n\n{}",
stale.len(),
stale.join("\n")
);
}
#[test]
fn no_expectation_is_left_behind_by_a_case_that_was_removed() {
let dir = golden_dir();
for entry in std::fs::read_dir(&dir).expect("a readable directory") {
let path = entry.expect("a readable directory entry").path();
if path.extension().is_some_and(|ext| ext == "tast" || ext == "ir") {
assert!(
path.with_extension("c").exists(),
"{}: an expectation with no case to produce it",
path.display()
);
}
}
}