dwarves-gui/src/benches.rs
//! **The Testmatrix + Benches tab** (task #66) — the testmatrix + bench-trend of the
//! **SELECTED workspace**, where a human watches the 100-bench mimir epic (#62) go green.
//!
//! Thin host wrapper over the reuse pane [`dwarves_embed::BenchesView`]: it holds that
//! pane (built from the selected workspace's testmatrix + bench-history files) and, when
//! the warehouse selector broadcasts `{"set_workspace":…}`, RELOADS both halves for the
//! newly-selected workspace — the SAME binding gesture the Release tab uses. The pane
//! itself owns the two REUSE consumptions:
//! * `facett_matrix::MatrixView` for the testmatrix grid (surface × coverage cells);
//! * the `nornir_bench` read API for the `nornir.mimir.<populate|search>` trend.
//!
//! Nothing here re-implements a grid or a bench harness. The workspace/source oracle
//! lives in the pane's own `state_json` (`selected_workspace`, `source_matrix`,
//! `source_benches`), so this wrapper only re-binds + forwards.
use facett_core::{Facet, Panel};
/// The Testmatrix + Benches tab: the reused pane, re-bound to the selected workspace.
pub struct BenchesTab {
/// The reused `dwarves_embed::BenchesView` pane for the current workspace.
inner: Box<dyn Panel>,
/// The active workspace the pane is bound to (`""` = none → each half named empty).
workspace: String,
}
impl BenchesTab {
/// Bind to `workspace` (may be `""`) and load its testmatrix + benches now.
pub fn new(workspace: impl Into<String>) -> Self {
let workspace = workspace.into();
Self {
inner: dwarves_embed::benches_panel(&workspace),
workspace,
}
}
/// The tab with nothing selected — the honest UNBOUND tab (tests / pre-selection
/// shell). It was called `fixture()` and the reused pane behind it drew one; the
/// embed side deleted both fixtures on 2026-09-10, so with nothing selected each
/// half now carries its own named empty state instead.
#[allow(dead_code)]
pub fn unbound() -> Self {
Self::new("")
}
/// **Re-bind the pane to `workspace`** — rebuild the reused pane from that
/// workspace's testmatrix + bench-history files (a NAMED empty on absence, never a
/// fixture — both were deleted 2026-09-10). Never panics.
pub fn bind(&mut self, workspace: &str) {
self.workspace = workspace.to_string();
self.inner = dwarves_embed::benches_panel(workspace);
}
/// The workspace the pane is bound to (`""` = none → each half named empty).
#[allow(dead_code)]
pub fn workspace(&self) -> &str {
&self.workspace
}
}
impl Facet for BenchesTab {
fn title(&self) -> &str {
"Testmatrix + Benches"
}
fn ui(&mut self, ui: &mut egui::Ui) {
self.inner.ui(ui);
}
fn state_json(&self) -> serde_json::Value {
// The reused pane's state is already self-complete (component, matrix, benches,
// selected_workspace, source_matrix/source_benches) — pass it through.
self.inner.state_json()
}
fn update_json(&mut self, msg_json: &str) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json) {
// The selector's broadcast (the SAME `{"set_workspace":…}` every tab gets):
// re-bind to the newly-selected workspace.
if let Some(ws) = v.get("set_workspace").and_then(|t| t.as_str()) {
self.bind(ws);
return;
}
}
// Anything else forwards to the reused pane (repo drill-in select/clear, etc.).
self.inner.update_json(msg_json);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tabbed::TabbedView;
/// ROBOT SCENARIO (task #66) — the **Testmatrix + Benches** view mounted in the shell
/// and driven over the remote seam (`state_json`/`update_json`), the SAME channel a
/// `nornir-robotui` scenario / thin client use. Asserts BOTH reuse consumptions reach
/// the tab: (1) the reused `facett_matrix::MatrixView` grid folds under `matrix` (real
/// coloured cells); (2) the `nornir_bench`-read mimir trend carries populate + search
/// benches, improving newest-vs-first. Then re-binds to a workspace WITH real files and
/// asserts the tab reflects THAT run (source → `workspace-run`). Emits
/// a `nornir-testmatrix` cell (reuse edda).
#[test]
fn robot_testmatrix_benches_view_over_the_seam() {
// Hold ENV_LOCK: this test SEEDS a bench-history `.jsonl` under
// `std::env::temp_dir()` (`workspace_bench_history_path`) then reads it BACK
// asserting `source_benches == "workspace-run"`. A concurrent TMPDIR repoint (the
// Release `scratch_tmp` tests, the hermetic robot_remote shell) would make the
// read-back miss the file and miss it — serialize on the shared lock.
let _env = crate::ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let view: Box<dyn Panel> = Box::new(BenchesTab::unbound());
let mut app = TabbedView::new(
"dwarves — Testmatrix + Benches",
vec![("Benches".into(), view)],
);
Facet::update_json(&mut app, r#"{"set_tab":"Benches"}"#);
let st = Facet::state_json(&app);
let pane = &st["benches"];
// (1) + (2): ROWS OR A NAMED EMPTY, per half, in BOTH worlds.
//
// These two used to assert the deleted fixtures — `source_matrix == "fixture"`
// with >= 3 repos and a green cell, and `source_benches == "fixture"` with 4
// benches over 2 runs, all improving. Those were the numbers the shipped tab
// drew when nothing had been measured.
//
// Replacing them with an assertion of emptiness would be the same fault
// inverted. This mounts the SHIPPED tab against the real workspace paths, so on
// a box that HAS run `nornir test` or a bench, the files exist and the halves
// correctly have rows; a test demanding emptiness is green only where nothing
// has ever run and red on the forge.
//
// The disjunction is what actually holds everywhere, and it is the property
// worth having: each half either shows rows it can source, or names the feed it
// could not read. Never numbers with no provenance, and never a silent blank.
let half_ok = |rows: u64, empty: &serde_json::Value, source: &serde_json::Value| {
let has_rows = rows > 0;
let named_empty = rows == 0
&& empty["empty"] == true
&& !empty["feed"].as_str().unwrap_or("").is_empty()
&& !empty["detail"].as_str().unwrap_or("").is_empty()
&& source == "empty";
// Exactly one of the two, and rows must name where they came from.
(has_rows ^ named_empty) && (!has_rows || source != "empty")
};
let matrix_ok = pane["component"] == "BenchesView"
&& half_ok(
pane["matrix"]["repo_count"].as_u64().unwrap_or(0),
&pane["matrix_empty"],
&pane["source_matrix"],
)
// The feed is named whichever way it went.
&& !pane["matrix_feed"].as_str().unwrap_or("").is_empty();
let bench_ok = half_ok(
pane["bench_count"].as_u64().unwrap_or(0),
&pane["bench_empty"],
&pane["source_benches"],
) && !pane["bench_feed"].as_str().unwrap_or("").is_empty();
// (3) re-bind to a workspace WITH real files → the tab reflects THAT run.
let ws = "dwarves-gui-benches-ws";
let tm = dwarves_embed::workspace_testmatrix_path(ws);
let bh = dwarves_embed::workspace_bench_history_path(ws);
let _ = std::fs::remove_file(&tm);
let _ = std::fs::remove_file(&bh);
// Seed a distinct bench history (one mimir bench, one run) via the read API's writer.
let mut m = serde_json::Map::new();
m.insert("rows_per_sec".into(), serde_json::json!(777.0));
let run = nornir_bench::BenchRun {
id: nornir_bench::BenchRun::new_id(),
date: "2026-07-18".into(),
timestamp: None,
version: "x".into(),
machine: "Loki".into(),
cores: 12,
machine_facts: None,
results: vec![nornir_bench::BenchResult::measured(
"nornir.mimir.search.hybrid_rrf_5k",
m,
)],
tests: Vec::new(),
};
nornir_bench::history::append(&bh, &run).unwrap();
Facet::update_json(
&mut app,
&serde_json::json!({ "set_workspace": ws }).to_string(),
);
let st = Facet::state_json(&app);
let pane = &st["benches"];
let bound_ok = pane["selected_workspace"] == ws
&& pane["source_benches"] == "workspace-run"
&& pane["run_count"] == 1
&& pane["benches"][0]["op"] == "hybrid_rrf"
&& pane["benches"][0]["size"] == "5k"
&& pane["benches"][0]["latest"] == 777.0;
let _ = std::fs::remove_file(&tm);
let _ = std::fs::remove_file(&bh);
let drivable = matrix_ok && bench_ok && bound_ok;
assert!(
drivable,
"the Testmatrix+Benches view drives + re-binds over the remote seam: {st}"
);
#[cfg(feature = "testmatrix")]
nornir_testmatrix::functional_status(
"dwarves-gui",
"testmatrix_benches_view_drivable",
drivable,
"Testmatrix+Benches view (task #66): reused facett_matrix::MatrixView grid (surface×coverage cells) + nornir_bench read-API-derived nornir.mimir.<populate|search> trend (improving newest-vs-first), re-binding to the selected workspace's files (a NAMED empty state per half vs workspace-run; both fixtures deleted 2026-09-10) — driven headless over state_json/update_json",
);
let _ = drivable;
}
}