mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-30 05:37:00 -06:00
Engine: macro debugger reports a step that panics as the step's failure
When a debugged step panicked, the nested execute rolled the run back and dropped the debugger before advance could reattach the machine, so the caller got "No macro is being debugged" instead of the failed step. For example, stepping into a step whose command panics lost the step path and the panic message. advance now sees that the run was already rolled back and returns the usual failed result with the step error.
This commit is contained in:
@@ -188,6 +188,7 @@ fn all_paths(steps: &[gridcraft_model::Step], prefix: &str) -> BTreeSet<String>
|
||||
/// Runs one step (`Step`), or steps until a breakpoint, the target step or the end.
|
||||
fn advance(s: &mut Session, mode: Mode) -> Result<Json> {
|
||||
let d = s.macro_debug.as_mut().ok_or_else(not_debugging)?;
|
||||
let name = d.name.clone();
|
||||
let breakpoints = d.breakpoints.clone();
|
||||
let only_listed = d.only_listed;
|
||||
let mut machine = d.machine.take().ok_or_else(not_debugging)?;
|
||||
@@ -219,8 +220,16 @@ fn advance(s: &mut Session, mode: Mode) -> Result<Json> {
|
||||
}
|
||||
}
|
||||
let done = machine.is_done();
|
||||
if let Some(d) = s.macro_debug.as_mut() {
|
||||
d.machine = Some(machine);
|
||||
let executed = machine.executed;
|
||||
match s.macro_debug.as_mut() {
|
||||
Some(d) => d.machine = Some(machine),
|
||||
// A step panicked and `recover` has already rolled the run back.
|
||||
None => {
|
||||
return match outcome {
|
||||
Err(e) => Ok(failed(&name, executed, &e)),
|
||||
Ok(()) => Err(not_debugging()),
|
||||
};
|
||||
}
|
||||
}
|
||||
note_touched(s, &before);
|
||||
if let Err(e) = outcome {
|
||||
@@ -247,19 +256,17 @@ fn finish(s: &mut Session, error: Option<StepError>) -> Result<Json> {
|
||||
// The group is gone only if its workbook was closed; then there is nothing to end.
|
||||
let _ = if rollback { s.group_rollback(token) } else { s.group_end(token).map(|_| ()) };
|
||||
}
|
||||
if let Some(e) = &error {
|
||||
return Ok(failed(&d.name, executed, e));
|
||||
}
|
||||
let mut out = json!({"macro": d.name, "executed": executed});
|
||||
match (&error, &stopped) {
|
||||
(Some(e), _) => {
|
||||
out["state"] = json!("failed");
|
||||
out["error"] = serde_json::to_value(e).unwrap_or(Json::Null);
|
||||
out["message"] = json!(format!("{} Nothing was changed.", e.describe()));
|
||||
}
|
||||
(None, Some((msg, rolled))) => {
|
||||
match &stopped {
|
||||
Some((msg, rolled)) => {
|
||||
out["state"] = json!("stopped");
|
||||
out["stopped"] = json!(msg);
|
||||
out["kept"] = json!(!rolled);
|
||||
}
|
||||
(None, None) => out["state"] = json!("finished"),
|
||||
None => out["state"] = json!("finished"),
|
||||
}
|
||||
if !rollback {
|
||||
out["changed"] = json!(changed);
|
||||
@@ -267,6 +274,17 @@ fn finish(s: &mut Session, error: Option<StepError>) -> Result<Json> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The result of a run that a failed step rolled back.
|
||||
fn failed(name: &str, executed: u64, e: &StepError) -> Json {
|
||||
json!({
|
||||
"macro": name,
|
||||
"executed": executed,
|
||||
"state": "failed",
|
||||
"error": serde_json::to_value(e).unwrap_or(Json::Null),
|
||||
"message": format!("{} Nothing was changed.", e.describe()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Stops the run: keeps what it did as one undo step, or rolls it back.
|
||||
fn stop(s: &mut Session, keep: bool) -> Result<Json> {
|
||||
let Some(mut d) = s.macro_debug.take() else { return Err(not_debugging()) };
|
||||
@@ -360,6 +378,33 @@ mod tests {
|
||||
s.execute("cell.set", json!({"cell": "B1", "input": "free"})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_step_that_panics_reports_the_step_failure() {
|
||||
crate::register_test_command("test.debugStepPanics", |_, _| panic!("boom"));
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
let m = crate::macros::parse_macro(&json!({"name": "M", "output": {"onConflict": "overwrite"}, "steps": [
|
||||
{"command": "cell.set", "params": {"cell": "A1", "input": "x"}},
|
||||
{"command": "test.debugStepPanics", "params": {}}
|
||||
]}))
|
||||
.unwrap();
|
||||
let d = s.doc_mut().unwrap();
|
||||
let mut wb = (*d.wb).clone();
|
||||
wb.macros.push(m);
|
||||
d.wb = std::sync::Arc::new(wb);
|
||||
let start = s.doc().unwrap().wb.clone();
|
||||
s.execute("macro.debug", json!({"name": "M"})).unwrap();
|
||||
s.execute("macro.debug", json!({"action": "step"})).unwrap();
|
||||
let r = s.execute("macro.debug", json!({"action": "step"})).unwrap();
|
||||
assert_eq!(r["state"], "failed", "{r}");
|
||||
assert_eq!(r["macro"], "M");
|
||||
assert_eq!(r["error"]["step"], "2");
|
||||
assert!(r["message"].as_str().unwrap().contains("boom"), "{r}");
|
||||
assert!(s.macro_debug.is_none());
|
||||
assert!(std::sync::Arc::ptr_eq(&s.doc().unwrap().wb, &start));
|
||||
assert!(!s.in_group(s.doc().unwrap().uid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_workbook_in_the_touched_log_waits_until_the_run_ends() {
|
||||
let mut s = Session::new();
|
||||
|
||||
Reference in New Issue
Block a user