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

nordisk/nornir

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