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");
}