Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/knut

crates/knut-popviz/tests/law4_no_opengl_backend.rs

//! **LAW #4 — the shipped desktop GUI renders on WebGPU, never OpenGL.**
//!
//! The nordisk rule is "modern platforms only: WebGPU everywhere, no WebGL/glow". This
//! crate broke it *silently* and for a long time, in a way no compiler and no runtime
//! check could catch:
//!
//! * `Cargo.toml` listed `eframe = { features = ["glow", …] }` as a REAL (non-dev)
//!   dependency behind `gui`;
//! * `main.rs` passes a bare `eframe::NativeOptions::default()`, and eframe resolves
//!   `Renderer::default()` to **`Glow`** whenever its `glow` feature is enabled
//!   (eframe 0.34.3, `epi.rs`).
//!
//! So the backend was chosen by a feature flag in a manifest, the source said nothing
//! about it, and `glow v0.17` was linked into the shipped binary twice — directly and
//! through `egui_glow`. Nothing in the tree stated which renderer shipped, so nothing
//! could disagree with it.
//!
//! This test is the missing statement. It reads the manifests — the things that actually
//! decide — and asserts no non-comment line in any of them so much as names `glow`, and
//! that every `eframe` declaration asks for `wgpu` explicitly.
//!
//! Manifest-reading is the point: an `eframe::Renderer` assertion would need a window, and
//! a dependency-*tree* assertion would trip over `wgpu-hal`'s internal GLES backend, which
//! is a legitimately different thing (`glow` reachable *under* `wgpu-hal` is not a
//! violation; `glow` reachable via `eframe`/`egui_glow` is). Sibling guards of the same
//! shape live in `korp/tests/`, `nornir/tests/` and `holger/server/ui/tests/`.
//!
//! # Two holes this file used to have, both measured on 2026-08-01
//!
//! The first version of this guard walked `crates/*/Cargo.toml` — one level — and checked
//! only the `eframe` line. Two mutations proved it blind while glow was genuinely present:
//!
//! 1. **Location.** `eframe = { features = ["glow"] }` appended to `xtask/Cargo.toml` —
//!    outside `crates/` — and the guard reported `ok`. knut's root manifest, `xtask/`, the
//!    vendored tree, and the workspace-`exclude`d `crates/knut-bifrost/wasm-guest` were all
//!    unreachable by that walk, and the file's own doc claimed it "covers the WHOLE
//!    workspace".
//! 2. **Vector.** `glow = "0.17"` plus `egui_glow` added straight to knut-popviz's
//!    `[dependencies]`, and the guard reported `ok` in the same cargo run that printed
//!    `Compiling glow v0.17.0` — glow linked, guard green. Checking one dependency's
//!    feature list can only ever catch one of the ways glow gets in.
//!
//! Both are closed by construction rather than by two more special cases (LAW #5): ONE
//! tree walk that reaches every `Cargo.toml` in the repo, and ONE rule — **the substring
//! `glow` may not appear in any non-comment line of any manifest.** That is airtight
//! against every reintroduction vector at once: a feature (`features = ["glow"]`), a
//! direct dep (`glow = "0.17"`), a wrapper (`egui_glow`), a table header
//! (`[dependencies.glow]`), a forwarded feature (`gui = ["eframe/glow"]`) and a
//! target-gated duplicate of any of them. Comments are excluded, so the prose ABOUT glow
//! in knut's manifests can neither satisfy nor trip the guard.
//!
//! RED when someone re-adds `glow` anywhere, or drops `wgpu`, in any manifest in the repo.

use std::path::PathBuf;

/// The repo root — the directory whose `Cargo.toml` declares `[workspace]`.
///
/// `CARGO_MANIFEST_DIR` is set by cargo, so this works from any working directory, any
/// worktree and any workspace layout.
fn repo_root() -> PathBuf {
    let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    while !std::fs::read_to_string(root.join("Cargo.toml"))
        .is_ok_and(|s| s.lines().any(|l| l.trim() == "[workspace]"))
    {
        assert!(
            root.pop(),
            "walked past the filesystem root without finding [workspace]"
        );
    }
    root
}

