kernel-cases: coverage by kind of source, and the ring join of #2357

- tests/kernel-cases/sources.json gives each looked-at number's kind: an open or closed issue, a
  merged PR, one of the frozen truck-geometry PRs, a kernel-paused PR. The report's coverage by
  source counts how many of each were looked at, became cases or were skipped.
- crashes/2357: one join of a ring of 90 overlapping cylinder segments. The right answer is one
  closed ring body. The old kernel still panics in its intersection curves and runs past the
  timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruck
2026-10-21 07:15:20 -04:00
committed by jyviko
parent 146ab740ae
commit c87bb72f0f
5 changed files with 1743 additions and 0 deletions
+3
View File
@@ -57,6 +57,9 @@ parts): every issue and PR looked at that did not become a case, with one line e
[{"source": "tangency-labs/solidcraft#123", "why": "UI only: the selection highlight, no geometry"}]
```
`sources.json` says what kind each source number is (an open or closed issue, a merged PR, one of
the frozen truck-geometry PRs, a `kernel-paused` PR), for the report's coverage by source.
## expected.json
```json
@@ -0,0 +1,10 @@
{
"sources": [
"tangency-labs/solidcraft#2357"
],
"what": "one join of a ring of 90 straight cylinder segments, each overlapping its neighbour at a 4 degree joint: a caught truck panic, then a refusal after 21 minutes",
"source": "invariants",
"method": "the segments (diameter 5, 7.6 long, centred on radius 100, every 4 degrees) overlap their neighbours (the 4 degree chord at radius 100 is 6.98 < 7.6), so they join into one closed ring: one body with one shell, valid. The joint overlaps have no simple closed form, so no volume is graded. The rig's timeout bounds the time (the issue expects time to grow about linearly with the segment count)",
"body_count": 1,
"shells": 1
}
@@ -0,0 +1,128 @@
{
"commands": [
{
"command": "solid.cylinder",
"params": {
"diameter": 5,
"height": 7.6,
"base": [
-3.8,
100,
0
],
"axis": "X",
"operation": "new",
"name": "Seg"
}
},
{
"command": "solid.pattern.circular",
"params": {
"features": [
"Seg"
],
"axis": "Z",
"count": 90,
"name": "Segs"
}
},
{
"command": "solid.combine",
"params": {
"target": "Body1",
"tools": [
"Body2",
"Body3",
"Body4",
"Body5",
"Body6",
"Body7",
"Body8",
"Body9",
"Body10",
"Body11",
"Body12",
"Body13",
"Body14",
"Body15",
"Body16",
"Body17",
"Body18",
"Body19",
"Body20",
"Body21",
"Body22",
"Body23",
"Body24",
"Body25",
"Body26",
"Body27",
"Body28",
"Body29",
"Body30",
"Body31",
"Body32",
"Body33",
"Body34",
"Body35",
"Body36",
"Body37",
"Body38",
"Body39",
"Body40",
"Body41",
"Body42",
"Body43",
"Body44",
"Body45",
"Body46",
"Body47",
"Body48",
"Body49",
"Body50",
"Body51",
"Body52",
"Body53",
"Body54",
"Body55",
"Body56",
"Body57",
"Body58",
"Body59",
"Body60",
"Body61",
"Body62",
"Body63",
"Body64",
"Body65",
"Body66",
"Body67",
"Body68",
"Body69",
"Body70",
"Body71",
"Body72",
"Body73",
"Body74",
"Body75",
"Body76",
"Body77",
"Body78",
"Body79",
"Body80",
"Body81",
"Body82",
"Body83",
"Body84",
"Body85",
"Body86",
"Body87",
"Body88",
"Body89",
"Body90"
],
"operation": "join"
}
}
]
}
File diff suppressed because it is too large Load Diff
+27
View File
@@ -213,6 +213,32 @@ fn link(source: &str) -> String {
}
}
/// Coverage by kind of source (issue, merged PR, a frozen truck-geometry PR...), from
/// `sources.json`: how many were looked at, how many became cases, how many were skipped.
fn source_kinds(dir: &Path, cited: &BTreeMap<String, Vec<String>>) -> Result<String, String> {
let kinds = read(&dir.join("sources.json"));
let Some(kinds) = kinds.as_object() else { return Ok(String::new()) };
let cited: std::collections::BTreeSet<u64> = cited.keys().map(|s| source_number(s)).collect();
let mut table: BTreeMap<String, (usize, usize)> = BTreeMap::new();
for (n, kind) in kinds {
let row = table.entry(kind.as_str().unwrap_or("?").to_string()).or_default();
if n.parse::<u64>().is_ok_and(|n| cited.contains(&n)) {
row.0 += 1;
} else {
row.1 += 1;
}
}
let mut md = String::from("\n## Coverage by source\n\n| Source | Looked at | Became cases | Skipped |\n|---|---|---|---|\n");
let (mut a, mut b) = (0, 0);
for (kind, (cases, skipped)) in &table {
md += &format!("| {kind} | {} | {cases} | {skipped} |\n", cases + skipped);
a += cases;
b += skipped;
}
md += &format!("| **all** | {} | {a} | {b} |\n", a + b);
Ok(md)
}
fn write_report(o: &Options, dir: &Path, list: &[PathBuf], results: &[Value]) -> Result<String, String> {
let key = |r: &Value| (r["area"].as_str().unwrap_or("").to_string(), r["case"].as_str().unwrap_or("").to_string());
let mut by_case: BTreeMap<(String, String), BTreeMap<String, Value>> = BTreeMap::new();
@@ -284,6 +310,7 @@ fn write_report(o: &Options, dir: &Path, list: &[PathBuf], results: &[Value]) ->
for (s, cs) in &per_source {
md += &format!("| {} | {} |\n", link(s), cs.join(", "));
}
md += &source_kinds(dir, &per_source)?;
// What was looked at and skipped, area by area (`<area>/skipped.json`).
md += "\n## Looked at and skipped\n\n";
let mut skipped = 0;