facett-map/tests/one_gpu_turn_is_the_only_one.rs
//! **ONE device turn for this crate's tests, and every file that opens a device takes it.**
//!
//! # The defect, measured on oden 2026-08-27
//!
//! `egui_kittest::Harness::render()` brings a wgpu device up. libtest runs `#[test]`
//! bodies on parallel threads. Two bring-ups at once take the whole test binary out with
//! `signal: 11, SIGSEGV` — not an assertion failure, a hard crash whose output looks
//! nothing like the test that was running, and which fails the *file*, not the test.
//!
//! The hazard is intermittent, which is why it was survivable and why it kept coming
//! back. Taking the lock back OUT of the two files that had it, default runner, five runs
//! each:
//!
//! ```text
//! rail_tier_pixels SIGSEGV 2/5
//! place_label_tier_pixels SIGSEGV 3/5
//! ```
//!
//! and the other door, `common::headless_device`, over eight runs each:
//!
//! ```text
//! line_gpu (7 tests, 7 devices) SIGSEGV 1/8
//! ```
//!
//! A file that came back green eight times is **not** a file that is safe — five of the
//! unlocked kittest files did exactly that (0/40 SIGSEGV) at a rate the two probed files
//! show is real. So the rule is not "the files that have crashed take the turn", it is
//! "every file that brings a device up takes it", and this guard is what keeps that true.
//!
//! # What went wrong the first time, and what this file forbids
//!
//! Six copies of the same two-line `static … Mutex<()>` existed at once — five declared
//! per file as `GPU_TURN`, plus `common/one_gpu.rs`, which arrived last and retired none
//! of them. A twinned *lock* is worse than a twinned helper: two mutexes over one device
//! serialise nothing at all, and the second one reads as coverage. Worse still, Rust
//! makes the twin invisible — `#[path = "common/one_gpu.rs"] mod one_gpu;` at a test
//! binary's root and `mod common;` → `common::one_gpu` compile **two different statics
//! out of one file**. So:
//!
//! 1. `common/one_gpu.rs` is the only place a device turn may be declared;
//! 2. a file that says `mod common;` reaches it as `common::one_gpu` and must NOT also
//! `#[path]`-include it — that is the invisible twin;
//! 3. every file that brings a device up from more than one `#[test]` takes the turn,
//! either by holding it itself or by going through `common::headless_device`, which
//! holds it for the bring-up.
//!
//! A single-`#[test]` binary is exempt from (3) and only from (3): it cannot race itself,
//! separate test binaries are separate processes, and the static is per binary.
//!
//! Run: `cargo test -p facett-map --test one_gpu_turn_is_the_only_one`
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
/// This file itself — it quotes every pattern it forbids, so it must never judge itself.
const SELF: &str = "one_gpu_turn_is_the_only_one.rs";
/// The one file allowed to declare a device turn.
const THE_ONE_WRITER: &str = "common/one_gpu.rs";
struct TestFile {
name: String,
text: String,
}
impl TestFile {
fn tests(&self) -> usize {
self.text.matches("#[test]").count()
}
/// Does this file bring a device up through `egui_kittest`? `Harness::render()` and
/// `snapshot()` are the two that do; `Harness::run()` drives egui with no device at
/// all, which is why files like `label_collision.rs` are not on this list.
fn kittest_device(&self) -> bool {
self.text.contains(".render()")
|| self.text.contains(".snapshot(")
|| self.text.contains("create_render_state(")
}
/// Does it bring a raw wgpu device up?
fn raw_device(&self) -> bool {
self.text.contains("headless_device(")
|| self.text.contains("device_or_skip(")
|| self.text.contains("request_device(")
}
fn holds(&self) -> bool {
self.text.contains("one_gpu::hold()")
}
fn says_mod_common(&self) -> bool {
self.text.lines().any(|l| l.trim() == "mod common;")
}
fn path_includes_one_gpu(&self) -> bool {
self.text.contains("#[path = \"common/one_gpu.rs\"]")
}
/// A `static … Mutex<()>` — the shape of a hand-rolled device turn.
fn declares_a_turn(&self) -> bool {
self.text.contains("Mutex::new(())")
}
/// Does it reach the SHARED turn in `facett-core` instead of declaring one? Since
/// 2026-08-31 that is where the lock lives: `facett-core`'s own device probes and
/// `facett-map3d`'s tests could not take a lock that sat in this crate's test
/// directory, and both then produced exactly the failures it exists to prevent (a
/// 13-minute deadlock and a `signal: 11`). Delegating is the RIGHT answer for the one
/// writer; declaring a second static is still the wrong one for everybody.
fn delegates_to_core(&self) -> bool {
self.text.contains("facett_core::render::gputurn")
}
}
fn tests_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests")
}
/// Every `tests/*.rs` plus `tests/common/*.rs`, by the name this file reports them under.
fn scan() -> BTreeMap<String, TestFile> {
let dir = tests_dir();
let mut out = BTreeMap::new();
let mut roots: Vec<PathBuf> = std::fs::read_dir(&dir)
.unwrap_or_else(|e| panic!("read {}: {e}", dir.display()))
.filter_map(Result::ok)
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "rs"))
.collect();
let common = dir.join("common");
if common.is_dir() {
roots.extend(
std::fs::read_dir(&common)
.unwrap_or_else(|e| panic!("read {}: {e}", common.display()))
.filter_map(Result::ok)
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "rs")),
);
}
roots.sort();
for p in roots {
let name = if p.parent().is_some_and(|d| d.ends_with("common")) {
format!("common/{}", p.file_name().unwrap().to_string_lossy())
} else {
p.file_name().unwrap().to_string_lossy().into_owned()
};
if name == SELF {
continue;
}
let text = std::fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {p:?}: {e}"));
out.insert(name.clone(), TestFile { name, text });
}
out
}
/// Non-vacuity, asserted once and used by every test below: a scan that stopped seeing
/// this directory would satisfy every set-difference in this file.
fn scanned() -> BTreeMap<String, TestFile> {
let files = scan();
assert!(
files.len() >= 25,
"the scan found only {} test sources under {:?} — it is not looking at facett-map, \
so its silence proves nothing",
files.len(),
tests_dir()
);
assert!(
files.contains_key(THE_ONE_WRITER),
"{THE_ONE_WRITER} is gone — the shared turn has no declaration, and every \
`one_gpu::hold()` below is naming something that no longer exists"
);
let device_files = files.values().filter(|f| f.kittest_device() || f.raw_device()).count();
assert!(
device_files >= 12,
"only {device_files} of {} test files look like they open a device — the pattern \
list has stopped matching this crate, and the rules below would pass vacuously",
files.len()
);
files
}
/// Unit mutexes in this directory that are **not** device turns, with the reason each
/// one exists. An excuse with no words is not an excuse, and a stale one is worse than
/// none: both are asserted below.
///
/// `std::sync::Mutex<()>` is the shape a hand-rolled device turn takes, and it is also
/// the shape of every other "these two tests must not overlap" lock. The difference is
/// WHAT is being protected, and only a sentence can say that — so the sentence is here,
/// where the guard can read it, rather than in a comment nobody re-reads.
const NOT_A_DEVICE_TURN: &[(&str, &str)] = &[
(
"gpu_clock.rs",
"LANE_LOCK serialises the process-global LANE RECORD (`lane::lane_use()`), not a \
device: both tests read a byte-count delta off one global counter and would smear \
each other's uploads. Its own device bring-up (`feature_device`) takes the shared \
turn separately, and must keep doing so.",
),
(
"zero_copy_gpu_lane.rs",
"SERIAL serialises HEAP MEASUREMENT under a counting global allocator — the \
thread-local counters are what make the numbers correct and this only keeps the \
fixture builds from thrashing each other. No device is opened in this file.",
),
];
/// **RED-when-broken: the device turn is declared in exactly one place.**
///
/// RED PROVEN on oden 2026-08-27: putting `static GPU_TURN: std::sync::Mutex<()> =
/// std::sync::Mutex::new(());` back into `paint_order_pixels.rs` fails with
/// `["paint_order_pixels.rs"] declare their own unit mutex`.
#[test]
fn only_common_one_gpu_declares_a_device_turn() {
let files = scanned();
let one = &files[THE_ONE_WRITER];
assert!(
one.declares_a_turn() || one.delegates_to_core(),
"{THE_ONE_WRITER} neither declares a `Mutex::new(())` nor re-exports \
`facett_core::render::gputurn` — the one writer is empty, and this whole guard \
would then report every file clean"
);
assert!(
one.text.matches("Mutex::new(())").count() <= 1,
"{THE_ONE_WRITER} declares more than one turn — the shared lock must be a single \
static, or the files that take `hold()` are not all taking the same one"
);
// Delegation and declaration are alternatives, never both: a file that re-exports the
// shared turn AND keeps a local static hands half its callers the wrong lock.
assert!(
!(one.declares_a_turn() && one.delegates_to_core()),
"{THE_ONE_WRITER} both declares a local `Mutex::new(())` and re-exports \
`facett_core::render::gputurn` — two locks over one device, which is the exact \
shape that let six copies coexist while the crashes went on"
);
// An excuse with no words is not an excuse.
let thin: Vec<&str> = NOT_A_DEVICE_TURN
.iter()
.filter(|(_, why)| why.trim().len() < 40)
.map(|(f, _)| *f)
.collect();
assert!(thin.is_empty(), "NOT_A_DEVICE_TURN entries whose reason is a shrug: {thin:?}");
// A stale excuse says a gap is accepted when the gap is gone, and the next real twin
// hides behind it.
let stale: Vec<&str> = NOT_A_DEVICE_TURN
.iter()
.filter(|(f, _)| files.get(*f).is_none_or(|tf| !tf.declares_a_turn()))
.map(|(f, _)| *f)
.collect();
assert!(
stale.is_empty(),
"NOT_A_DEVICE_TURN excuses {stale:?}, which no longer declare a unit mutex (or no \
longer exist) — delete the lines, or the list rots into a blanket"
);
let excused: std::collections::BTreeSet<&str> =
NOT_A_DEVICE_TURN.iter().map(|(f, _)| *f).collect();
let twins: Vec<&str> = files
.values()
.filter(|f| {
f.name != THE_ONE_WRITER && f.declares_a_turn() && !excused.contains(f.name.as_str())
})
.map(|f| f.name.as_str())
.collect();
assert!(
twins.is_empty(),
"{twins:?} declare their own unit mutex. Six copies of the DEVICE turn existed at \
once on 2026-08-27 and the crashes went on anyway: two mutexes over one device \
serialise nothing, and the second one reads as coverage. If this is the device \
turn, take `{THE_ONE_WRITER}`'s instead. If it protects something else, say what \
in `NOT_A_DEVICE_TURN` — in a sentence, where this guard can read it."
);
}
/// **RED-when-broken: no binary compiles two copies of the shared static.**
///
/// `#[path = "common/one_gpu.rs"] mod one_gpu;` at a test binary's root and `mod common;`
/// → `common::one_gpu` are two DIFFERENT modules built from one file, each with its own
/// `static`. A file doing both takes two different locks and is exactly as unprotected as
/// a file taking none — while looking, in every grep, like it is protected twice.
///
/// RED PROVEN on oden 2026-08-27: adding the `#[path]` include back to
/// `fill_extent_gate.rs` (which says `mod common;`) fails with `fill_extent_gate.rs
/// declares BOTH`.
#[test]
fn no_file_reaches_the_shared_turn_by_two_names_at_once() {
let files = scanned();
let both: Vec<&str> = files
.values()
.filter(|f| f.name != THE_ONE_WRITER && f.says_mod_common() && f.path_includes_one_gpu())
.map(|f| f.name.as_str())
.collect();
assert!(
both.is_empty(),
"{both:?} declare BOTH `mod common;` and `#[path = \"common/one_gpu.rs\"]`. That \
compiles TWO statics out of one file into one test binary, and two mutexes over \
one device serialise nothing. A file with `mod common;` says \
`common::one_gpu::hold()`; a file without it `#[path]`-includes the module and \
says `one_gpu::hold()`. Never both."
);
// Non-vacuity in both directions: this rule is a set difference, and it would pass on
// a crate where NOBODY reached the turn either way.
let by_path = files.values().filter(|f| f.path_includes_one_gpu()).count();
let by_common = files.values().filter(|f| f.says_mod_common()).count();
assert!(
by_path >= 5 && by_common >= 5,
"only {by_path} file(s) reach the turn by `#[path]` and {by_common} by \
`mod common;` — with one route empty this rule can never fire"
);
}
/// **RED-when-broken: every file that opens a device from more than one `#[test]` takes
/// the turn.**
///
/// The two routes are equally accepted, because both end at the same static:
/// * the file holds it itself (`one_gpu::hold()`), which the `egui_kittest` render lane
/// must do — `headless_device` is not on that path;
/// * or it goes through `common::headless_device`, which takes the turn for the bring-up,
/// which is what `mod common;` buys the raw-wgpu files.
///
/// RED PROVEN on oden 2026-08-27: deleting the `let _gpu = one_gpu::hold();` from
/// `render_proof.rs::assert_non_blank` fails with `render_proof.rs renders from 7
/// #[test]s and never takes the turn`.
#[test]
fn every_file_that_opens_a_device_from_two_tests_takes_the_turn() {
let files = scanned();
let mut naked: Vec<String> = Vec::new();
let mut covered = 0usize;
let mut exempt_single = 0usize;
for f in files.values() {
if f.name == THE_ONE_WRITER || !(f.kittest_device() || f.raw_device()) {
continue;
}
// A binary with one `#[test]` cannot race itself, and separate test binaries are
// separate processes. The static is per binary, so this exemption is exact.
if f.tests() < 2 {
exempt_single += 1;
continue;
}
let ok = if f.kittest_device() {
// The kittest render lane never passes through `headless_device`, so
// `mod common;` buys it nothing. It must hold the turn itself.
f.holds()
} else {
f.holds() || f.says_mod_common()
};
if ok {
covered += 1;
} else {
naked.push(format!(
" {:32} {} device from {} #[test]s, and never takes the turn",
f.name,
if f.kittest_device() { "kittest" } else { "raw wgpu" },
f.tests()
));
}
}
assert!(
covered >= 10,
"only {covered} file(s) were found to take the turn — the detection has stopped \
matching, and an empty left-hand side passes every check below"
);
assert!(
naked.is_empty(),
"{} test file(s) bring a wgpu device up from more than one `#[test]` and take no \
turn. That is a `signal: 11` waiting for the scheduler, not a test failure — \
MEASURED on oden 2026-08-27 at 2/5, 3/5 and 1/8 on three different files:\n\n{}\n\n\
Fix: `mod common;` files say `common::one_gpu::hold()`, the rest \
`#[path = \"{THE_ONE_WRITER}\"] mod one_gpu;` then `one_gpu::hold()`. \
({covered} file(s) already do; {exempt_single} are single-`#[test]` binaries and \
cannot race themselves.)",
naked.len(),
naked.join("\n"),
);
}