/// Every `Cargo.toml` in the repo, as `(path relative to root, contents)`.
///
/// This walks the whole tree rather than the `[workspace] members` list, and that is
/// deliberate twice over. knut's root manifest **excludes**
/// `crates/knut-bifrost/wasm-guest` (a self-rooting wasm32 crate), so a members-driven
/// walk would skip a real crate; and the previous `crates/*` walk skipped the root
/// manifest and `xtask/` entirely, which is measured hole #1 above. Walking the tree means
/// the next manifest to declare a GUI stack is covered the moment it lands, by
/// construction, wherever it lands.
fn all_manifests() -> Vec<(String, String)> {
    let root = repo_root();
    let mut out = Vec::new();
    let mut stack = vec![root.clone()];

    while let Some(dir) = stack.pop() {
        let Ok(entries) = std::fs::read_dir(&dir) else {
            continue;
        };
        for e in entries.flatten() {
            let p = e.path();
            let name = e.file_name().to_string_lossy().into_owned();
            if p.is_dir() {
                // Skip build output, git internals and agent worktrees. A `target/` tree
                // holds thousands of unrelated manifests, and a `.claude/worktrees/`
                // checkout is a DIFFERENT commit of this same repo whose manifest must not
                // be able to fail (or rescue) this guard. `vendor/` is NOT skipped: it is
                // compiled into knut's binaries by path, so glow arriving there would be a
                // real violation of the shipped tree.
                if matches!(
                    name.as_str(),
                    "target" | ".git" | ".claude" | "node_modules"
                ) {
                    continue;
                }
                stack.push(p);
            } else if name == "Cargo.toml" {
                if let Ok(src) = std::fs::read_to_string(&p) {
                    let rel = p
                        .strip_prefix(&root)
                        .unwrap_or(&p)
                        .to_string_lossy()
                        .into_owned();
                    out.push((rel, src));
                }
            }
        }
    }

    out.sort();
    // Non-vacuity floor: if the walk finds nothing, every loop over it passes having
    // inspected nothing — precisely the silent green this file exists to prevent. knut has
    // 17 manifests today (15 workspace members + the excluded wasm-guest + the vendored
    // spark tree).
    assert!(
        out.len() >= 15,
        "found only {} Cargo.toml files under {} — the walk is broken, and a broken walk \
         makes this guard pass while checking nothing. Found: {:?}",
        out.len(),
        root.display(),
        out.iter().map(|(p, _)| p).collect::<Vec<_>>()
    );
    out
}

/// The non-comment lines of a manifest — the ones that actually configure cargo.
///
/// Comment-stripping is load-bearing in both directions: knut's manifests carry paragraphs
/// of prose about why glow is forbidden, and that prose must neither trip the guard nor be
/// mistakable for a dependency declaration.
fn effective_lines(src: &str) -> Vec<&str> {
    src.lines()
        .map(str::trim)
        .filter(|l| !l.starts_with('#') && !l.is_empty())
        .collect()
}

/// Every `eframe = { … }` dependency line in a manifest.
///
/// Returns every match, not the first: a manifest can grow a second, target-gated eframe
/// line (nornir's `nornir-viz-web` has exactly that shape — one for wasm32 and one for
/// native), and a `find()` would silently check only one of them.
fn eframe_lines(src: &str) -> Vec<String> {
    effective_lines(src)
        .into_iter()
        .filter(|l| l.starts_with("eframe"))
        .map(str::to_owned)
        .collect()
}

/// Every non-comment line that names `glow` — a LAW #4 violation in any form.
fn glow_lines(src: &str) -> Vec<String> {
    effective_lines(src)
        .into_iter()
        .filter(|l| l.contains("glow"))
        .map(str::to_owned)
        .collect()
}

