xtask/tests/wasm_bundle_has_gpu_features.rs
//! **Row 3 — the wasm bundle must ship the GPU lanes it is built to run on.**
//!
//! LAW 4 says WebGPU everywhere, and `facet-wrapped` already runs eframe on the wgpu
//! backend with `Backends::BROWSER_WEBGPU`. But until this row, the bundle pulled
//! `facett-map3d`, `facett-graph3d` and `facett-graphview` on their DEFAULT features —
//! which means their GPU lanes were compiled OUT. The result was CPU painters
//! projecting, culling and lighting on the main thread, *behind* a WebGPU surface: the
//! page reported `webgpu` truthfully and drew every triangle in software.
//!
//! That is the exact shape of a green nobody has seen red. `facet_backend()` returns
//! the constant `"webgpu"`, and `facet_canvas_api()` asks the canvas which context it
//! holds — both were and are `webgpu`, and neither can tell you whether the 3D map's
//! GPU renderer exists in the binary at all.
//!
//! So this test asserts the DEPENDENCY EDGES, read out of cargo's own manifest parse
//! rather than by grepping the TOML text. A comment claiming the feature is on is not
//! the feature being on.
use std::process::Command;
/// The three edges this row turns on, and the feature each one needs.
///
/// `facett-graphview` spells it `gpu` and the other two spell it `wgpu` — the names
/// differ in the crates themselves, so they are listed here rather than derived.
const REQUIRED: [(&str, &str); 7] = [
("facett-flow", "wgpu"),
("facett-pipeline", "wgpu"),
("facett-plandag", "wgpu"),
("facett-brokkrview", "wgpu"),
("facett-map3d", "wgpu"),
("facett-graph3d", "wgpu"),
("facett-graphview", "gpu"),
];
/// `cargo metadata --no-deps`, so this reads the DECLARED edges of the workspace's own
/// packages and never resolves the whole graph (fast, and unaffected by feature
/// unification across other members).
fn metadata() -> serde_json::Value {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let out = Command::new("cargo")
.current_dir(root)
.args(["metadata", "--no-deps", "--format-version", "1"])
.output()
.expect("run cargo metadata");
assert!(out.status.success(), "cargo metadata failed: {}", String::from_utf8_lossy(&out.stderr));
serde_json::from_slice(&out.stdout).expect("cargo metadata emits JSON")
}
fn facet_wrapped_deps(md: &serde_json::Value) -> Vec<serde_json::Value> {
md["packages"]
.as_array()
.expect("packages")
.iter()
.find(|p| p["name"] == "facet-wrapped")
.expect("facet-wrapped is a workspace member")["dependencies"]
.as_array()
.expect("dependencies")
.clone()
}
#[test]
fn the_bundle_enables_every_gpu_lane_it_wraps() {
let md = metadata();
let deps = facet_wrapped_deps(&md);
let mut missing = Vec::new();
for (crate_name, feature) in REQUIRED {
let dep = deps
.iter()
.find(|d| d["name"] == crate_name)
.unwrap_or_else(|| panic!("facet-wrapped does not depend on {crate_name} at all"));
let feats: Vec<&str> =
dep["features"].as_array().map(|a| a.iter().filter_map(|f| f.as_str()).collect())
.unwrap_or_default();
if !feats.contains(&feature) {
missing.push(format!("{crate_name}/{feature} (declared features: {feats:?})"));
}
}
assert!(
missing.is_empty(),
"the wasm bundle wraps these components with their GPU lane compiled OUT: {missing:?}\n\
The page would report `webgpu` for the canvas and still draw every triangle on \
the CPU — a truthful backend string in front of a software renderer."
);
}
#[test]
fn the_gpu_lanes_are_not_turned_on_by_accident_of_default_features() {
// `default-features` must stay ON for these three: their defaults carry the CPU
// painter, which is the reference path and the snapshot golden path. This row adds
// the GPU lane, it does not replace the CPU one — a bundle with only the GPU lane
// has nothing to fall back to when `requestAdapter()` returns null.
let md = metadata();
for (crate_name, _) in REQUIRED {
let deps = facet_wrapped_deps(&md);
let dep = deps.iter().find(|d| d["name"] == crate_name).unwrap();
assert_ne!(
dep["uses_default_features"], serde_json::Value::Bool(false),
"{crate_name} has default-features off — the CPU painter is the reference \
path and the fallback, and this row must not remove it"
);
}
}
#[test]
fn the_comment_beside_the_edge_no_longer_claims_the_lane_is_off() {
// The manifest carried a comment stating, in so many words, that map3d "rides its
// DEFAULT features on purpose: the `wgpu` feature is off". Leaving that in place
// while turning the feature on would leave the next reader with a file that
// contradicts itself — and comments are what people read before code.
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let toml = std::fs::read_to_string(root.join("facet-wrapped/Cargo.toml")).unwrap();
// NORMALISED first, and that is not a nicety: this test was GREEN on the
// unchanged manifest because the phrase is line-wrapped across a `#` comment
// continuation —
//
// # `facett-map3d` rides its DEFAULT features on purpose: the `wgpu`
// # feature is off, so the 3D map projects + back-face-culls …
//
// so the substring "the `wgpu` feature is off" does not occur anywhere in the
// bytes. A guard that passes because of where a line happens to wrap is a guard
// that would have passed on the very file it exists to catch.
let flat = toml
.lines()
.map(|l| l.trim_start().trim_start_matches('#').trim())
.collect::<Vec<_>>()
.join(" ");
assert!(
!flat.contains("the `wgpu` feature is off"),
"facet-wrapped/Cargo.toml still says the wgpu feature is off while enabling it"
);
}