mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Tests: the command sweeps and import fuzz tests fail when a command panics
The engine's guard turns a panic inside a command into EngineError::Internal and rolls the document back. The panic checks in command_sweep.rs counted that error like any other error. They include probe, which the empty-params, junk-params and structured-junk sweeps call. The tests in import_fuzz.rs that open, place or paste a file through a command counted it the same way. None of them reported the panic. Each of these checks now passes the command's result through no_panic, which the library file tests in import_fuzz.rs already use, and the change adds a copy of it to command_sweep.rs. A case whose command panics now fails. The panic from no_panic reads "<command> panicked: <message>", and the checks that print a panic's message show it. Each check handles any other error as before.
This commit is contained in:
1 parent
99959e4b21
commit
d875efd6e1
2 files changed
+42
-31
No files matched your search
@@ -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<Value, vectorcraft_engine::EngineError>) -> Result<Value, vectorcraft_engine::EngineError> {
|
||||
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<String> {
|
||||
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() {
|
||||
|
||||
@@ -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(())
|
||||
|
||||
Reference in new issue
Block a user