#[test]
fn no_manifest_in_the_repo_names_glow_outside_a_comment() {
    let manifests = all_manifests();

    // Non-vacuity for measured hole #1: the walk must reach the three places the previous
    // `crates/*` walk could not see. If it ever regresses to a one-level walk, these fail
    // immediately and by name instead of the guard quietly narrowing.
    for required in [
        "Cargo.toml",
        "xtask/Cargo.toml",
        "crates/knut-bifrost/wasm-guest/Cargo.toml",
    ] {
        assert!(
            manifests.iter().any(|(p, _)| p == required),
            "the walk did not reach {required} — that is measured hole #1 (a glow-featured \
             eframe line appended to xtask/Cargo.toml passed this guard on 2026-08-01). \
             Reached: {:?}",
            manifests.iter().map(|(p, _)| p).collect::<Vec<_>>()
        );
    }

    // Non-vacuity: count what was actually inspected. An empty or line-less read is shaped
    // exactly like a clean tree.
    let mut lines_scanned = 0usize;
    let mut offenders: Vec<String> = Vec::new();
    for (path, src) in &manifests {
        lines_scanned += effective_lines(src).len();
        for line in glow_lines(src) {
            offenders.push(format!("{path}: {line}"));
        }
    }
    assert!(
        lines_scanned > 200,
        "only {lines_scanned} non-comment manifest lines inspected across {} manifests — \
         too few to be knut's real tree, and a guard that reads nothing passes",
        manifests.len()
    );

    assert!(
        offenders.is_empty(),
        "LAW #4: no manifest in knut may name `glow` outside a comment — not as an eframe \
         feature, not as a direct dependency, not as `egui_glow`, not in a \
         `[dependencies.glow]` table and not behind a target gate. eframe resolves \
         `Renderer::default()` to `Glow` whenever its `glow` feature is on, so one word in \
         a manifest decides that the shipped desktop binary is OpenGL with nothing in the \
         source saying so; and a direct `glow` edge links a whole second GL backend into \
         the binary regardless of which renderer runs. ({lines_scanned} lines inspected.) \
         Offending lines:\n  {}",
        offenders.join("\n  ")
    );
}

#[test]
fn every_eframe_declaration_asks_for_wgpu_explicitly() {
    let mut checked: Vec<String> = Vec::new();

    for (path, src) in all_manifests() {
        for line in eframe_lines(&src) {
            checked.push(path.clone());

            assert!(
                line.contains("\"wgpu\""),
                "LAW #4: {path} must ASK for the WebGPU backend explicitly. Dropping both \
                 eframe backends is a `compile_error!` in eframe, so an eframe line naming \
                 neither cannot be right. Offending line:\n  {line}"
            );
            // `default-features = false` is what keeps the choice explicit rather than
            // inherited from whatever eframe's defaults happen to bring.
            assert!(
                line.contains("default-features = false"),
                "{path}'s eframe dep must pin `default-features = false` so the renderer is \
                 the one this line names, not whatever eframe's defaults bring. Offending \
                 line:\n  {line}"
            );
        }
    }

    // Non-vacuity: the two crates that declare eframe today are `knut-popviz` and
    // `knut-pipelines`. If this list ever comes back empty or shrinks, the loop above
    // proved nothing — which is the failure mode this whole file exists to make impossible.
    assert!(
        checked.len() >= 2,
        "expected at least the two known eframe crates (knut-popviz, knut-pipelines); \
         inspected {checked:?} — an empty or shrunken list means the detector stopped \
         finding dependency lines, not that the violations were fixed"
    );
}

