use std::path::PathBuf;
fn corpus() -> Vec<(String, String)> {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("spec");
let mut out = Vec::new();
for e in std::fs::read_dir(&root)
.unwrap_or_else(|err| panic!("spec dir {} unreadable: {err}", root.display()))
.filter_map(Result::ok)
{
let p = e.path();
if p.extension().and_then(|s| s.to_str()) != Some("b") {
continue;
}
let name = p
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("?")
.to_string();
if let Ok(src) = std::fs::read_to_string(&p) {
out.push((name, src));
}
}
out.sort();
assert!(
!out.is_empty(),
"spec corpus EMPTY — suite would pass vacuously"
);
out
}
#[test]
fn hostile_source_returns_a_run_error_rather_than_aborting() {
let deep_parens = "(".repeat(2_000);
let deep_blocks = "def a\n".repeat(2_000);
for (label, src) in [
("empty", ""),
("nul", "\0"),
("lone-open", "("),
("lone-close", ")"),
("unterminated-string", "\"abc"),
("dangling-def", "def"),
("def-without-end", "def foo\n 1"),
("lone-end", "end"),
("trailing-operator", "1 +"),
("bare-comment", "#"),
("crlf", "def a\r\n1\r\nend\r\n"),
("emoji", "🔥"),
("undefined-name", "no_such_function_anywhere"),
("arity-nonsense", "1(2)(3)(4)"),
("deep-parens", deep_parens.as_str()),
("deep-blocks", deep_blocks.as_str()),
] {
let r = std::panic::catch_unwind(|| {
let _ = blue_lang_runtime::pipeline::run(src);
});
assert!(
r.is_ok(),
"PANIC/ABORT running hostile input `{label}` — the evaluator must \
return RunError. shikumi, the LSP and the CLI all reach this."
);
}
}
#[test]
fn truncated_programs_do_not_abort_the_evaluator() {
let mut checked = 0usize;
for (name, src) in corpus() {
for end in (0..=src.len()).step_by(7) {
let Some(slice) = src.get(..end) else {
continue;
};
let r = std::panic::catch_unwind(|| {
let _ = blue_lang_runtime::pipeline::run(slice);
});
assert!(
r.is_ok(),
"PANIC/ABORT evaluating a {end}-byte prefix of {name}"
);
checked += 1;
}
}
assert!(
checked > 100,
"only {checked} truncations evaluated — corpus shrank or the step is \
too coarse, and either way this gate is weaker than it reads"
);
}
#[test]
fn running_the_same_source_twice_agrees() {
for (name, src) in corpus() {
let a = blue_lang_runtime::pipeline::run(&src).is_ok();
let b = blue_lang_runtime::pipeline::run(&src).is_ok();
assert_eq!(a, b, "{name}: run() is not deterministic across two calls");
}
}