diff --git a/crates/engine/tests/command_sweep.rs b/crates/engine/tests/command_sweep.rs index 4bfd1152d..fcf1138b9 100644 --- a/crates/engine/tests/command_sweep.rs +++ b/crates/engine/tests/command_sweep.rs @@ -17,10 +17,19 @@ fn safe_path() -> String { vectorcraft_testkit::temp_dir("sweep").join("out.bin").to_string_lossy().to_string() } +/// `r`, after failing the case if the command panicked. The engine's guard reports a panic as +/// `EngineError::Internal`. +fn no_panic(r: Result) -> Result { + if let Err(vectorcraft_engine::EngineError::Internal { cmd, msg }) = &r { + panic!("{cmd} panicked: {msg}"); + } + r +} + /// Run one call; returns a failure description or None. fn probe(fx: Fixture, id: &str, params: &Value) -> Option { let mut s = fx.session(); - let r = catch_quiet(|| s.execute(id, params)); + let r = catch_quiet(|| no_panic(s.execute(id, params))); match r { Err(msg) => Some(format!("PANIC {id} {params} [{fx:?}]: {msg}")), Ok(_) => { @@ -68,7 +77,7 @@ fn every_command_without_a_document() { let mut failures = vec![]; for c in command_specs() { let mut s = Session::new(); - match catch_quiet(|| s.execute(c.id, &json!({}))) { + match catch_quiet(|| no_panic(s.execute(c.id, &json!({})))) { Err(p) => failures.push(format!("PANIC {} with no document: {p}", c.id)), // Anything that needs a document must say so via enablement, not fail later. Ok(Err(vectorcraft_engine::EngineError::NoDocument)) => failures.push(format!("{}: enabled without a document but needs one", c.id)), @@ -106,7 +115,7 @@ fn bug_huge_transform_makes_document_unloadable() { ("object.distributeSpacing", json!({"spacing": 1e308})), ] { let mut s = Fixture::Multi.session(); - if s.execute(id, &p).is_ok() { + if no_panic(s.execute(id, &p)).is_ok() { vectorcraft_testkit::invariants::check_native_roundtrip(&s.doc().unwrap().doc).unwrap_or_else(|e| panic!("{id} {p}: {e}")); } } @@ -117,7 +126,7 @@ fn bug_artboard_huge_props_break_svg() { for key in ["x", "y", "width", "height"] { let mut s = Fixture::Single.session(); let p = json!({"index": 0, key: 1e308}); - if s.execute("artboard.setProps", &p).is_ok() { + if no_panic(s.execute("artboard.setProps", &p)).is_ok() { vectorcraft_testkit::invariants::check_svg_roundtrip(&s.doc().unwrap().doc).unwrap_or_else(|e| panic!("{p}: {}", &e[..e.len().min(200)])); } } @@ -412,14 +421,14 @@ fn area_options_fit_junk() { for (size, (w, h)) in [(12.0, (120.0, 40.0)), (0.1, (1.0, 1.0)), (1296.0, (100_000.0, 1.0))] { for p in &cases { let mut s = Fixture::Multi.session(); - let made = s.execute("text.create", &json!({"x": 10, "y": 10, "size": size, "text": story, "area": {"width": w, "height": h}})); + let made = no_panic(s.execute("text.create", &json!({"x": 10, "y": 10, "size": size, "text": story, "area": {"width": w, "height": h}}))); let Ok(made) = made else { continue }; let id = made["id"].as_u64().unwrap_or(0); - let r = catch_quiet(|| s.execute("text.areaOptions", p)); + let r = catch_quiet(|| no_panic(s.execute("text.areaOptions", p))); match r { Err(m) => failures.push(format!("PANIC text.areaOptions {p} [{size} pt, {w}×{h}]: {m}")), Ok(_) => { - let typed = catch_quiet(|| s.execute("text.editRange", &json!({"id": id, "start": 0, "insert": "More words. "}))); + let typed = catch_quiet(|| no_panic(s.execute("text.editRange", &json!({"id": id, "start": 0, "insert": "More words. "})))); if typed.is_err() { failures.push(format!("PANIC typing after text.areaOptions {p}")); } @@ -447,11 +456,13 @@ fn polygon_properties_junk() { for scale in [None, Some((300.0, 20.0)), Some((1e-300, 100.0))] { for p in &cases { let mut s = Fixture::Multi.session(); - let Ok(_) = s.execute("shape.polygon", &json!({"cx": 100, "cy": 100, "radius": 40, "sides": 7, "rotation": 10})) else { continue }; + let Ok(_) = no_panic(s.execute("shape.polygon", &json!({"cx": 100, "cy": 100, "radius": 40, "sides": 7, "rotation": 10}))) else { + continue; + }; if let Some((sx, sy)) = scale { - let _ = s.execute("object.scale", &json!({"sx": sx, "sy": sy})); + let _ = no_panic(s.execute("object.scale", &json!({"sx": sx, "sy": sy}))); } - match catch_quiet(|| s.execute("object.setLiveShape", p)) { + match catch_quiet(|| no_panic(s.execute("object.setLiveShape", p))) { Err(m) => failures.push(format!("PANIC object.setLiveShape {p} [{scale:?}]: {m}")), Ok(_) => { if let Err(e) = catch_quiet(|| check_all(&mut s)).unwrap_or_else(|m| Err(format!("panic in checks: {m}"))) { @@ -504,7 +515,7 @@ fn every_command_undoes_exactly() { let depth = s.doc().unwrap().history.undo.len(); let Ok(Ok(_)) = catch_quiet(|| s.execute(c.id, &json!({}))) else { continue }; if s.doc().unwrap().history.undo.len() > depth { - let _ = s.execute("edit.undo", &json!({})); + let _ = no_panic(s.execute("edit.undo", &json!({}))); let after = doc_json(&s.doc().unwrap().doc); if after != before { failures.push(format!("{}: {}", c.id, vectorcraft_testkit::invariants::first_diff(&before, &after, "$"))); @@ -543,7 +554,7 @@ fn recovery_commands_with_a_damaged_store_and_junk_params() { for p in std::iter::once(json!({})).chain(junk_params(spec.params, &path)) { let mut s = fx.session(); s.recovery.set_store(store.clone()); - let r = catch_quiet(|| s.execute(id, &p)); + let r = catch_quiet(|| no_panic(s.execute(id, &p))); assert!(r.is_ok(), "PANIC {id} {p} [{fx:?}]"); check_session(&s).unwrap_or_else(|e| panic!("{id} {p} [{fx:?}]: {e}")); } @@ -576,9 +587,9 @@ fn envelope_commands_with_junk_params_on_envelopes() { continue; } if editing { - let _ = s.execute("object.envelope.editContents", &json!({"editing": true})); + let _ = no_panic(s.execute("object.envelope.editContents", &json!({"editing": true}))); } - let r = catch_quiet(|| s.execute(id, &p)); + let r = catch_quiet(|| no_panic(s.execute(id, &p))); if r.is_err() { failures.push(format!("PANIC {id} {p} [{make}, editing {editing}]")); } else if s.in_interaction() { @@ -646,7 +657,7 @@ fn text_commands_with_junk_params_on_inline_graphics() { let mut failures = vec![]; let mut run = |id: &str, p: &Value| { let (mut s, _) = setup(); - match catch_quiet(|| s.execute(id, p)) { + match catch_quiet(|| no_panic(s.execute(id, p))) { Err(msg) => failures.push(format!("PANIC {id} {p}: {msg}")), Ok(r) => { if s.in_interaction() { diff --git a/crates/engine/tests/import_fuzz.rs b/crates/engine/tests/import_fuzz.rs index ba7f6fe15..7bad66218 100644 --- a/crates/engine/tests/import_fuzz.rs +++ b/crates/engine/tests/import_fuzz.rs @@ -260,8 +260,8 @@ proptest! { let mut s = vectorcraft_engine::Session::new(); s.execute("file.new", &json!({"width":100,"height":100})).unwrap(); let p = json!({"name":"renamed.png", "dataBase64":vectorcraft_format::base64_encode(&bytes)}); - for cmd in ["file.place", "file.place.info"] { assert!(s.execute(cmd, &p).is_err()); } - assert!(s.execute("file.place.queue", &json!({"files":[p]})).is_err()); + for cmd in ["file.place", "file.place.info"] { assert!(no_panic(s.execute(cmd, &p)).is_err()); } + assert!(no_panic(s.execute("file.place.queue", &json!({"files":[p]}))).is_err()); }); prop_assert!(r.is_ok(), "Affinity placement panicked: {:?}", r.err()); } @@ -669,8 +669,8 @@ proptest! { let r = catch_quiet(|| { let mut s = vectorcraft_engine::Session::new(); let p = json!({"name": "x.pdf", "dataBase64": b64, "pages": pages, "cropTo": crop, "password": password, "thumbnail": 1, "thumbnailSize": 64}); - let _ = s.execute("document.pdfInfo", &p); - if s.execute("document.open", &p).is_ok() { + let _ = no_panic(s.execute("document.pdfInfo", &p)); + if no_panic(s.execute("document.open", &p)).is_ok() { let d = (*s.doc().unwrap().doc).clone(); survive("pdf options", || Some(d)).unwrap(); } @@ -1037,8 +1037,8 @@ proptest! { let mut s = vectorcraft_engine::Session::new(); s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); let p = json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(&bytes), "thumbnail": 8}); - let _ = s.execute("file.place.info", &p); - let _ = s.execute("file.place", &p); + let _ = no_panic(s.execute("file.place.info", &p)); + let _ = no_panic(s.execute("file.place", &p)); }); prop_assert!(r.is_ok(), "{name}: panicked: {:?}", r.err()); } @@ -1133,10 +1133,10 @@ fn paste_from_elsewhere(cmd: &str, p: Value) -> Result<(), TestCaseError> { let r = catch_quiet(|| { let mut s = vectorcraft_engine::Session::new(); s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); - if s.execute(cmd, &p).is_ok() { + if no_panic(s.execute(cmd, &p)).is_ok() { // Paste may refuse what it was given, e.g. a damaged PDF whose art spans more than the // canvas ("result would exceed the canvas"): an error is a fine outcome, a panic isn't. - let _ = s.execute("edit.paste", &json!({"center": [50, 50]})); + let _ = no_panic(s.execute("edit.paste", &json!({"center": [50, 50]}))); } }); prop_assert!(r.is_ok(), "{cmd}: panicked: {:?}", r.err()); @@ -1294,7 +1294,7 @@ proptest! { survive("hostile PostScript", || vectorcraft_engine::cmd::fileio::load("x.eps", ps.as_bytes()).ok().map(|l| l.doc))?; let r = catch_quiet(|| { let mut s = rich_session(); - let _ = s.execute("file.place", &json!({"name": "x.eps", "dataBase64": vectorcraft_format::base64_encode(ps.as_bytes())})); + let _ = no_panic(s.execute("file.place", &json!({"name": "x.eps", "dataBase64": vectorcraft_format::base64_encode(ps.as_bytes())}))); }); r.map_err(|msg| TestCaseError::fail(format!("placing hostile PostScript panicked: {msg}")))?; } @@ -1390,8 +1390,8 @@ proptest! { let r = catch_quiet(|| { let mut s = vectorcraft_engine::Session::new(); let p = json!({"name": "x.pdf", "dataBase64": b64, "password": password}); - let _ = s.execute("document.pdfInfo", &p); - if s.execute("document.open", &p).is_ok() { + let _ = no_panic(s.execute("document.pdfInfo", &p)); + if no_panic(s.execute("document.open", &p)).is_ok() { let d = (*s.doc().unwrap().doc).clone(); survive("encrypted pdf", || Some(d)).unwrap(); } @@ -1460,11 +1460,11 @@ fn survive_dxf(what: &str, bytes: &[u8], fit: bool) -> Result<(), TestCaseError> let _ = vectorcraft_cad::is_dxf(bytes); survive(what, || { let mut s = vectorcraft_engine::Session::new(); - s.execute("file.new", &json!({"width": 300, "height": 200})).ok()?; + no_panic(s.execute("file.new", &json!({"width": 300, "height": 200}))).ok()?; let p = json!({"name": "x.dxf", "dataBase64": vectorcraft_format::base64_encode(bytes), "dxf": {"fit": fit}, "thumbnail": 8}); - let _ = s.execute("file.place.info", &p); - let _ = s.execute("file.place", &p); - let _ = s.execute("document.dxfInfo", &p); + let _ = no_panic(s.execute("file.place.info", &p)); + let _ = no_panic(s.execute("file.place", &p)); + let _ = no_panic(s.execute("document.dxfInfo", &p)); Some((*s.doc().ok()?.doc).clone()) }) } @@ -1569,7 +1569,7 @@ fn survive_metafile(what: &str, name: &str, bytes: &[u8]) -> Result<(), TestCase let r = catch_quiet(|| { let mut s = vectorcraft_engine::Session::new(); s.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); - let _ = s.execute("file.place", &json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)})); + let _ = no_panic(s.execute("file.place", &json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)}))); }); prop_assert!(r.is_ok(), "{what}: placing panicked: {:?}", r.err()); Ok(())