/// **The guard is not blind: prove each detector fires on the shape it exists to catch.**
///
/// A manifest-reading test is exactly the kind that quietly stops working — a renamed key,
/// a reformatted table or an over-eager comment filter and it inspects nothing while still
/// reporting `ok`. Every constant below is a line that was really run against the real
/// guard on 2026-08-01, including the two shapes it passed.
#[test]
fn the_guard_itself_can_report_red() {
    // ── The original violation, verbatim from knut-popviz's own history ───────────────
    const BEFORE: &str = r#"eframe = { version = "0.34", default-features = false, features = ["glow", "wayland", "x11"], optional = true }"#;
    const AFTER: &str = r#"eframe = { version = "0.34", default-features = false, features = ["default_fonts", "wgpu", "wayland", "x11"], optional = true }"#;
    assert_eq!(
        glow_lines(BEFORE).len(),
        1,
        "the detector must see glow in the pre-fix line"
    );
    assert!(
        glow_lines(AFTER).is_empty(),
        "the post-fix line must carry no glow"
    );
    assert!(
        AFTER.contains("\"wgpu\""),
        "the post-fix line must ask for wgpu"
    );

    // korp's shape is the HARDEST, and the reason the glow check and not the wgpu check is
    // doing the work: korp's pre-fix line already asked for wgpu, so a wgpu-only guard
    // reported korp green for two months while glow was linked.
    const BOTH_BACKENDS: &str = r#"eframe = { version = "0.34", default-features = false, features = ["glow", "wgpu", "wayland", "x11"] }"#;
    assert!(
        BOTH_BACKENDS.contains("\"wgpu\"") && glow_lines(BOTH_BACKENDS).len() == 1,
        "a line asking for BOTH backends is still a violation, and only the glow check \
         catches it"
    );

    // `knut-pipelines`' pre-fix line — a DIFFERENT eframe major (0.28, pinned there by
    // egui-snarl 0.4), so the detector is proven against both shapes it had to catch.
    const PIPELINES_BEFORE: &str = r#"eframe = { version = "0.28", default-features = false, features = ["glow", "wayland", "x11"], optional = true }"#;
    assert_eq!(
        glow_lines(PIPELINES_BEFORE).len(),
        1,
        "must fire on the 0.28-line shape too"
    );
    assert_eq!(
        eframe_lines(&format!("[dependencies]\n{PIPELINES_BEFORE}\n")),
        vec![PIPELINES_BEFORE.to_owned()],
        "the extractor must pull that line intact from a manifest body"
    );

    // ── Measured hole #2: the vectors the eframe-feature check could not see ──────────
    // The first two passed the previous guard for real; `glow = "0.17"` did so in the same
    // cargo run that printed `Compiling glow v0.17.0`.
    for vector in [
        r#"glow = "0.17""#,
        r#"egui_glow = { version = "0.34", optional = true }"#,
        r#"egui-glow = "0.34""#,
        r#"[dependencies.glow]"#,
        r#"gui = ["dep:eframe", "eframe/glow"]"#,
        r#"egui = { version = "0.34", features = ["glow"] }"#,
        r#"glow.workspace = true"#,
    ] {
        assert_eq!(
            glow_lines(vector).len(),
            1,
            "the glow detector must fire on `{vector}` — every one of these links glow \
             while an eframe-feature-only guard reports ok"
        );
        // And prove it is NOT the eframe check catching them: that check is blind here.
        assert!(
            eframe_lines(vector).is_empty(),
            "`{vector}` is not an eframe line, so only the glow rule can catch it"
        );
    }

    // ── The comment filter must be doing real work in both directions ─────────────────
    let commented =
        "[dependencies]\n# eframe = { features = [\"glow\"] }\n# see glow\nserde = \"1\"\n";
    assert!(
        glow_lines(commented).is_empty(),
        "the extractor must ignore comments — otherwise the prose explaining why glow is \
         forbidden could itself trip the guard"
    );
    assert!(
        eframe_lines(commented).is_empty(),
        "a manifest whose only mention of eframe is a comment declares no eframe"
    );
    // …and the filter must not swallow real lines that merely sit under prose.
    assert_eq!(
        glow_lines("# LAW 4 forbids glow\nglow = \"0.17\"\n").len(),
        1,
        "stripping comments must not strip the declaration under them"
    );

    // ── And it is the LIVE tree being read, not only these constants ──────────────────
    let manifests = all_manifests();
    let popviz = manifests
        .iter()
        .find(|(p, _)| p == "crates/knut-popviz/Cargo.toml")
        .expect("the walk must reach knut-popviz's own manifest");
    let live = eframe_lines(&popviz.1);
    assert_eq!(
        live.len(),
        1,
        "knut-popviz declares exactly one eframe line: {live:?}"
    );
    assert!(
        live[0].contains("\"wgpu\""),
        "live manifest asks for wgpu: {}",
        live[0]
    );
    assert!(
        glow_lines(&popviz.1).is_empty(),
        "live manifest names no glow outside a comment"
    );
    // The prose about glow really is present in that file, so the comment filter above is
    // exercised against the live tree and not only against a constructed string.
    assert!(
        popviz
            .1
            .lines()
            .filter(|l| l.trim().starts_with('#') && l.contains("glow"))
            .count()
            >= 3,
        "knut-popviz's manifest documents why glow is forbidden; if that prose is gone, the \
         comment filter is no longer exercised against a real file"
    );
}