use sui_bytecode::StringKeyedValue;
fn tree_walker_str(expr: &str) -> String {
let v = sui_eval::eval(expr)
.unwrap_or_else(|e| panic!("tree-walker failed for `{expr}`: {e}"));
let forced = sui_eval::eval::force_value(&v)
.unwrap_or_else(|e| panic!("tree-walker force failed for `{expr}`: {e}"));
forced
.to_str()
.unwrap_or_else(|e| panic!("tree-walker result for `{expr}` is not a string: {e}"))
}
fn vm_str(expr: &str) -> String {
let r = sui_bytecode::eval_full(expr)
.unwrap_or_else(|e| panic!("bytecode VM failed for `{expr}`: {e}"));
match r.to_string_keyed() {
StringKeyedValue::String(s) => s,
other => panic!("bytecode VM result for `{expr}` is not a string: {other:?}"),
}
}
struct Shape {
label: &'static str,
expr: &'static str,
}
const MUST_MATCH: &[Shape] = &[
Shape {
label: "single-output",
expr: r#"(derivation { name="s"; system="x86_64-linux"; builder="/b"; }).drvPath"#,
},
Shape {
label: "multi-output out/dev/lib",
expr: r#"(derivation { name="m"; system="x86_64-linux"; builder="/b"; outputs=["out" "dev" "lib"]; }).drvPath"#,
},
Shape {
label: "thunk-valued env vars",
expr: r#"(derivation { name="t"; system="x86_64-linux"; builder="/b"; foo=(1+2); bar=(if true then "y" else "n"); }).drvPath"#,
},
Shape {
label: "float env attr (%f)",
expr: r#"(derivation { name="f"; system="x86_64-linux"; builder="/b"; ratio=1.5; pi=3.14159; }).drvPath"#,
},
Shape {
label: "__ignoreNulls drops nulls",
expr: r#"(derivation { name="n"; system="x86_64-linux"; builder="/b"; __ignoreNulls=true; userHook=null; keep="k"; }).drvPath"#,
},
Shape {
label: "list-valued env var",
expr: r#"(derivation { name="l"; system="x86_64-linux"; builder="/b"; tags=["a" "b" "c"]; }).drvPath"#,
},
Shape {
label: "FOD flat",
expr: r#"(derivation { name="fod"; system="x86_64-linux"; builder="/b"; outputHash="0000000000000000000000000000000000000000000000000000"; outputHashAlgo="sha256"; outputHashMode="flat"; }).drvPath"#,
},
];
const KNOWN_VM_BLOCKED: &[Shape] = &[
Shape {
label: "FOD/env referencing another drv (inputDrv edge)",
expr: r#"(let dep = derivation { name="dep"; system="x86_64-linux"; builder="/b"; }; in (derivation { name="cons"; system="x86_64-linux"; builder="/b"; input="${dep}"; }).drvPath)"#,
},
Shape {
label: "__structuredAttrs = true",
expr: r#"(derivation { name="sa"; system="x86_64-linux"; builder="/b"; __structuredAttrs=true; buildInputs=["x"]; }).drvPath"#,
},
];
#[test]
fn vm_matches_treewalker_byte_for_byte() {
let mut failures: Vec<String> = Vec::new();
for shape in MUST_MATCH {
let tw = tree_walker_str(shape.expr);
let vm = vm_str(shape.expr);
if tw != vm {
failures.push(format!(
"{}:\n tree-walker => {tw}\n bytecode VM => {vm}",
shape.label
));
}
}
assert!(
failures.is_empty(),
"{} derivation shape(s) diverge between the VM and the tree-walker:\n - {}",
failures.len(),
failures.join("\n - ")
);
}
#[test]
fn vm_multi_output_has_three_outputs_and_matches() {
let all_len_expr = r#"builtins.length (derivation { name="m"; system="x86_64-linux"; builder="/b"; outputs=["out" "dev" "lib"]; }).all"#;
let vm_len = sui_bytecode::eval_full(all_len_expr).expect("VM eval");
match vm_len.to_string_keyed() {
StringKeyedValue::Int(n) => assert_eq!(
n, 3,
"VM multi-output `.all` length must be 3 (the outputs-list-item force fix); got {n}"
),
other => panic!("expected Int, got {other:?}"),
}
let drv_expr = r#"(derivation { name="m"; system="x86_64-linux"; builder="/b"; outputs=["out" "dev" "lib"]; }).drvPath"#;
assert_eq!(
vm_str(drv_expr),
tree_walker_str(drv_expr),
"VM multi-output drvPath must equal the tree-walker's"
);
}
#[test]
fn known_vm_blocked_shapes_still_diverge() {
for shape in KNOWN_VM_BLOCKED {
let tw = tree_walker_str(shape.expr);
let vm = vm_str(shape.expr);
assert_ne!(
tw, vm,
"`{}` now MATCHES between engines — VM string-context has advanced. \
Promote this row from KNOWN_VM_BLOCKED to MUST_MATCH.",
shape.label
);
}
}