1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// RLX — versatile ML compiler + runtime.
// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! **`RLX_REQUIRE_DEVICE` must reach every device-gated test. No exceptions.**
//!
//! The flag turns "no GPU" from a silent skip into a failure — but only for
//! tests that route their skip through `skip_unless_available` /
//! `rlx_ir::env::skip_unless_device`. The alternative reports `ok`:
//!
//! ```ignore
//! if !is_available(device) {
//! eprintln!("skip ... (unavailable)");
//! return; // → the summary line says `ok`
//! }
//! ```
//!
//! This started as a ratchet — a pinned count that could only go down — because
//! 186 files were on the wrong side of it and migrating them in one pass was
//! not safe. They have since all been migrated, so the pin is **0** and this is
//! now an absolute rule rather than a backlog: a raw `if !is_available(..)`
//! skip anywhere in the test tree fails here.
//!
//! `tests/device_present.rs` (and its per-backend twins) remain as a floor at
//! suite granularity — they fail if a compiled-in backend cannot be
//! instantiated at all, whatever the individual tests do. The two are
//! complementary: the floor catches a dead card, this catches the skip that
//! would have hidden it.
//!
//! Left unmeasured, coverage sat at 29-of-110 without anyone noticing.
use std::path::{Path, PathBuf};
/// **Zero. Do not raise this.**
///
/// Raising it means a test was written with a skip that reports `ok` on a rig
/// with no device — the thing this whole mechanism exists to stop. Route the
/// skip through the helper instead.
///
/// The count went 186 → 128 → 45 → 0: ~700 call sites across 155 files in
/// rlx-metal, wgpu, vulkan, mlx, cuda, rocm, coreml, tpu and rlx-runtime's own
/// tests. Every rewrite was compile-checked; metal, wgpu, vulkan, mlx and
/// rlx-runtime were also run in full.
///
/// Two corrections along the way, both found by the gate arguing with itself:
///
/// * it **scanned a hardcoded list of backend directories** and missed seven,
/// including rlx-tpu's three unmigrated files. Fixing that pushed the count
/// 47 → 50 and the ratchet refused the raise, so the files got migrated.
/// * it counted things that are **not skips** — `assert!(is_available())`,
/// `assert!(!is_available())` asserting a device is *absent*, and
/// `.find(|d| !is_available(*d))` searching *for* an unavailable one. See
/// [`gates_on_availability`]; a skip is now specifically a negated call in an
/// `if` condition.
///
/// Six hand-rolled copies of the skip decision also turned up — as
/// `device_missing()` in four backend crates and a shadowing
/// `fn skip_unless_available` in `activation_batch_parity.rs` — and **one of
/// them had lost the `RLX_REQUIRE_DEVICE` assert entirely**, so it read like a
/// guarded file while being exempt. All six now call the shared helper.
const PINNED_UNMIGRATED: usize = 0;
/// Every test directory to scan: this crate's, plus `tests/` under every crate
/// in `crates/backends/`.
///
/// **Discovered, not listed.** The first version of this hardcoded eight
/// backend paths and silently missed seven others — rlx-tpu alone had three
/// unmigrated files sitting outside the count. A hand-maintained list of the
/// places to look for drift is itself a thing that drifts, which is the lesson
/// this whole gate exists to encode. A new backend is now covered the day its
/// `tests/` directory appears.
fn test_dirs(root: &Path) -> Vec<PathBuf> {
let mut dirs = vec![root.join("tests")];
let backends = root.join("../../backends");
if let Ok(entries) = std::fs::read_dir(&backends) {
let mut found: Vec<PathBuf> = entries
.flatten()
.map(|e| e.path().join("tests"))
.filter(|p| p.is_dir())
.collect();
found.sort();
dirs.extend(found);
}
dirs
}
/// Does this file decide to **skip** based on device availability?
///
/// Three things that are *not* skips have to be excluded, or the backlog fills
/// with work that must never be done:
///
/// * `assert!(rlx_oneapi::is_available())` — a positive assertion that the
/// backend is always selectable. The opposite of a skip.
/// * `assert!(!rlx_runtime::is_available(Device::Egpu))` (`egpu_seam.rs`) and
/// `assert!(!rlx_tpu::is_available(), ..)` (`rlx-tpu/basic.rs`) — deliberate
/// NEGATIVE assertions that a device is absent. Turning one into a skip would
/// delete the test.
/// * `.find(|d| !is_available(*d))` (`graph_devices_parity.rs`) — *searches
/// for* an unavailable device so the fallback path runs on every host.
/// Inverting it would invert the test's premise.
///
/// So a skip is: a **negated** availability call **in an `if` condition**.
/// Assertions and closure predicates do not qualify. That is narrow enough to
/// exclude all three above and wide enough to catch every real skip left.
fn gates_on_availability(src: &str) -> bool {
src.lines().any(|line| {
let t = line.trim_start();
if !(t.starts_with("if ") || t.starts_with("} else if ") || t.starts_with("&& ")) {
return false;
}
["is_available(", "is_supported("].iter().any(|probe| {
let mut from = 0usize;
while let Some(hit) = line[from..].find(probe) {
let at = from + hit;
let head = line[..at]
.trim_end_matches(|c: char| c.is_alphanumeric() || c == '_' || c == ':');
if head.trim_end().ends_with('!') {
return true;
}
from = at + probe.len();
}
false
})
})
}
/// Does it route that decision through something `RLX_REQUIRE_DEVICE` sees?
///
/// Matching the *name* alone is not enough, and this gate was fooled by exactly
/// that: `activation_batch_parity.rs` defined its own local
/// `fn skip_unless_available` — same name, same signature, no
/// `RLX_REQUIRE_DEVICE` assert — so a file exempt from the flag counted as
/// migrated. A local definition now disqualifies the file; only a call into the
/// shared helper counts.
fn uses_the_helper(src: &str) -> bool {
if src.contains("fn skip_unless_available") || src.contains("fn skip_unless_device") {
return false;
}
src.contains("skip_unless_available") || src.contains("skip_unless_device")
}
fn scan(dir: &Path) -> Vec<String> {
let Ok(entries) = std::fs::read_dir(dir) else {
// A backend crate absent from this checkout is not a finding.
return Vec::new();
};
let mut out = Vec::new();
for e in entries.flatten() {
let p = e.path();
if p.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
// This file quotes every pattern it looks for — in the doc comment and
// in `uses_the_helper`'s own body — so it matches itself. Scanning a
// scanner is a category error, not a finding.
if p.file_name().and_then(|s| s.to_str()) == Some("require_device_coverage.rs") {
continue;
}
let Ok(src) = std::fs::read_to_string(&p) else {
continue;
};
if gates_on_availability(&src) && !uses_the_helper(&src) {
let name = p
.strip_prefix(dir)
.unwrap_or(&p)
.to_string_lossy()
.into_owned();
let crate_dir = dir
.parent()
.and_then(|d| d.file_name())
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
out.push(format!("{crate_dir}/tests/{name}"));
}
}
out
}
#[test]
fn every_device_gated_test_routes_through_the_helper() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let dirs = test_dirs(&root);
let mut unmigrated: Vec<String> = dirs.iter().flat_map(|d| scan(d)).collect();
unmigrated.sort();
eprintln!(
"scanned {} test directory/ies; {} file(s) gate on device availability \
without the shared helper",
dirs.len(),
unmigrated.len()
);
assert_eq!(
unmigrated.len(),
PINNED_UNMIGRATED,
"these test file(s) gate on device availability with a raw \
`if !is_available(..) {{ return }}`, which reports `ok` on a rig with no \
device:\n {}\n\nRoute the skip through `common::skip_unless(dev)` (label \
derived), `common::skip_unless_available(dev, label)`, or — in a backend \
crate, which cannot depend on rlx-runtime — \
`rlx_ir::env::skip_unless_device(label, compiled, available)`.\n\nIf the \
call is NOT a skip (a negative assertion that a device is absent, or a \
search FOR an unavailable device), it should not be in an `if` condition \
reachable by `gates_on_availability` — check that function before \
changing this number.",
unmigrated.join("\n ")
);
}
/// **A test that drives a GPU device must serialize against the others.**
///
/// The adapters in this tree are not safe to init/teardown from several test
/// threads at once. `common::GpuTestGuard` is the mechanism; the failure when a
/// file skips it is not a crash but *wrong numbers* — `elementwise_backend_parity`
/// returned `worst_rel=1.0` on Vulkan about one run in six, and
/// `vulkan_parity::fma_decomposed` did the same, both clean at
/// `--test-threads=1`. A flake that rare reads as "someone else's problem" for a
/// long time.
///
/// So the same ratchet as [`every_device_gated_test_routes_through_the_helper`]:
/// a file that constructs a `Session` on a non-CPU device and never takes the
/// guard fails here. It is pinned at 0 — the sweep that fixed the two flakes
/// covered every file in this directory.
///
/// CPU-only files are exempt: there is no device to contend for.
#[test]
fn gpu_tests_serialize_device_access() {
const PINNED_UNGUARDED: usize = 0;
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut unguarded: Vec<String> = Vec::new();
let Ok(entries) = std::fs::read_dir(root.join("tests")) else {
panic!("tests/ unreadable");
};
for e in entries.flatten() {
let p = e.path();
if p.extension().and_then(|s| s.to_str()) != Some("rs") {
continue;
}
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("");
// This file and the coverage/floor gates construct no GPU session.
if name == "require_device_coverage.rs" {
continue;
}
let Ok(src) = std::fs::read_to_string(&p) else {
continue;
};
if !src.contains("Session::new") {
continue;
}
// Does it name a GPU device at all?
let gpu = [
"Device::Metal",
"Device::Gpu",
"Device::Cuda",
"Device::Rocm",
"Device::Mlx",
"Device::Vulkan",
"Device::Ane",
]
.iter()
.any(|d| src.contains(d));
if !gpu {
continue;
}
if src.contains("GpuTestGuard") || src.contains("serialize_gpu") {
continue;
}
unguarded.push(name.to_string());
}
unguarded.sort();
assert_eq!(
unguarded.len(),
PINNED_UNGUARDED,
"these test file(s) build a `Session` on a GPU device without taking \
`common::GpuTestGuard`, so they race every other GPU test in their \
binary:\n {}\n\nAdd `let _gpu = common::serialize_gpu();` as the first \
statement of each `#[test]`. The guard is re-entrant, so a helper that \
also acquires costs nothing.",
unguarded.join("\n ")
);
}