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

nordisk/facett

facett-binview/tests/robot.rs

//! **Robot binary-analysis scenario for the shared pane** (`egui_kittest`).
//!
//! Renders the REAL [`facett_binview::BinaryPane`] body over the stub
//! [`BinaryDataSeam`] and asserts, off the live AccessKit tree + the pane's
//! `state_json`, that every render layer reaches the surface a robot (or a screen
//! reader) sees:
//! 1. **Checksec badges** — a hardened protection (PIE ✓) and an unhardened one
//!    (CANARY ✗) both paint as real, colour-coded chips.
//! 2. **Bloat treemap** — the layout is non-blank (the tiles cover most of the map)
//!    and the biggest crate (`nornir`) gets the biggest rect.
//! 3. **Diff waterfall** — the before→after totals + a growth AND a shrink section
//!    both reach the render (asserted as data), and the summary line paints.
//! 4. No crash: the whole pane body runs through the harness without panicking.
//!
//! This is the L4 UI proof for the suite's binary-analysis surface: dwarves/nornir
//! delete any bespoke bloat/checksec body and mount THIS one (implementing
//! `BinaryDataSeam` over the real warehouse), so this test guards theirs.
//!
//! RED-when-broken (verified by neutralizing): if the checksec badges, the treemap
//! layout, or the diff growth/shrink regressed, an assertion goes red. One matrix
//! cell `facett-binview/binary-pane`.

use std::cell::RefCell;
use std::rc::Rc;

use egui::{Pos2, Rect, Vec2};
use egui_kittest::kittest::Queryable;
use egui_kittest::Harness;
use facett_binview::{BinaryPane, Facet, StubBinaryData};

/// Emit shim (real row with `--features testmatrix`, no-op otherwise).
#[cfg(feature = "testmatrix")]
fn emit(check: &str, ok: bool, detail: &str) {
    nornir_testmatrix::functional_status("facett-binview", check, ok, detail);
}
#[cfg(not(feature = "testmatrix"))]
fn emit(_check: &str, _ok: bool, _detail: &str) {}

#[test]
fn binary_pane_renders_all_layers() {
    let pane = Rc::new(RefCell::new(BinaryPane::new(Box::new(StubBinaryData))));

    let mut harness = Harness::builder().with_size(egui::vec2(520.0, 720.0)).build_ui({
        let pane = Rc::clone(&pane);
        move |ui| {
            egui::ScrollArea::vertical().show(ui, |ui| {
                pane.borrow_mut().ui(ui);
            });
        }
    });

    // ≥1 = present (a label can ride in as multiple nodes).
    let seen = |h: &Harness, label: &str| h.query_all_by_label(label).count() >= 1;

    // Run a frame — proves the whole body paints without panicking (no crash).
    harness.run();

    // (1) Checksec badges: a hardened + an unhardened protection both paint.
    let pie_seen = seen(&harness, "PIE ✓");
    let canary_seen = seen(&harness, "CANARY ✗");
    assert!(pie_seen, "the PIE ✓ (hardened, green) badge paints");
    assert!(canary_seen, "the CANARY ✗ (unhardened, red) badge paints");

    // (2) Treemap: non-blank + biggest crate biggest rect (asserted off the exact
    //     layout the pane paints).
    let guard = pane.borrow_mut();
    let tiles = guard.crate_treemap(Rect::from_min_size(Pos2::ZERO, Vec2::new(400.0, 200.0)));
    assert!(!tiles.is_empty(), "the treemap laid out tiles");
    let covered: f32 = tiles.iter().map(|t| t.rect.width() * t.rect.height()).sum();
    let treemap_nonblank = covered > 400.0 * 200.0 * 0.7;
    assert!(treemap_nonblank, "the treemap is non-blank (tiles cover the map)");
    let biggest = tiles.iter().max_by_key(|t| t.bytes).unwrap();
    let widest = tiles
        .iter()
        .max_by(|a, b| {
            (a.rect.width() * a.rect.height())
                .partial_cmp(&(b.rect.width() * b.rect.height()))
                .unwrap()
        })
        .unwrap();
    let biggest_wins = biggest.crate_name == widest.crate_name && biggest.crate_name == "nornir";
    assert!(biggest_wins, "the biggest crate (nornir) gets the biggest rect");

    // (3) Diff waterfall: a growth AND a shrink section both reach the render.
    let s = guard.state_json();
    let secs = s["diff"]["sections"].as_array().unwrap();
    let has_growth = secs.iter().any(|d| d["delta"].as_i64().unwrap() > 0);
    let has_shrink = secs.iter().any(|d| d["delta"].as_i64().unwrap() < 0);
    assert!(has_growth && has_shrink, "the diff waterfall shows a growth and a shrink");
    let checksec_ok = s["checksec"]
        .as_array()
        .unwrap()
        .iter()
        .any(|b| b["label"] == "PIE" && b["ok"] == true)
        && s["checksec"]
            .as_array()
            .unwrap()
            .iter()
            .any(|b| b["label"] == "CANARY" && b["ok"] == false);
    drop(guard);

    // The before→after summary line paints too.
    let summary_seen = seen(&harness, "ABI changed");

    let ok = pie_seen
        && canary_seen
        && treemap_nonblank
        && biggest_wins
        && has_growth
        && has_shrink
        && checksec_ok
        && summary_seen;
    emit(
        "binary-pane",
        ok,
        &format!(
            "pie={pie_seen} canary={canary_seen} treemap_nonblank={treemap_nonblank} \
             biggest_wins={biggest_wins} growth={has_growth} shrink={has_shrink} \
             checksec={checksec_ok} summary={summary_seen}"
        ),
    );
    assert!(ok, "every binary-analysis layer reached the render");
}