tests/viz_metro_robot.rs
//! 🚇 **Metro robot-walk (LIVE render)** — drive the REAL 🚇 Metro tab through a
//! kittest [`Harness`] (the same `draw_ui` tick the live window runs), so the facett
//! [`facett_graphview::MetroView`] is ACTUALLY painted, then prove from the rendered
//! AccessKit tree + the live `state_json` that the tab shows REAL data.
//!
//! The prior `viz_arch_metro.rs` proofs assert `state_json` slices WITHOUT ever
//! rendering the pane. This file closes that gap: it walks the LIVE pane (LAW
//! "verify UI live, not isolated-green") and asserts the populated-state invariant
//! `metro_edges > 0 ∧ metro_stub_lines == 0 ∧ metro_emitter_nodes > 0` (sannr) on a
//! populated warehouse, and that an EMPTY warehouse goes RED through the SAME live
//! render (sensitivity / RAGNARÖK). It also reads the facett MetroView's drawn
//! header out of the AccessKit tree (`N line(s)` / `✓ G green` / `no lines`) so the
//! assertion sees what the user sees, not just an internal slice.
//!
//! Run: `cargo test --features viz --test viz_metro_robot -- --test-threads=1`
#![cfg(feature = "viz")]
use egui_kittest::Harness;
use nornir::knowledge::symbols::{CallEdgeRow, SymbolRow};
use nornir::viz::UrdrThreadsApp;
use nornir::viz::control::VizCommand;
use nornir_robotui::atom_labels;
const MEMBERS: [&str; 1] = ["nornir"];
fn sym(module: &str, name: &str, kind: &str, file: &str, line: u32) -> SymbolRow {
SymbolRow {
crate_name: "nornir".into(),
module_path: module.into(),
item_kind: kind.into(),
item_name: name.into(),
visibility: "pub".into(),
file: file.into(),
line,
doc_lines: 0,
signature: None,
}
}
fn call(caller: &str, callee: &str, file: &str, line: u32) -> CallEdgeRow {
CallEdgeRow {
crate_name: "nornir".into(),
caller_path: caller.into(),
callee_ident: callee.into(),
call_kind: "call".into(),
file: file.into(),
line,
}
}
/// The same realistic `run_release_gate → Ops.RunRelease` slice the arch-metro
/// proofs use: the `draw_release_op` UI click-handler fires an emitter, then the RPC
/// client fn, which reaches the server's `OpsSvc::run_release` gRPC handler.
fn fixture_symbols() -> Vec<SymbolRow> {
vec![
sym(
"nornir::viz::release_tab::ReleaseTabState",
"draw_release_op",
"fn",
"src/viz/release_tab.rs",
207,
),
sym(
"nornir::viz::trace",
"emit_in",
"fn",
"src/viz/trace.rs",
90,
),
sym(
"nornir::viz::remote",
"run_release_rpc",
"fn",
"src/viz/remote.rs",
300,
),
sym(
"nornir_server",
"impl OpsSvcTrait for OpsSvc",
"impl",
"src/bin/nornir-server.rs",
3243,
),
sym(
"nornir_server::OpsSvc",
"run_release",
"fn",
"src/bin/nornir-server.rs",
3244,
),
]
}
fn fixture_calls() -> Vec<CallEdgeRow> {
vec![
call(
"nornir::viz::release_tab::ReleaseTabState::draw_release_op",
"emit_in",
"src/viz/release_tab.rs",
210,
),
call(
"nornir::viz::trace::emit_in",
"run_release_rpc",
"src/viz/trace.rs",
92,
),
call(
"nornir::viz::remote::run_release_rpc",
"OpsSvc::run_release",
"src/viz/remote.rs",
305,
),
]
}
/// A real local (fat) app with an injected scan — `with_repos` opens local mode so
/// `facett_metro_map()` uses the injected `symbols`/`calls` (no second checkout
/// scan: `ensure_symbols` early-returns when `self.symbols.is_some()`).
fn app_with(symbols: Vec<SymbolRow>, calls: Vec<CallEdgeRow>) -> UrdrThreadsApp {
let pid = std::process::id();
let nonce = uuid::Uuid::new_v4();
let dir = std::env::temp_dir();
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe {
std::env::set_var(
"NORNIR_VIZ_TRACE",
dir.join(format!("nornir-metro-robot-trace-{pid}-{nonce}.jsonl")),
)
};
let wh = dir.join(format!("nornir-metro-robot-wh-{pid}-{nonce}"));
let _ = std::fs::create_dir_all(&wh);
let checkout = dir.join(format!("nornir-metro-robot-co-{pid}-{nonce}"));
let _ = std::fs::create_dir_all(&checkout);
let mut app = UrdrThreadsApp::with_repos(
wh,
"nornir".to_string(),
checkout,
MEMBERS.iter().map(|s| s.to_string()).collect(),
);
app.inject_arch_scan_for_test(symbols, calls);
app.arch_select_metro_for_test("run_release_gate");
app
}
/// Host the real viz in a kittest Harness (the LIVE `draw_ui` tick), drive to the
/// 🚇 Metro tab, and pump a few frames so the facett `MetroView` actually paints
/// the pushed map.
fn render_metro_tab(app: UrdrThreadsApp) -> Harness<'static, UrdrThreadsApp> {
let mut harness: Harness<'_, UrdrThreadsApp> = Harness::builder()
.with_size(egui::Vec2::new(1400.0, 900.0))
.build_ui_state(|ui, app: &mut UrdrThreadsApp| app.draw_ui(ui), app);
harness.state_mut().apply_command(&VizCommand {
tab: Some("Metro".into()),
..Default::default()
});
for _ in 0..4 {
let _ = harness.run_ok();
}
harness
}
/// SANNR — the LIVE Metro tab, fed an injected POPULATED warehouse, renders REAL
/// button → gRPC lines. Proven THREE ways from one render:
/// 1. the rendered AccessKit tree carries the facett header (`N line(s)`/`green`),
/// NOT the `no lines` empty-hint — what the USER sees;
/// 2. `state_json["architecture"]["metro"]` carries the populated invariant
/// `metro_edges > 0 ∧ metro_stub_lines == 0 ∧ metro_emitter_nodes > 0`;
/// 3. the facett `state_json["metro"]` (the drawn `MetroView`) reports `lines >= 1`
/// with a green line — the pushed map reached the view.
#[test]
fn metro_tab_live_render_populated_shows_real_lines() {
let harness = render_metro_tab(app_with(fixture_symbols(), fixture_calls()));
// (1) The drawn facett header is in the live AccessKit tree (NOT "no lines").
let labels = atom_labels(&harness);
let has_line_count = labels.iter().any(|l| l.contains("line(s)"));
let has_no_lines = labels.iter().any(|l| l.contains("no lines"));
assert!(
has_line_count,
"the rendered Metro pane shows a line count: {labels:?}"
);
assert!(
!has_no_lines,
"a populated metro must NOT render the empty hint: {labels:?}"
);
let state = harness.state().state_json();
// (2) The native metro populated-state invariant (RAGNARÖK), from the LIVE app.
let metro = &state["architecture"]["metro"];
let edges = metro["metro_edges"].as_u64().unwrap();
let nodes = metro["metro_nodes"].as_u64().unwrap();
let emitter_nodes = metro["metro_emitter_nodes"].as_u64().unwrap();
let stub_lines = metro["metro_stub_lines"].as_u64().unwrap();
let populated = edges > 0 && stub_lines == 0 && emitter_nodes > 0;
nornir_testmatrix::functional_status(
"viz-arch-metro",
"live_render_metro_tab_populated_not_stub",
populated,
&format!(
"metro_edges={edges} metro_nodes={nodes} metro_emitter_nodes={emitter_nodes} metro_stub_lines={stub_lines}"
),
);
assert!(edges > 0, "live metro draws call-path edges: {metro}");
assert_eq!(
stub_lines, 0,
"no resolvable line degraded to a head-only stub: {metro}"
);
assert!(
emitter_nodes > 0,
"emitter stations MARKED on the lines: {metro}"
);
assert!(
nodes > edges,
"nodes(stations) > edges(rail segments): {metro}"
);
assert_eq!(
metro["selected"]["reached"], true,
"the selected line reaches gRPC: {metro}"
);
assert_eq!(metro["selected"]["grpc_terminus"], "run_release");
// (3) The facett MetroView (the thing actually painted) got the map.
let view = &state["metro"];
assert_eq!(
view["fed"],
serde_json::json!(true),
"the MetroView was fed a map: {view}"
);
let view_lines = view["lines"].as_u64().unwrap();
assert!(view_lines >= 1, "the painted view carries lines: {view}");
let any_green = view["line"].as_array().unwrap().iter().any(|l| {
l["green"] == serde_json::json!(true) || l["station_count"].as_u64().unwrap_or(0) >= 3
});
assert!(
any_green,
"a real (multi-station) line reached the painted view: {view}"
);
nornir_testmatrix::functional_status(
"viz-arch-metro",
"live_render_metroview_painted_real_lines",
has_line_count && !has_no_lines && view_lines >= 1,
&format!(
"view_lines={view_lines} has_line_count={has_line_count} has_no_lines={has_no_lines}"
),
);
}
/// SENSITIVITY (RAGNARÖK inverse) — the SAME live render, but the injected warehouse
/// has symbols and NO `call_edges` (the unpopulated-warehouse state). Every
/// resolvable line degrades to a head-only stub, so the populated invariant the
/// test above asserts TRUE must be FALSE here, AND the painted view goes RED (`0
/// green` in the rendered header). A blank metro can never go green.
#[test]
fn metro_tab_live_render_empty_warehouse_is_red() {
let harness = render_metro_tab(app_with(fixture_symbols(), Vec::new())); // NO call edges
let labels = atom_labels(&harness);
let state = harness.state().state_json();
let metro = &state["architecture"]["metro"];
let edges = metro["metro_edges"].as_u64().unwrap();
let emitter_nodes = metro["metro_emitter_nodes"].as_u64().unwrap();
let stub_lines = metro["metro_stub_lines"].as_u64().unwrap();
let populated = edges > 0 && stub_lines == 0 && emitter_nodes > 0;
nornir_testmatrix::functional_status(
"viz-arch-metro",
"live_render_empty_warehouse_is_red",
!populated, // a RED metro is the CORRECT (passing) outcome for empty input
&format!(
"metro_edges={edges} metro_emitter_nodes={emitter_nodes} metro_stub_lines={stub_lines}"
),
);
assert!(
!populated,
"empty warehouse MUST render a RED metro, never a silent green: {metro}"
);
assert_eq!(edges, 0, "no call edges → no rail segments drawn");
assert_eq!(emitter_nodes, 0, "no emitter stations on stub lines");
assert!(
stub_lines >= 1,
"a resolvable line degraded to a head-only stub: {metro}"
);
// The painted view goes RED: NO green line, and the rendered header reports it.
let view = &state["metro"];
assert_eq!(
view["green"],
serde_json::json!(false),
"the painted view is not all-green: {view}"
);
// The facett header line `✓ G green` shows zero greens for a fully-stub map
// (head-only lines are never green). Read it from the live tree.
let zero_green_shown = labels.iter().any(|l| l.contains("✓ 0 green"));
assert!(
zero_green_shown || view["green_lines"] == serde_json::json!(0),
"the rendered metro shows zero green lines for an empty warehouse: labels={labels:?} view={view}"
);
}