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

nordisk/nornir

tests/viz_arch_subviews.rs

//! 🏛 **Architecture tab as a CONTAINER** — inject-and-assert (LAW #1) + a wgpu
//! snapshot PROOF (LAW #4) for the live, clickable, multi-functionality system
//! diagram.
//!
//! The 🏛 Architecture tab is a container with an internal sub-view selector
//! (Deps / Live system / Wiring / Hållpunkter). This test proves, WITHOUT pixels
//! (`state_json["architecture"]`):
//!   1. the tab lists every sub-view + which is active (the container contract);
//!   2. the injected wiring board renders with the right node/edge/per-kind counts;
//!   3. a node click resolves to a real `file:line` via the shared symbol
//!      resolver (the click-to-code bridge) — asserted path + line for a known
//!      symbol, plus the inline source-preview window;
//!   4. the Hållpunkter (boundary) landmarks include known ui / gRPC / emitter
//!      functions, classified by the coverage feature's `classify` (reused).
//! Plus a wgpu golden PNG of the boundaries lens (LAW #4).
//!
//! Run: `cargo test --features viz --test viz_arch_subviews -- --nocapture`
#![cfg(feature = "viz")]

use std::path::{Path, PathBuf};

use egui_kittest::Harness;
use nornir::arch::{ArchEdge, ArchEdgeKind, ArchGraph, ArchNode, NodeKind};
use nornir::knowledge::symbols::SymbolRow;
use nornir::viz::UrdrThreadsApp;

const MEMBERS: [&str; 1] = ["nornir"];

/// The same four-layer fixture board the pane lays out.
fn fixture_board() -> ArchGraph {
    let node = |id: &str, label: &str, kind: NodeKind| ArchNode {
        id: id.into(),
        label: label.into(),
        kind,
    };
    let edge = |from: &str, to: &str, kind: ArchEdgeKind| ArchEdge {
        from: from.into(),
        to: to.into(),
        kind,
    };
    ArchGraph {
        nodes: vec![
            node(
                "component:ArchTabState",
                "ArchTabState",
                NodeKind::Component,
            ),
            node("grpc:Viz.Architecture", "Viz.Architecture", NodeKind::Grpc),
            node("corefn:nornir::arch", "nornir::arch", NodeKind::CoreFn),
            node(
                "table:architecture_wiring",
                "architecture_wiring",
                NodeKind::Table,
            ),
        ],
        edges: vec![
            edge(
                "component:ArchTabState",
                "table:architecture_wiring",
                ArchEdgeKind::Reads,
            ),
            edge(
                "component:ArchTabState",
                "corefn:nornir::arch",
                ArchEdgeKind::Calls,
            ),
            edge(
                "grpc:Viz.Architecture",
                "table:architecture_wiring",
                ArchEdgeKind::Writes,
            ),
        ],
    }
}

/// A small scanned-symbol set in a REAL temp file, so click-to-code resolves to
/// an actual `file:line` AND the inline source-preview can read the lines. The
/// `ArchTabState` symbol lives in `src/viz/arch_tab.rs`; the `state_json` symbol
/// is an emitter boundary; the `draw` symbol is a ui boundary; `architecture` in
/// the server file is a gRPC boundary.
fn fixture_symbols(file_rel: &str) -> Vec<SymbolRow> {
    let sym = |module: &str, name: &str, kind: &str, file: &str, line: u32| 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,
    };
    vec![
        sym(
            "nornir::viz::arch_tab",
            "ArchTabState",
            "struct",
            file_rel,
            3,
        ),
        sym(
            "nornir::viz::arch_tab::ArchTabState",
            "draw",
            "fn",
            "src/viz/arch_tab.rs",
            600,
        ),
        sym(
            "nornir::viz::arch_tab::ArchTabState",
            "state_json",
            "fn",
            "src/viz/arch_tab.rs",
            1100,
        ),
        sym(
            "nornir_server::viz",
            "architecture",
            "fn",
            "src/bin/nornir-server.rs",
            1200,
        ),
    ]
}

/// Build a harness over a fresh app with a real checkout root containing the
/// preview file, inject the board + symbols, drive to the Architecture tab.
fn harness(tag: &str) -> (Harness<'static, UrdrThreadsApp>, PathBuf) {
    let pid = std::process::id();
    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_CMD",
            dir.join(format!("nornir-archsv-cmd-{tag}-{pid}.json")),
        )
    };
    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe {
        std::env::set_var(
            "NORNIR_VIZ_STATE",
            dir.join(format!("nornir-archsv-state-{tag}-{pid}.json")),
        )
    };
    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };

    let warehouse_root = dir.join(format!("nornir-archsv-wh-{tag}-{pid}"));
    let _ = std::fs::create_dir_all(&warehouse_root);
    // A real checkout dir with the preview source file (so source_window reads it).
    let checkout = dir.join(format!("nornir-archsv-co-{tag}-{pid}"));
    let _ = std::fs::create_dir_all(checkout.join("nornir/src/viz"));
    let file_rel = "src/viz/arch_tab.rs";
    std::fs::write(
        checkout.join("nornir").join(file_rel),
        "// line 1\n// line 2\npub struct ArchTabState {\n    field: u8,\n}\n// line 6\n",
    )
    .unwrap();

    let mut app = UrdrThreadsApp::with_repos(
        warehouse_root,
        "nornir".to_string(),
        checkout,
        MEMBERS.iter().map(|s| s.to_string()).collect(),
    );
    app.inject_arch_for_test(
        fixture_board(),
        MEMBERS.iter().map(|s| s.to_string()).collect(),
    );
    app.inject_arch_symbols_for_test(fixture_symbols(file_rel));

    let h = Harness::builder()
        .with_size(egui::Vec2::new(1400.0, 900.0))
        .wgpu()
        .build_ui_state(|ui, app: &mut UrdrThreadsApp| app.draw_ui(ui), app);
    (h, PathBuf::from(file_rel))
}

