use std::path::{Path, PathBuf};
use std::sync::Once;
use sui_bytecode::fallback::{self, Layer};
use sui_bytecode::render::render_vm;
use sui_bytecode::{EvalError, FileEvalError};
use sui_eval::Evaluator as _;
use sui_eval::render::render_tree;
const KNOWN_GAPS: &[(&str, &str)] = &[
("eval-okay-derivation-legacy", "WRONG-VALUE:drv-hash-differs-from-cppnix-exp"),
("eval-okay-eq-derivations", "WRONG-VALUE:derivation-equality-not-by-outPath"),
("eval-okay-equal-function-attrset-identical", "WRONG-VALUE:no-value-identity-optimization"),
("eval-okay-equal-function-list-identical", "WRONG-VALUE:no-value-identity-optimization"),
("eval-okay-equal-function-alias-nested", "WRONG-VALUE:no-value-identity-optimization"),
("eval-okay-toxml-functions", "WRONG-VALUE:bridge-crossing-maps-lambda-to-null"),
("eval-okay-tofile-refs", "WRONG-VALUE:vm-has-no-string-context"),
("eval-okay-tojson-floats", "UNDECLARED-ERROR:vm-tojson-attrset-needs-interner"),
("eval-okay-tryeval", "WRONG-VALUE:tryEval-misses-assert-and-returns-unwrapped-body"),
("eval-okay-attrnames", "with-scope-lost-across-import:tail"),
("eval-okay-attrs5", "with-scope-lost-across-import:head"),
("eval-okay-closure", "with-scope-lost-across-import:lessThan"),
("eval-okay-concatmap", "with-scope-lost-across-import:genList"),
("eval-okay-elem", "with-scope-lost-across-import:genList"),
("eval-okay-filter", "with-scope-lost-across-import:genList"),
("eval-okay-flatten", "with-scope-lost-across-import:isList"),
("eval-okay-foldlStrict", "with-scope-lost-across-import:genList"),
("eval-okay-groupBy", "with-scope-lost-across-import:genList"),
("eval-okay-list", "with-scope-lost-across-import:tail"),
("eval-okay-listtoattrs", "with-scope-lost-across-import:tail"),
("eval-okay-map", "with-scope-lost-across-import:tail"),
("eval-okay-partition", "with-scope-lost-across-import:genList"),
("eval-okay-zipAttrsWith", "with-scope-lost-across-import:genList"),
("eval-okay-baseNameOf", "err:assertion-failed"),
("eval-okay-callable-attrs", "err:not-a-function-set (no __functor dispatch)"),
("eval-okay-context", "err:abort-context-not-discarded (no string context)"),
("eval-okay-delayed-with", "err:derivation-attr-system-expected-string-got-thunk"),
("eval-okay-delayed-with-inherit", "err:undefined-variable-b (delayed `with` + inherit)"),
("eval-okay-foldlStrict-lazy-elements", "err:forces-an-element-nix-never-forces"),
("eval-okay-foldlStrict-lazy-initial-accumulator", "err:forces-an-accumulator-nix-never-forces"),
("eval-okay-functionargs", "err:assertion-failed"),
("eval-okay-getattrpos", "err:expected-set-got-null (unsafeGetAttrPos returns null)"),
("eval-okay-intersectAttrs", "err:throw-f (forces a value nix leaves lazy)"),
("eval-okay-json-roundtrip", "err:toJSON-attrset-conversion-requires-interner"),
("eval-okay-null-dynamic-attrs", "err:attrset-key-expected-string-got-null"),
("eval-okay-scope-4", "err:null-plus-string (scope resolution yields null)"),
("eval-okay-scope-6", "err:null-plus-string (scope resolution yields null)"),
("eval-okay-sort", "err:lessThan-expected-comparable-types"),
("eval-okay-substring-context", "err:getContext-expected-string (no string context)"),
];
fn lang_dir() -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.pop(); p.push("sui-eval/tests/fixtures/lang");
p
}
fn fixtures() -> Vec<PathBuf> {
let mut out = Vec::new();
let Ok(entries) = std::fs::read_dir(lang_dir()) else {
return out;
};
for e in entries.flatten() {
let p = e.path();
if p.extension().and_then(|s| s.to_str()) != Some("nix") {
continue;
}
if !p
.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.starts_with("eval-okay-"))
{
continue;
}
out.push(p);
}
out.sort();
out
}
fn stem(p: &Path) -> String {
p.file_stem().unwrap_or_default().to_string_lossy().to_string()
}
fn gap_reason(name: &str) -> Option<&'static str> {
KNOWN_GAPS.iter().find(|(n, _)| *n == name).map(|(_, r)| *r)
}
fn oracle(p: &Path) -> String {
std::fs::read_to_string(p.with_extension("exp"))
.map(|s| s.trim().to_string())
.unwrap_or_else(|e| format!("<no .exp: {e}>"))
}
fn arm_strict() {
static ARM: Once = Once::new();
ARM.call_once(|| {
unsafe { std::env::set_var("SUI_VM_STRICT", "1") };
});
}
fn tree_outcome(p: &Path) -> Result<String, String> {
sui_eval::builtins::clear_import_cache();
let v = sui_eval::TreeWalkEvaluator
.eval_file(p)
.map_err(|e| e.to_string())?;
render_tree(&v)
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Fail {
UnsupportedCompile,
StrictRefusal,
Runtime,
}
fn vm_outcome(p: &Path) -> Result<String, (Fail, String)> {
match sui_bytecode::eval_file(p) {
Ok(r) => render_vm(&r.value, &r.interner).map_err(|e| (Fail::Runtime, e)),
Err(e) => {
let class = match &e {
FileEvalError::Eval(EvalError::Compile(_)) => Fail::UnsupportedCompile,
FileEvalError::Eval(EvalError::Runtime(r)) => {
if r.to_string().contains("SUI_VM_STRICT:") {
Fail::StrictRefusal
} else {
Fail::Runtime
}
}
FileEvalError::Read { .. } => Fail::Runtime,
};
Err((class, e.to_string()))
}
}
}
struct Row {
name: String,
tree: Result<String, String>,
vm: Result<String, (Fail, String)>,
builtin_bridges: u64,
agree: bool,
}
struct Corpus {
rows: Vec<Row>,
strict: bool,
builtin: u64,
imported_file: u64,
whole_expression: u64,
}
fn corpus() -> &'static Corpus {
static CORPUS: std::sync::OnceLock<Corpus> = std::sync::OnceLock::new();
CORPUS.get_or_init(|| {
arm_strict();
on_worker(|| {
fallback::reset();
let rows = run_corpus();
Corpus {
rows,
strict: fallback::strict(),
builtin: fallback::count(Layer::Builtin),
imported_file: fallback::count(Layer::ImportedFile),
whole_expression: fallback::count(Layer::WholeExpression),
}
})
})
}
fn run_corpus() -> Vec<Row> {
let _bridges = sui_eval::install_vm_bridges();
let mut rows = Vec::new();
for path in fixtures() {
let name = stem(&path);
let tree = tree_outcome(&path);
let before = fallback::count(Layer::Builtin);
let vm = vm_outcome(&path);
let builtin_bridges = fallback::count(Layer::Builtin).saturating_sub(before);
let agree = match (&tree, &vm) {
(Ok(a), Ok(b)) => a == b,
(Err(_), Err(_)) => true,
_ => false,
};
rows.push(Row {
name,
tree,
vm,
builtin_bridges,
agree,
});
}
rows
}
fn on_worker<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> T {
std::thread::Builder::new()
.stack_size(256 * 1024 * 1024)
.spawn(f)
.expect("spawn corpus worker")
.join()
.expect("corpus worker panicked")
}
#[test]
fn vm_corpus_matches_the_tree_walker() {
let c = corpus();
let rows = &c.rows;
assert!(
c.strict,
"SUI_VM_STRICT did not arm — every fallback below would be silent and \
this run would report the tree-walker's coverage as the VM's. Refusing \
to report a number that cannot be trusted."
);
let fixtures = fixtures();
assert!(
fixtures.len() > 100,
"found only {} lang fixtures — discovery is broken",
fixtures.len()
);
assert_eq!(
rows.len(),
fixtures.len(),
"scanned {} fixtures but produced {} rows",
fixtures.len(),
rows.len()
);
let mut matched_value = 0usize;
let mut matched_both_error = 0usize;
let mut bridged_agreements = 0usize;
let mut gaps = 0usize;
let mut failures: Vec<String> = Vec::new();
let mut declared_compile = 0usize;
let mut declared_strict = 0usize;
let mut undeclared_error = 0usize;
let mut wrong_value = 0usize;
let mut vm_only_success = 0usize;
for r in rows {
if r.agree {
if r.tree.is_ok() {
matched_value += 1;
if r.builtin_bridges > 0 {
bridged_agreements += 1;
}
} else {
matched_both_error += 1;
}
} else {
match (&r.tree, &r.vm) {
(Ok(_), Err((Fail::UnsupportedCompile, _))) => declared_compile += 1,
(Ok(_), Err((Fail::StrictRefusal, _))) => declared_strict += 1,
(Ok(_), Err((Fail::Runtime, _))) => undeclared_error += 1,
(Ok(_), Ok(_)) => wrong_value += 1,
(Err(_), Ok(_)) => vm_only_success += 1,
(Err(_), Err(_)) => unreachable!("both-error is agreement"),
}
}
match (gap_reason(&r.name), r.agree) {
(Some(_), false) => gaps += 1,
(Some(reason), true) => failures.push(format!(
" {}: listed in KNOWN_GAPS ({reason}) but now AGREES — remove \
the entry. An allowlist nobody prunes becomes the coverage number.",
r.name
)),
(None, true) => {}
(None, false) => {
let class = match (&r.tree, &r.vm) {
(Ok(_), Err((Fail::UnsupportedCompile, _))) => "DECLARED/compile",
(Ok(_), Err((Fail::StrictRefusal, _))) => "DECLARED/strict-refusal",
(Ok(_), Err((Fail::Runtime, _))) => "UNDECLARED-ERROR",
(Ok(_), Ok(_)) => "WRONG-VALUE",
_ => "VM-ONLY-SUCCESS",
};
let vm_text = match &r.vm {
Ok(v) => v.clone(),
Err((_, m)) => format!("ERR {m}"),
};
let tree_text = r
.tree
.as_ref()
.map_or_else(|e| format!("ERR {e}"), Clone::clone);
let mut entry = format!(
" [{class}] {}:\n walker: {tree_text}\n vm: {vm_text}",
r.name
);
if matches!((&r.tree, &r.vm), (Ok(_), Ok(_))) {
entry.push_str(&format!("\n .exp: {}", oracle(&lang_dir().join(format!("{}.nix", r.name)))));
}
failures.push(entry);
}
}
}
eprintln!(
"\nVM vs tree-walker on the lang corpus (SUI_VM_STRICT=1): \
{}/{} agree ({matched_value} value, {matched_both_error} both-error), {gaps} known gaps\n\
\x20 of the {matched_value} value-agreements, {bridged_agreements} used >=1 tree-walker \
builtin bridge (architectural, not failure — but not 'the VM computed this' either)\n\
\x20 divergence classes: DECLARED/compile={declared_compile} \
DECLARED/strict-refusal={declared_strict} UNDECLARED-ERROR={undeclared_error} \
WRONG-VALUE={wrong_value} VM-ONLY-SUCCESS={vm_only_success}\n\
\x20 fallback counters for THIS run: builtin={} imported-file={} \
whole-expression={}\n",
matched_value + matched_both_error,
rows.len(),
c.builtin,
c.imported_file,
c.whole_expression,
);
assert_eq!(
(c.imported_file, c.whole_expression),
(0, 0),
"the VM delegated a whole file or the whole expression to the \
tree-walker; under strict that should have errored, so either the \
latch is not doing its job or these counts are stale"
);
assert!(
matched_value >= 30,
"only {matched_value} of {} rows agreed on a VALUE; the rest agreed by \
both failing. Two engines erroring in unison is not parity evidence.",
rows.len()
);
assert!(
failures.is_empty(),
"\n{} of {} lang fixtures diverge between the bytecode VM and the tree-walker:\n{}",
failures.len(),
rows.len(),
failures.join("\n")
);
}
#[test]
fn no_agreement_rests_on_a_placeholder() {
const PLACEHOLDERS: &[&str] = &["<<lambda>>", "<<builtin ", "<...>"];
let c = corpus();
assert!(c.strict, "SUI_VM_STRICT did not arm");
let agreeing: Vec<&Row> = c.rows.iter().filter(|r| r.agree && r.tree.is_ok()).collect();
assert!(
agreeing.len() >= 30,
"only {} value-agreements to check — this guard is vacuous below that \
and must not report clean",
agreeing.len()
);
let mut tainted: Vec<String> = Vec::new();
for r in agreeing {
for (side, rendered) in [
("walker", r.tree.as_deref().ok()),
("vm", r.vm.as_ref().map(String::as_str).ok()),
] {
let Some(rendered) = rendered else { continue };
for ph in PLACEHOLDERS {
if rendered.contains(ph) {
tainted.push(format!(
" {} [{side}]: agreement contains {ph} — {rendered}",
r.name
));
break;
}
}
}
}
assert!(
tainted.is_empty(),
"{} agreeing fixtures agree only on a placeholder; two placeholders \
compare EQUAL, so these are not evidence either engine produced a \
value:\n{}",
tainted.len(),
tainted.join("\n")
);
}
#[test]
fn every_known_gap_names_a_real_fixture() {
let present: Vec<String> = fixtures().iter().map(|p| stem(p)).collect();
assert!(
present.len() > 100,
"discovery found {} fixtures — this guard cannot certify an allowlist \
against a corpus it failed to read",
present.len()
);
let phantom: Vec<&str> = KNOWN_GAPS
.iter()
.map(|(n, _)| *n)
.filter(|n| !present.iter().any(|p| p == n))
.collect();
assert!(
phantom.is_empty(),
"these KNOWN_GAPS entries name no active fixture: {phantom:?}. Either \
the name is a typo, or the fixture moved — in which case delete the \
entry, because it is suppressing nothing."
);
}
#[test]
fn the_known_gap_list_is_pinned() {
assert_eq!(
KNOWN_GAPS.len(),
39,
"KNOWN_GAPS changed size. It may SHRINK freely — delete the entry and \
update this number. Growing it means the VM regressed against the \
corpus, or a newly-vendored fixture was allowlisted instead of fixed; \
either way say which in the commit."
);
}
#[test]
fn the_render_depth_cap_is_shared() {
assert_eq!(
sui_bytecode::render::MAX_RENDER_DEPTH,
sui_eval::render::MAX_RENDER_DEPTH
);
assert_eq!(
sui_bytecode::render::DEEP_SENTINEL,
sui_eval::render::DEEP_SENTINEL
);
}
#[test]
fn escape_str_is_byte_identical_across_the_engines() {
let cases = [
"",
"plain",
"with \"quotes\"",
"back\\slash",
"new\nline",
"tab\there",
"carriage\rreturn",
"dollar${interp}",
"mixed \"a\\b\nc\"",
"unicode: é 日本語 🙂",
"\u{1}\u{7f}",
];
for c in cases {
assert_eq!(
sui_bytecode::render::escape_str(c),
sui_eval::render::escape_str(c),
"escape_str diverges on {c:?} — a rendered-string differential \
would report this as an EVALUATOR divergence"
);
}
}