Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
100 lines
3.2 KiB
Rust
100 lines
3.2 KiB
Rust
//! Regression tests: parse errors must reach a captured-diagnostics sink.
|
|
//!
|
|
//! Empirically (host_launcher, 2026-08-14): the parser RECOVERS from errors
|
|
//! like a dangling `else` in expression position — it logs, sets `had_error`,
|
|
//! and still produces a runnable module. Nothing entered the trap queue, so a
|
|
//! validating host (`captured_errors` sink + `take_errors`) reported SUCCESS
|
|
//! for scripts that failed to parse, and broken mini-apps sailed through
|
|
//! validation with their errors only in the log.
|
|
|
|
use makepad_script::*;
|
|
|
|
fn test_vm() -> ScriptVm<'static> {
|
|
let host = Box::leak(Box::new(ScriptVmHost::new(0i32, ())));
|
|
ScriptVm {
|
|
host,
|
|
bx: Box::new(ScriptVmBase::new()),
|
|
}
|
|
}
|
|
|
|
fn eval_captured(vm: &mut ScriptVm, name: &str, code: &str) -> Vec<String> {
|
|
vm.bx.captured_errors = Some(Vec::new());
|
|
vm.with_instruction_limit(500_000, |vm| {
|
|
vm.eval(ScriptMod {
|
|
cargo_manifest_path: String::new(),
|
|
module_path: String::new(),
|
|
file: format!("parse_error_capture_{name}"),
|
|
line: 0,
|
|
column: 0,
|
|
code: code.to_string(),
|
|
values: vec![],
|
|
})
|
|
});
|
|
vm.take_errors()
|
|
}
|
|
|
|
/// The exact shape that sailed through host_launcher's validation while
|
|
/// failing to parse (isolation_probe's svc_result, 2026-08-14): a fn whose
|
|
/// FINAL statement is an if with call-statement branches and the `else` on
|
|
/// its own line. The parser reports "Unexpected else" and recovers; the sink
|
|
/// must see it.
|
|
const DANGLING_ELSE: &str = "\
|
|
fn svc_result(r){
|
|
let ok = r.ok
|
|
if ok {
|
|
ui.a.set_text(\"A\")
|
|
ui.b.set_text(\"A\")
|
|
}
|
|
else {
|
|
ui.a.set_text(\"B\")
|
|
ui.b.set_text(\"B\")
|
|
}
|
|
}
|
|
1 + 2";
|
|
|
|
#[test]
|
|
fn dangling_else_reaches_the_sink() {
|
|
let mut vm = test_vm();
|
|
let errors = eval_captured(&mut vm, "dangling_else", DANGLING_ELSE);
|
|
assert!(
|
|
errors.iter().any(|e| e.contains("Unexpected else")),
|
|
"parse error missing from captured sink: {errors:?}"
|
|
);
|
|
}
|
|
|
|
/// A clean script contributes nothing. (Ends on a parenthesized expression:
|
|
/// a final `let` and a final bare ident each trip unrelated quirks.)
|
|
#[test]
|
|
fn clean_parse_captures_nothing() {
|
|
let mut vm = test_vm();
|
|
let errors = eval_captured(&mut vm, "clean", "let x = 1 + 2\n(x + 1)");
|
|
assert!(errors.is_empty(), "unexpected errors: {errors:?}");
|
|
}
|
|
|
|
/// The append/streaming eval path surfaces parse errors the same way.
|
|
#[test]
|
|
fn streaming_eval_surfaces_parse_errors() {
|
|
let mut vm = test_vm();
|
|
vm.bx.captured_errors = Some(Vec::new());
|
|
let code = DANGLING_ELSE;
|
|
vm.with_instruction_limit(500_000, |vm| {
|
|
vm.eval_with_append_source(
|
|
ScriptMod {
|
|
cargo_manifest_path: String::new(),
|
|
module_path: "stream#1".to_string(),
|
|
file: "parse_error_capture_stream".to_string(),
|
|
line: 0,
|
|
column: 0,
|
|
code: String::new(),
|
|
values: vec![],
|
|
},
|
|
code,
|
|
NIL.into(),
|
|
)
|
|
});
|
|
let errors = vm.take_errors();
|
|
assert!(
|
|
errors.iter().any(|e| e.contains("Unexpected else")),
|
|
"streaming parse error missing from captured sink: {errors:?}"
|
|
);
|
|
}
|