tests/self_emitters_viz.rs
//! **Self-emitter matrix — viz RENDER-path emit-fallback surfaces.**
//!
//! nornir wiring its OWN [`nornir_testmatrix::functional_status`] across the
//! "can't-observe-from-outside" *render* surfaces: the egui paints and the
//! screenshot PNG write. Built ONLY under `--features testmatrix` (the whole file
//! is gated), so a release `cargo test` never compiles the emitters.
//!
//! EMIT-DOCTRINE this file obeys — the **emit-fallback** branch: the egui paint /
//! screenshot PNG write is a side effect that can't be read from outside. The
//! emit is implanted INSIDE the render/side-effect fn (gated
//! `#[cfg(feature = "testmatrix")]`); here we drive that fn HEADLESSLY with REAL
//! injected input, then READ BACK the emitted row from the in-process buffer via
//! [`nornir_testmatrix::drain_functional_rows`] (NOT a JSON sink — nornir's own
//! self-tests run in-process and drain the process-global buffer). Inject-assert:
//! a real graph / a real frame in, a real green non-blank row drained back out.
//!
//! Run:
//! cargo test --features "viz,testmatrix" --test self_emitters_viz -- --test-threads=1
#![cfg(all(feature = "viz", feature = "testmatrix"))]
use std::path::PathBuf;
use nornir::viz::UrdrThreadsApp;
const WS: &str = "self-emit-viz";
/// A fresh fat app over a throwaway local warehouse under `dir`. Local mode (a
/// real warehouse path) is what the funnel demo + screenshot warehouse-persist
/// paths need; the render emit-fallbacks fire regardless.
fn local_app(dir: &std::path::Path) -> UrdrThreadsApp {
let wh = dir.join(".nornir").join("warehouse");
std::fs::create_dir_all(&wh).unwrap();
UrdrThreadsApp::with_repos(wh, WS.to_string(), dir.to_path_buf(), vec![])
}
/// Drain + return only the rows whose `test_name` matches `check`. Clears the
/// process-global buffer (so a sibling test never reads ours, and we never read
/// theirs) — callers drain FIRST to clear any leftovers, then drive, then drain.
fn drained_for(check: &str) -> Vec<nornir_testmatrix::TestResultRow> {
nornir_testmatrix::drain_functional_rows()
.into_iter()
.filter(|r| r.test_name == check)
.collect()
}
// ════════════════════════════════════════════════════════════════════════════
// emit-fallback: the egui paints (CallGraph + Funnel-3D). Inject a real graph /
// real plan, headlessly DRAW both tabs, then READ BACK the rows implanted inside
// the render fns. ok ⇔ the scene painted non-blank.
// ════════════════════════════════════════════════════════════════════════════
#[test]
fn viz_callgraph_and_funnel3d_render_emit_fallback_roundtrip() {
// SAFETY: set before the app/harness build; tests in this file are independent.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
let dir = std::env::temp_dir().join(format!("nornir-self-emit-render-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let mut app = local_app(&dir);
// Clear any leftover buffer rows, then drive the real render paths.
let _ = nornir_testmatrix::drain_functional_rows();
app.test_render_callgraph_and_funnel3d_nonblank();
// Drain ONCE; the buffer is cleared by the drain so split it by test_name.
let all = nornir_testmatrix::drain_functional_rows();
// ── CallGraph: a 6-node ring graph painted ⇒ a green non-blank row.
let cg = all
.iter()
.filter(|r| r.test_name == "callgraph_rendered_non_blank")
.last()
.expect("a callgraph render row must be in the drained buffer");
assert_eq!(
cg.status,
nornir_testmatrix::status::PASS,
"callgraph draw must paint a non-blank scene: {}",
cg.message
);
assert_eq!(cg.suite, "viz/callgraph (egui draw)");
assert!(
cg.message.contains("6 node circle(s)"),
"the row carries the REAL painted count: {}",
cg.message
);
// ── Funnel-3D: a real demo plan projected + painted ⇒ a green non-blank row.
let f3d = all
.iter()
.filter(|r| r.test_name == "funnel3d_rendered_non_blank")
.last()
.expect("a funnel3d render row must be in the drained buffer");
assert_eq!(
f3d.status,
nornir_testmatrix::status::PASS,
"funnel3d draw must paint a non-blank scene: {}",
f3d.message
);
assert_eq!(f3d.suite, "viz/funnel3d (egui/3D draw)");
assert!(
f3d.message.contains("node sphere(s)"),
"the row carries the REAL painted/total count: {}",
f3d.message
);
let _ = std::fs::remove_dir_all(&dir);
// SAFETY: cleared after the test's single-threaded body finished.
unsafe { std::env::remove_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG") };
}
// ════════════════════════════════════════════════════════════════════════════
// emit-fallback: the DepGraph egui paint. Inject a real dep-graph snapshot, draw
// the DepGraph tab headlessly, and read back the `dep_graph_rendered_non_blank`
// row implanted inside `draw_dep_graph`.
// ════════════════════════════════════════════════════════════════════════════
#[test]
fn viz_dep_graph_render_emit_fallback_roundtrip() {
use std::collections::BTreeMap;
use nornir::release::pipeline::{RepoGitState, RepoReleaseRecord, persist_lineage};
use nornir::warehouse::dep_graph::{CrossRepoEdge, WorkspaceGraph, record_dep_graph};
use nornir::warehouse::iceberg::IcebergWarehouse;
use uuid::Uuid;
const REPOS: [&str; 3] = ["alpha", "beta", "gamma"];
// SAFETY: set before the app/harness build; tests in this file are independent.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
let dir =
std::env::temp_dir().join(format!("nornir-self-emit-depgraph-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let wh_path = dir.join(".nornir").join("warehouse");
std::fs::create_dir_all(&wh_path).unwrap();
// Seed a REAL dep-graph snapshot + release lineage so the Timeline carries
// repos for `draw_dep_graph` to lay out into painted node cards.
let wh = IcebergWarehouse::open(&wh_path).expect("open warehouse");
let edges = vec![
CrossRepoEdge {
from: REPOS[1].into(),
to: REPOS[0].into(),
via: ["alpha-core".to_string()].into_iter().collect(),
kind: Default::default(),
},
CrossRepoEdge {
from: REPOS[0].into(),
to: REPOS[2].into(),
via: ["gamma-leaf".to_string()].into_iter().collect(),
kind: Default::default(),
},
];
let graph = WorkspaceGraph::from_query_parts(BTreeMap::new(), edges);
let snapshot_id = wh
.block_on(record_dep_graph(&wh, WS, &graph))
.expect("record dep graph");
let release_id = Uuid::from_u128(0x5e5f_0000_0000_0000_0000_0000_0000_0001u128);
let lineage: Vec<RepoReleaseRecord> = REPOS
.iter()
.enumerate()
.map(|(i, r)| RepoReleaseRecord {
repo: (*r).to_string(),
build_order_idx: i,
git: RepoGitState {
sha: format!("{r}0000000sha"),
branch: "main".into(),
dirty: false,
},
gate_status: "succeeded".into(),
tests_passed: 2,
tests_failed: 0,
published_versions: vec![((*r).to_string(), "0.1.0".into())],
tantivy_snapshot_id: None,
dwarf_snapshot_id: None,
})
.collect();
wh.block_on(persist_lineage(
&wh,
release_id,
WS,
&snapshot_id,
&lineage,
false,
))
.expect("persist_lineage");
drop(wh);
let mut app = UrdrThreadsApp::with_repos(
wh_path,
WS.to_string(),
dir.clone(),
REPOS.iter().map(|r| r.to_string()).collect(),
);
let _ = nornir_testmatrix::drain_functional_rows();
// Select the DepGraph tab + draw it headlessly via the public render driver.
app.test_render_dep_graph_nonblank();
let rows = drained_for("dep_graph_rendered_non_blank");
let dg = rows
.last()
.expect("a dep_graph render row must be in the drained buffer");
assert_eq!(
dg.status,
nornir_testmatrix::status::PASS,
"dep-graph draw must paint a non-blank scene: {}",
dg.message
);
assert_eq!(dg.suite, "viz/graph (DepGraphView egui draw)");
assert!(
dg.message.contains("node card(s)"),
"the row carries the REAL painted node/edge count: {}",
dg.message
);
let _ = std::fs::remove_dir_all(&dir);
// SAFETY: cleared after the test's single-threaded body finished.
unsafe { std::env::remove_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG") };
}
// ════════════════════════════════════════════════════════════════════════════
// emit-fallback: the 🧵 Timeline egui paint. Seed a real release lineage so the
// Timeline carries release nodes, render every tab headlessly (which paints the
// Timeline tab), then read back the `timeline_rendered` row implanted in the
// Timeline draw arm — GREEN because it painted non-blank release nodes.
// ════════════════════════════════════════════════════════════════════════════
#[test]
fn viz_timeline_render_emit_fallback_roundtrip() {
use std::collections::BTreeMap;
use nornir::release::pipeline::{RepoGitState, RepoReleaseRecord, persist_lineage};
use nornir::warehouse::dep_graph::{WorkspaceGraph, record_dep_graph};
use nornir::warehouse::iceberg::IcebergWarehouse;
use uuid::Uuid;
const REPOS: [&str; 2] = ["alpha", "beta"];
// SAFETY: set before the app/harness build; tests in this file are independent.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
let dir =
std::env::temp_dir().join(format!("nornir-self-emit-timeline-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let wh_path = dir.join(".nornir").join("warehouse");
std::fs::create_dir_all(&wh_path).unwrap();
let wh = IcebergWarehouse::open(&wh_path).expect("open warehouse");
let graph = WorkspaceGraph::from_query_parts(BTreeMap::new(), vec![]);
let snapshot_id = wh
.block_on(record_dep_graph(&wh, WS, &graph))
.expect("record dep graph");
let release_id = Uuid::from_u128(0x71_0000_0000_0000_0000_0000_0000_0001u128);
let lineage: Vec<RepoReleaseRecord> = REPOS
.iter()
.enumerate()
.map(|(i, r)| RepoReleaseRecord {
repo: (*r).to_string(),
build_order_idx: i,
git: RepoGitState {
sha: format!("{r}0000000sha"),
branch: "main".into(),
dirty: false,
},
gate_status: "succeeded".into(),
tests_passed: 1,
tests_failed: 0,
published_versions: vec![((*r).to_string(), "0.1.0".into())],
tantivy_snapshot_id: None,
dwarf_snapshot_id: None,
})
.collect();
wh.block_on(persist_lineage(
&wh,
release_id,
WS,
&snapshot_id,
&lineage,
false,
))
.expect("persist_lineage");
drop(wh);
let mut app = UrdrThreadsApp::with_repos(
wh_path,
WS.to_string(),
dir.clone(),
REPOS.iter().map(|r| r.to_string()).collect(),
);
let _ = nornir_testmatrix::drain_functional_rows();
// Render every tab headlessly — the Timeline arm paints + emits its row.
app.render_all_tabs_headless();
let rows = drained_for("timeline_rendered");
let row = rows
.last()
.expect("a timeline_rendered row must be in the drained buffer");
assert_eq!(
row.status,
nornir_testmatrix::status::PASS,
"the Timeline painted non-blank release nodes: {}",
row.message
);
assert_eq!(row.suite, "nornir/viz/timeline");
assert!(
row.message.contains("release node(s) painted"),
"the row carries the REAL painted lane/node count: {}",
row.message
);
let _ = std::fs::remove_dir_all(&dir);
// SAFETY: cleared after the test's single-threaded body finished.
unsafe { std::env::remove_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG") };
}
// ════════════════════════════════════════════════════════════════════════════
// ════════════════════════════════════════════════════════════════════════════
// emit-fallback: the PNG-write side effect. Feed a synthetic non-blank frame into
// `handle_screenshot_image` via the testmatrix hook, then READ BACK the row
// implanted inside the fn — and confirm the object-state half (PNG on disk).
// ════════════════════════════════════════════════════════════════════════════
#[test]
fn viz_screenshot_png_written_emit_fallback_roundtrip() {
// SAFETY: set before the app/harness build; tests in this file are independent.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
let dir = std::env::temp_dir().join(format!("nornir-self-emit-png-{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
let png = dir.join("shot.png");
let mut app = UrdrThreadsApp::with_repos(dir.clone(), WS.to_string(), PathBuf::new(), vec![]);
let _ = nornir_testmatrix::drain_functional_rows();
// Feed a synthetic non-blank frame straight into the GPU-frame path.
app.test_feed_screenshot_frame(64, 48, Some(png.to_string_lossy().into_owned()));
// 1. object-state: the PNG exists on disk + state_json points at it.
assert!(png.exists(), "screenshot PNG must be written to disk");
let last = app.state_json()["screenshot"]["last_path"]
.as_str()
.map(String::from);
assert_eq!(last.as_deref(), Some(png.to_string_lossy().as_ref()));
// 2. emit-fallback: the row implanted INSIDE handle_screenshot_image landed.
let rows = drained_for("screenshot_command_pending_then_png_written");
let row = rows
.last()
.expect("the screenshot emit-fallback row must be present in the buffer");
assert_eq!(
row.status,
nornir_testmatrix::status::PASS,
"the PNG was actually written non-empty: {}",
row.message
);
assert_eq!(row.suite, "viz/app self-screenshot (LAW4b egui capture)");
assert!(
row.message.contains("64x48px") && row.message.contains("bytes PNG written"),
"the row carries the REAL dimensions + byte count: {}",
row.message
);
let _ = std::fs::remove_dir_all(&dir);
// SAFETY: cleared after the test's single-threaded body finished.
unsafe { std::env::remove_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG") };
}