kernel-cases: an smlib feature for the CLI, and no grading of a kernel the build isn't running

- solidcraft-cli and solidcraft-engine forward an `smlib` feature to solidcraft-kernel/smlib, so
  `cargo build -p solidcraft-cli --features smlib` gives the rig SMLib's check.
- solidcraft_kernel::boolean_kernel() says which kernel this process runs booleans on.
  `default` until the SMLib switch (SOLIDCRAFT_BOOLEANS=smlib) lands and answers there. An smlib
  run on a build without the switch reports "not supported yet" instead of grading the default
  kernel under SMLib's name.
- An area may keep its skipped list in parts (skipped-<part>.json).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruck
2026-10-21 05:31:54 -04:00
committed by jyviko
parent a7c0109eab
commit 7bb74d1c6b
7 changed files with 44 additions and 7 deletions
+4
View File
@@ -24,6 +24,10 @@ default = ["raytrace-cpu"]
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
[features]
# Link SMLib into the kernel, for the regression rig's smlib runs (tests/kernel-cases).
smlib = ["solidcraft-engine/smlib"]
[dependencies]
solidcraft-engine = { workspace = true }
solidcraft-gpu = { workspace = true }
+7 -1
View File
@@ -62,7 +62,13 @@ fn check_case(dir: &str) -> Value {
};
if kernel() == "smlib" && !solidcraft_engine::kernel::smlib_built() {
out["status"] = json!("unsupported");
out["error"] = json!("this build has no SMLib: build solidcraft-cli with --features solidcraft-kernel/smlib");
out["error"] = json!("this build has no SMLib: build solidcraft-cli with --features smlib");
return out;
}
let running = solidcraft_engine::kernel::boolean_kernel();
if kernel() != running {
out["status"] = json!("unsupported");
out["error"] = json!(format!("this build runs booleans on the {running} kernel, not {}", kernel()));
return out;
}
let refused = want["outcome"].as_str() == Some("refused");
+2
View File
@@ -13,6 +13,8 @@ default = []
# Optional authored texture/HDR bytes; the command registry and metadata remain available.
bundled-render-library = []
raytrace-cpu = ["solidcraft-render/raytrace-cpu"]
# Link SMLib into the kernel (solidcraft-kernel/smlib; needs scripts/fetch-smlib.sh).
smlib = ["solidcraft-kernel/smlib"]
# Turns on the parameter-contract audit recorder (`SOLIDCRAFT_PARAM_AUDIT`, see
# `src/schema/record.rs`), for checking a new contract against the whole test suite.
param-audit = []
+1 -1
View File
@@ -224,7 +224,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::{Validity, smlib_built, validity};
pub use validity::{Validity, boolean_kernel, smlib_built, validity};
pub use whole_face::{WholeFace, cut_takes_only_the_face, cut_tool, face_prism, matching_face};
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
+8
View File
@@ -47,3 +47,11 @@ fn smlib_validity(_: &Body) -> Option<Result<(), String>> {
pub fn smlib_built() -> bool {
cfg!(feature = "smlib")
}
/// The kernel booleans run on in this process: `"default"` or `"smlib"`. The regression rig
/// refuses to grade a kernel the process isn't running. The SMLib boolean switch
/// (`SOLIDCRAFT_BOOLEANS=smlib`) answers here once it lands; until then this is always
/// `"default"`.
pub fn boolean_kernel() -> &'static str {
"default"
}
+3 -3
View File
@@ -10,7 +10,7 @@ cargo xtask kernel-cases --kernel both --cli target/release/solidcraft-cli # a
cargo run -p solidcraft-cli -- kernel-cases tests/kernel-cases/booleans/<id> # one case
```
For the `smlib` column, build `solidcraft-cli` with `--features solidcraft-kernel/smlib` (after
For the `smlib` column, build `solidcraft-cli` with `--features smlib` (after
`scripts/fetch-smlib.sh`). The rig sets `SOLIDCRAFT_BOOLEANS=smlib` for those runs.
## Rules
@@ -46,8 +46,8 @@ Each case has:
specification, never a user's file).
- `expected.json`: the right answer, graded by `solidcraft-cli kernel-cases`.
Each area also has `skipped.json`: every issue and PR looked at that did not become a case, with
one line each saying why.
Each area also has `skipped.json` (or several, `skipped-<part>.json`, where an area is triaged in
parts): every issue and PR looked at that did not become a case, with one line each saying why.
```json
[{"source": "tangency-labs/solidcraft#123", "why": "UI only: the selection highlight, no geometry"}]
+19 -2
View File
@@ -189,6 +189,11 @@ fn sources_of(want: &Value) -> Vec<String> {
want["sources"].as_array().into_iter().flatten().filter_map(|s| s.as_str().map(str::to_string)).collect()
}
/// The number in `owner/repo#123`, for sorting.
fn source_number(s: &str) -> u64 {
s.rsplit('#').next().and_then(|n| n.parse().ok()).unwrap_or(u64::MAX)
}
/// `tangency-labs/solidcraft#123` as a link.
fn link(source: &str) -> String {
match source.split_once('#') {
@@ -274,8 +279,20 @@ fn write_report(o: &Options, dir: &Path, list: &[PathBuf], results: &[Value]) ->
let mut area_dirs: Vec<PathBuf> = std::fs::read_dir(dir).map_err(|e| e.to_string())?.flatten().map(|e| e.path()).filter(|p| p.is_dir()).collect();
area_dirs.sort();
for a in area_dirs {
let list = read(&a.join("skipped.json"));
let Some(items) = list.as_array().filter(|l| !l.is_empty()) else { continue };
// skipped.json, or several (skipped-<part>.json) where people triage an area in parts.
let mut files: Vec<PathBuf> = std::fs::read_dir(&a)
.map_err(|e| e.to_string())?
.flatten()
.map(|e| e.path())
.filter(|p| p.file_name().and_then(|n| n.to_str()).is_some_and(|n| n.starts_with("skipped") && n.ends_with(".json")))
.collect();
files.sort();
let mut items: Vec<Value> = files.iter().flat_map(|f| read(f).as_array().cloned().unwrap_or_default()).collect();
if items.is_empty() {
continue;
}
items.sort_by_key(|it| source_number(it["source"].as_str().unwrap_or("")));
let items = &items;
md += &format!("### {}\n\n| Source | Why skipped |\n|---|---|\n", a.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default());
for it in items {
skipped += 1;