kernel-cases: --dumps=DIR leaves each failing SMLib call's inputs for replay

A rig case whose SMLib call fails left nothing to replay it from: nothing called
solidcraft_smlib::set_dump_dir. `solidcraft-cli kernel-cases --dumps=DIR` now points SMLib's
dump folder at DIR/<area>-<case> before each case runs, so a failing call leaves its
arguments and B-reps there (smlib_brep_read reads them back into a scratch C program), and
turns it off after the run. Off without the flag. The kernel exposes it as
smlib_dump_dir, beside smlib_built (the CLI reaches SMLib only through the kernel).

(not built: base in progress)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
jyviko
2026-10-22 07:21:21 -04:00
parent 166ebe2ee9
commit beebf22032
5 changed files with 40 additions and 5 deletions
+22 -2
View File
@@ -129,7 +129,7 @@ fn case_name(dir: &str) -> (String, String) {
(name(p.parent()), name(Some(p)))
}
fn check_case(dir: &str) -> Value {
fn check_case(dir: &str, dumps: Option<&std::path::Path>) -> Value {
let (area, name) = case_name(dir);
let mut out = json!({"case": name, "area": area, "kernel": kernel()});
let want = match read_json(&format!("{dir}/expected.json")) {
@@ -167,6 +167,18 @@ fn check_case(dir: &str) -> Value {
let outcome = want["outcome"].as_str().unwrap_or("solid");
let refused = outcome == "refused";
let may_refuse = refused || outcome == "solid_or_refused";
// The SMLib calls that fail in this case leave their inputs in its own folder.
if let Some(root) = dumps {
let folder = root.join(format!("{area}-{name}"));
if let Err(e) = std::fs::create_dir_all(&folder)
.map_err(|e| e.to_string())
.and_then(|()| solidcraft_engine::kernel::smlib_dump_dir(Some(&folder)).map_err(|e| e.to_string()))
{
out["status"] = json!("fail");
out["error"] = json!(format!("--dumps: {}: {e}", folder.display()));
return out;
}
}
let mut s = solidcraft_engine::Session::default();
let clock = std::time::Instant::now();
let ran = s.run_script(&commands);
@@ -331,7 +343,15 @@ pub fn run(args: &[String]) -> Result<(), String> {
if dirs.is_empty() {
return Err("kernel-cases: give one or more case directories (each with recipe.json and expected.json)".into());
}
let results: Vec<Value> = dirs.iter().map(|d| check_case(d)).collect();
// `--dumps=DIR`: each case's failing SMLib calls leave their inputs in DIR/<area>-<case>.
let dumps = args.iter().find_map(|a| a.strip_prefix("--dumps=")).map(std::path::PathBuf::from);
if dumps.as_ref().is_some_and(|d| d.as_os_str().is_empty()) {
return Err("kernel-cases: --dumps= needs a folder".into());
}
let results: Vec<Value> = dirs.iter().map(|d| check_case(d, dumps.as_deref())).collect();
if dumps.is_some() {
solidcraft_engine::kernel::smlib_dump_dir(None).map_err(|e| format!("kernel-cases: --dumps: {e}"))?;
}
report(args, &results);
let bad = results.iter().filter(|r| matches!(r["status"].as_str(), Some("fail" | "crash"))).count();
if bad > 0 { Err(format!("{bad} kernel case(s) failed")) } else { Ok(()) }
+2 -2
View File
@@ -15,7 +15,7 @@
//! solidcraft-cli commands the command registry as JSON
//! solidcraft-cli recipe <recipe.json> translate an oracle recipe to a script
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
//! solidcraft-cli kernel-cases [--json] <case-dir>... the kernel regression rig (tests/kernel-cases)
//! solidcraft-cli kernel-cases [--json] [--dumps=DIR] <case-dir>... the kernel regression rig (tests/kernel-cases)
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
//! [--param-check off|warn|strict]
//! MCP server on stdio (docs/mcp.md)
@@ -82,7 +82,7 @@ const USAGE_RECIPE: &str = " solidcraft-cli recipe <recipe.json>\n";
/// `oracle`'s lines in the usage.
const USAGE_ORACLE: &str = " solidcraft-cli oracle <case-dir>... [--json]\n";
/// `kernel-cases`'s lines in the usage.
const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [--json]\n (the kernel regression rig, tests/kernel-cases; SOLIDCRAFT_KERNEL picks the kernel)\n";
const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [--json] [--dumps=DIR]\n (the kernel regression rig, tests/kernel-cases; SOLIDCRAFT_KERNEL picks the kernel; --dumps: each case's\n failing SMLib calls leave their inputs for replay in DIR/<area>-<case>)\n";
/// `fuzz`'s lines in the usage.
const USAGE_FUZZ: &str =
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
+1 -1
View File
@@ -217,7 +217,7 @@ pub use sweep_path::{RailFit, SweepControl, SweepOrientation, sweep_path, sweep_
pub use topo::{CylinderFace, cylinder_end_at, cylinder_face, cylinder_face_at};
pub use topo::{TopoCounts, merged_topology, seam_flags};
pub use tspline::tspline_sheet;
pub use validity::{SmlibCheck, Validity, smlib_built, validity};
pub use validity::{SmlibCheck, Validity, smlib_built, smlib_dump_dir, validity};
pub use whole_face::{WholeFace, cut_takes_only_the_face, cut_tool, face_prism, matching_face};
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
+14
View File
@@ -59,3 +59,17 @@ pub fn smlib_built() -> bool {
cfg!(feature = "smlib")
}
/// Where every SMLib call that fails from now on leaves its inputs for replay (the C API's
/// `smlib_set_dump_dir`: the call's arguments and B-reps, which `smlib_brep_read` reads back);
/// `None` stops it. A build without SMLib makes no calls to dump.
pub fn smlib_dump_dir(dir: Option<&std::path::Path>) -> crate::Result<()> {
#[cfg(feature = "smlib")]
{
solidcraft_smlib::set_dump_dir(dir).map_err(|e| crate::KernelError::Failed(format!("SMLib dump folder: {e}")))
}
#[cfg(not(feature = "smlib"))]
{
let _ = dir;
Ok(())
}
}
+1
View File
@@ -8,6 +8,7 @@ them. `cargo xtask kernel-cases` runs them and writes [`docs/kernel-cases.md`](.
cargo xtask kernel-cases # the default kernel
cargo xtask kernel-cases --kernel both --cli target/release/solidcraft-cli # and SMLib
cargo run -p solidcraft-cli -- kernel-cases tests/kernel-cases/booleans/<id> # one case
cargo run -p solidcraft-cli --features smlib -- kernel-cases --dumps=/tmp/dumps <case-dir> # failing SMLib calls leave replayable inputs
```
For the `smlib` column, build `solidcraft-cli` with `--features smlib` (after