From d506382aca38b382605fdeda5148eebb2c4c1148 Mon Sep 17 00:00:00 2001 From: bherbruck Date: Wed, 14 Oct 2026 07:19:25 -0400 Subject: [PATCH] ci: run the action-matrix sample on its own; name a hang in an edit round (#1037) The sampled pi-case hole on Body1/F13:side:l7@F14.1 became a real hole with #952 (7.5 s instead of 170 ms); beside the heavy tests on main's push runs it passed the matrix's 90 s action limit in its edit round, reported as the design not loading. The sample now takes every CPU in nextest so nothing runs beside it, and the edit round writes a start line so a hang there is reported as +edit. Co-Authored-By: Claude Opus 5.5 --- .config/nextest.toml | 8 ++++++++ apps/solidcraft-cli/tests/action_matrix.rs | 13 ++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 5e98c2b7..34129069 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -28,3 +28,11 @@ slow-timeout = { period = "60s" } [profile.ci.junit] path = "junit.xml" + +# The action matrix sample runs its designs in parallel worker processes, one per CPU. Run with +# other tests beside it (the heavy ones on main), a pi-case hole that takes 8 s alone (#952 makes +# it a real hole) went past the matrix's 90 s action limit. It runs on its own. +[[profile.ci.overrides]] +filter = "binary(action_matrix) & test(=action_matrix_sample)" +threads-required = "num-cpus" + diff --git a/apps/solidcraft-cli/tests/action_matrix.rs b/apps/solidcraft-cli/tests/action_matrix.rs index 55301884..b35bc110 100644 --- a/apps/solidcraft-cli/tests/action_matrix.rs +++ b/apps/solidcraft-cli/tests/action_matrix.rs @@ -693,6 +693,8 @@ fn action_matrix_worker() { && let Some((name, expr)) = &edit && stable_hash(&format!("{k} edit")).is_multiple_of(EDIT_SAMPLE) { + // Marked like an action, so a hang here is put down to the edit round (not to loading). + emit(json!({"start": i, "edit": true})); let t = Instant::now(); let start = s.doc.features.len(); let r = scratch @@ -785,7 +787,9 @@ fn run_design(design: &str, out: &Path, sample: u64) -> Vec { .map(|f| std::io::BufReader::new(f).lines().map_while(Result::ok).filter_map(|l| serde_json::from_str(&l).ok()).collect()) .unwrap_or_default(); let done = lines.iter().any(|l| l.get("done").is_some()); - let last_start = lines.iter().rev().take_while(|l| l.get("start").is_some() || l.get("design").is_none()).find_map(|l| l["start"].as_u64()); + let last = lines.iter().rev().take_while(|l| l.get("start").is_some() || l.get("design").is_none()).find(|l| l.get("start").is_some()); + let last_start = last.and_then(|l| l["start"].as_u64()); + let in_edit = last.is_some_and(|l| l["edit"] == true); results = lines.iter().filter(|l| l.get("design").is_some()).cloned().collect(); if done { break; @@ -800,7 +804,7 @@ fn run_design(design: &str, out: &Path, sample: u64) -> Vec { }; // Which action was it? The child enumerates them; ask it by running from there with a // marker: simplest is to record the index and let the next run name it. - extra.push(json!({"design": design, "index": i, "kind": if timed_out { "timeout" } else { "crash" }, + extra.push(json!({"design": design, "index": i, "edit": in_edit, "kind": if timed_out { "timeout" } else { "crash" }, "message": if timed_out { format!("no result after {} s", ACTION_TIMEOUT.as_secs()) } else { format!("the process ended: {status:?}") }})); from = i as usize + 1; } @@ -809,7 +813,10 @@ fn run_design(design: &str, out: &Path, sample: u64) -> Vec { let names = action_names(design); for e in &mut extra { if let Some(i) = e.get("index").and_then(Value::as_u64) { - let (entity, action) = names.get(i as usize).cloned().unwrap_or_else(|| (format!("action{i}"), "unknown".into())); + let (entity, mut action) = names.get(i as usize).cloned().unwrap_or_else(|| (format!("action{i}"), "unknown".into())); + if e["edit"] == true { + action += "+edit"; + } *e = json!({"design": design, "entity": entity, "action": action, "ok": false, "kind": e["kind"], "message": e["message"]}); } }