tests/viz_callgraph_render.rs
//! 🕸 **Call Graph lens — honest empty-workspace proof** (regression guard).
//!
//! The lens draws the code room's call rows. Over a workspace nothing was
//! consumed into it must SAY so: drawn through the real `draw_ui` path
//! (kittest) over a real warehouse, its `state_json` slice carries
//! `state == "unavailable"` and an `error` with the reader's `not_consumed` —
//! never a silent blank board, never a fake graph. (The drawn graph over a
//! consumed workspace: `viz::callgraph::tests`.) Both entries are covered: the
//! Architecture container's CallGraph sub-view and the back-compat top-level tab
//! (which must route to the call graph, not the Metro map).
//!
//! Run: `cargo test --features viz --test viz_callgraph_render -- --nocapture`
#![cfg(feature = "viz")]
use egui_kittest::Harness;
use nornir::viz::{Timeline, UrdrThreadsApp};
use nornir::warehouse::iceberg::IcebergWarehouse;
/// An empty timeline — the CallGraph lens does not read the release timeline.
fn empty_timeline() -> Timeline {
Timeline {
workspace_name: REPO.to_string(),
lanes: Vec::new(),
release_order: Vec::new(),
release_snapshot: Default::default(),
snapshots: Default::default(),
latest_snapshot: None,
bench_history: Default::default(),
}
}
const REPO: &str = "nornir";
/// A real (empty) warehouse under a fresh temp dir; returns `(dir, warehouse root)`.
fn fresh_warehouse(tag: &str) -> (std::path::PathBuf, std::path::PathBuf) {
let pid = std::process::id();
let dir = std::env::temp_dir().join(format!("nornir-cg-{tag}-{pid}"));
let _ = std::fs::remove_dir_all(&dir);
let wh_root = dir.join("warehouse");
std::fs::create_dir_all(&wh_root).unwrap();
IcebergWarehouse::open(&wh_root).expect("open warehouse");
// TODO: Audit that the environment access only happens in single-threaded code.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
(dir, wh_root)
}
/// The nothing-consumed contract on the lens's `state_json` slice.
fn assert_not_consumed(cg: &serde_json::Value) {
assert!(!cg.is_null(), "the call-graph lens emits its own state slice");
assert_eq!(cg["state"], "unavailable", "the lens states it could not read: {cg}");
let err = cg["error"].as_str().unwrap_or_default();
assert!(
err.contains("[not_consumed]"),
"the lens names the reader's reason instead of drawing a blank board: {cg}"
);
assert_eq!(cg["node_count"].as_u64(), Some(0), "no fake graph: {cg}");
}
/// The lens, driven the way the user does: switched to via the Architecture
/// container's CallGraph sub-view, drawn through the real frame loop.
#[test]
fn callgraph_lens_says_not_consumed() {
let (dir, wh_root) = fresh_warehouse("subview");
let mut app = UrdrThreadsApp::with_repos(
wh_root,
REPO.to_string(),
dir.clone(),
vec![REPO.to_string()],
);
assert!(
app.inject_timeline_for_test("Architecture", empty_timeline(), vec![REPO.to_string()]),
"the Architecture tab is selectable"
);
assert!(
app.arch_set_sub_view_for_test("CallGraph"),
"the CallGraph lens is a switchable Architecture sub-view"
);
let mut h = Harness::builder()
.with_size(egui::Vec2::new(1400.0, 900.0))
.build_ui_state(|ui, app: &mut UrdrThreadsApp| app.draw_ui(ui), app);
for _ in 0..6 {
h.run_ok();
}
let cg = h.state().state_json()["callgraph"].clone();
assert_not_consumed(&cg);
nornir_testmatrix::functional_status(
"viz-callgraph",
"callgraph_lens_says_not_consumed",
cg["state"] == "unavailable",
&format!("state={} error={}", cg["state"], cg["error"]),
);
let _ = std::fs::remove_dir_all(&dir);
}
/// The OTHER entry: the back-compat top-level 🕸 Call Graph tab (`Tab::CallGraph`)
/// must route to the call-graph lens (NOT the Metro map it was once hijacked
/// to), which states the same nothing-consumed contract.
#[test]
fn top_level_callgraph_tab_routes_to_the_call_graph_lens() {
let (dir, wh_root) = fresh_warehouse("tab");
let mut app = UrdrThreadsApp::with_repos(
wh_root,
REPO.to_string(),
dir.clone(),
vec![REPO.to_string()],
);
assert!(
app.inject_timeline_for_test("CallGraph", empty_timeline(), vec![REPO.to_string()]),
"the top-level CallGraph tab is selectable"
);
let mut h = Harness::builder()
.with_size(egui::Vec2::new(1400.0, 900.0))
.build_ui_state(|ui, app: &mut UrdrThreadsApp| app.draw_ui(ui), app);
for _ in 0..6 {
h.run_ok();
}
let cg = h.state().state_json()["callgraph"].clone();
assert!(cg.get("line_count").is_none(), "must NOT be the metro slice: {cg}");
assert_not_consumed(&cg);
let _ = std::fs::remove_dir_all(&dir);
}