mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 02:55:54 +00:00
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:
@@ -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(()) }
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user