/// (a) The container lists all sub-views; (b) the board renders with the right
/// counts.
#[test]
fn arch_container_lists_subviews_and_renders_board() {
    let (mut h, _) = harness("subviews");
    for _ in 0..6 {
        h.run_ok();
    }
    let state = h.state().state_json();
    let arch = &state["architecture"];

    // (a) every sub-view is enumerable + the default active one is reported.
    let subs: Vec<&str> = arch["sub_views"]
        .as_array()
        .unwrap()
        .iter()
        .map(|v| v.as_str().unwrap())
        .collect();
    assert_eq!(
        subs,
        vec!["Deps", "Live", "Wiring", "Boundaries", "CallGraph", "Metro"],
        "all six lenses listed (CallGraph restored as its own lens, separate from Metro)"
    );
    assert!(
        arch["sub_view"].as_str().is_some(),
        "an active sub-view is reported"
    );

    // (b) the injected board's counts.
    assert_eq!(arch["node_count"].as_u64(), Some(4));
    assert_eq!(arch["edge_count"].as_u64(), Some(3));
    assert_eq!(arch["components"].as_u64(), Some(1));
    assert_eq!(arch["grpc"].as_u64(), Some(1));
    assert_eq!(arch["tables"].as_u64(), Some(1));
}

/// (c) Clicking a node resolves to a real `file:line` via the shared resolver,
/// readable back in `state_json["architecture"]["last_open"]` (no pixels).
#[test]
fn arch_click_resolves_symbol_to_file_line() {
    let (mut h, file_rel) = harness("click");
    for _ in 0..3 {
        h.run_ok();
    }
    // Drive a click-to-code on the ArchTabState component (best-effort editor:
    // `code` may be absent in CI — preview + resolution are the asserted parts).
    let found = h.state_mut().arch_open_symbol_for_test("ArchTabState");
    assert!(found, "the symbol resolved to a location");
    for _ in 0..3 {
        h.run_ok();
    }
    let state = h.state().state_json();
    let open = &state["architecture"]["last_open"];
    assert_eq!(open["query"], "ArchTabState");
    assert_eq!(
        open["file"].as_str(),
        Some(file_rel.to_str().unwrap()),
        "resolved to the right file"
    );
    assert_eq!(
        open["line"].as_u64(),
        Some(3),
        "resolved to the struct's line"
    );
    assert_eq!(open["symbol"], "ArchTabState");
    // The inline source-preview window read real lines around line 3.
    assert!(
        open["preview_lines"].as_u64().unwrap() >= 3,
        "preview window has lines: {open}"
    );
}

/// (d) The Hållpunkter (boundary) landmarks include known ui / gRPC / emitter
/// functions, classified by the coverage feature's `classify` (reused).
#[test]
fn arch_boundary_landmarks_cover_ui_grpc_emitter() {
    let (mut h, _) = harness("bnd");
    // Switch to the Boundaries lens.
    assert!(h.state_mut().arch_set_sub_view_for_test("Boundaries"));
    for _ in 0..6 {
        h.run_ok();
    }
    let state = h.state().state_json();
    let b = &state["architecture"]["boundaries"];
    assert!(b["ui"].as_u64().unwrap() >= 1, "a ui landmark (draw)");
    assert!(
        b["grpc"].as_u64().unwrap() >= 1,
        "a gRPC landmark (architecture handler)"
    );
    assert!(
        b["emitter"].as_u64().unwrap() >= 1,
        "an emitter landmark (state_json)"
    );

    let labels: Vec<&str> = b["landmarks"]
        .as_array()
        .unwrap()
        .iter()
        .filter_map(|l| l["label"].as_str())
        .collect();
    assert!(
        labels.contains(&"draw"),
        "ui draw landmark present: {labels:?}"
    );
    assert!(
        labels.contains(&"state_json"),
        "emitter state_json landmark present: {labels:?}"
    );
    assert!(
        labels.contains(&"architecture"),
        "gRPC handler landmark present: {labels:?}"
    );

    // LAW #4: the boundaries lens renders to a committed PNG.
    let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/snapshots");
    std::fs::create_dir_all(&dir).expect("create tests/snapshots");
    let image = h.render().expect("wgpu render failed");
    let png = dir.join("viz_architecture_boundaries.png");
    image.save(&png).expect("write boundaries PNG");
    let size = std::fs::metadata(&png).map(|m| m.len()).unwrap_or(0);
    assert!(
        size > 3_000,
        "boundaries PNG suspiciously small ({size} bytes)"
    );
    h.snapshot("viz_architecture_boundaries");
    let results = h.take_snapshot_results();
    if let Err(results) = results.into_result() {
        let inner: Vec<_> = results.into();
        for err in &inner {
            let msg = err.to_string();
            assert!(
                msg.contains("Missing snapshot"),
                "golden snapshot diff: {msg}"
            );
        }
    }
}