#[cfg(feature = "testmatrix")]
pub fn emit(component: &str, check: &str, ok: bool, detail: &str) {
nornir_testmatrix::functional_status(component, check, ok, detail);
}
#[cfg(not(feature = "testmatrix"))]
#[inline(always)]
pub fn emit(_component: &str, _check: &str, _ok: bool, _detail: &str) {}
#[cfg(feature = "testmatrix")]
pub fn emit_render(component: &str, vertices: usize, geometry: usize) {
let ok = vertices > 0 && geometry > 0;
emit(
component,
"headless_render",
ok,
&format!("vertices={vertices} geometry={geometry}"),
);
}
#[cfg(not(feature = "testmatrix"))]
#[inline(always)]
pub fn emit_render(_component: &str, _vertices: usize, _geometry: usize) {}
#[cfg(feature = "testmatrix")]
pub fn emit_non_blank(component: &str, ratio: f64, min_ratio: f64, geometry: usize) {
let ok = ratio >= min_ratio && geometry > 0;
emit(
component,
"renders_non_blank",
ok,
&format!("content_ratio={ratio:.4} min={min_ratio} geometry={geometry}"),
);
}
#[cfg(not(feature = "testmatrix"))]
#[inline(always)]
pub fn emit_non_blank(_component: &str, _ratio: f64, _min_ratio: f64, _geometry: usize) {}
#[cfg(feature = "testmatrix")]
pub fn emit_facet_probe(component: &str, probe: &crate::harness::FacetProbe) {
emit_render(component, probe.vertices, probe.cardinality());
if !probe.steps.is_empty() {
emit(
component,
"responds_to_input",
probe.responded(),
&format!(
"changed={}/{} steps",
probe.steps.iter().filter(|s| s.changed).count(),
probe.steps.len()
),
);
}
}
#[cfg(not(feature = "testmatrix"))]
#[inline(always)]
pub fn emit_facet_probe(_component: &str, _probe: &crate::harness::FacetProbe) {}
pub fn probe_and_emit(
component: &str,
facet: &mut dyn crate::Facet,
msgs: &[&str],
) -> crate::harness::FacetProbe {
let probe = crate::harness::probe_facet(facet, msgs);
emit_facet_probe(component, &probe);
probe
}
#[cfg(all(test, feature = "testmatrix"))]
mod tests {
use super::*;
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn emit_render_writes_green_and_red_rows() {
let _env = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let dir = std::env::temp_dir().join(format!(
"facett-tm-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("functional.json");
unsafe {
std::env::set_var("NORNIR_TESTMATRIX_OUT", &file);
std::env::set_var("NORNIR_TESTMATRIX_REPO", "facett");
std::env::set_var("NORNIR_TESTMATRIX_RUN", "run-fc-test");
}
emit_render("live_view", 1234, 9);
emit_render("dead_map", 50, 0);
emit_non_blank("blank_pane", 0.0007, 0.01, 40_000);
emit_non_blank("real_map", 0.028, 0.01, 40_000);
let rows = nornir_testmatrix::JsonFileSink::new(&file)
.read_all()
.unwrap();
assert_eq!(rows.len(), 4, "four checks → four functional rows");
let live = rows.iter().find(|r| r.suite == "live_view").unwrap();
assert_eq!(live.status, nornir_testmatrix::status::PASS);
assert_eq!(live.aspect, "functional");
let dead = rows.iter().find(|r| r.suite == "dead_map").unwrap();
assert_eq!(dead.status, nornir_testmatrix::status::FAIL, "zero geometry = RED");
let blank = rows.iter().find(|r| r.suite == "blank_pane").unwrap();
assert_eq!(blank.status, nornir_testmatrix::status::FAIL, "blank pane = RED");
let real = rows.iter().find(|r| r.suite == "real_map").unwrap();
assert_eq!(real.status, nornir_testmatrix::status::PASS);
unsafe {
std::env::remove_var("NORNIR_TESTMATRIX_OUT");
std::env::remove_var("NORNIR_TESTMATRIX_REPO");
std::env::remove_var("NORNIR_TESTMATRIX_RUN");
}
let _ = std::fs::remove_dir_all(&dir);
}
struct ProbeListPane {
items: Vec<String>,
}
impl crate::Facet for ProbeListPane {
fn title(&self) -> &str {
"probe_list"
}
fn ui(&mut self, ui: &mut egui::Ui) {
for it in &self.items {
ui.label(it);
}
}
fn state_json(&self) -> serde_json::Value {
serde_json::json!({ "items": self.items })
}
fn update_json(&mut self, msg_json: &str) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json)
&& let Some(s) = v.get("push").and_then(|x| x.as_str())
{
self.items.push(s.to_string());
}
}
}
#[test]
fn probe_and_emit_writes_discovery_rows() {
let _env = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let dir = std::env::temp_dir().join(format!(
"facett-tm-probe-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("functional.json");
unsafe {
std::env::set_var("NORNIR_TESTMATRIX_OUT", &file);
std::env::set_var("NORNIR_TESTMATRIX_REPO", "facett");
std::env::set_var("NORNIR_TESTMATRIX_RUN", "run-probe");
}
let mut pane = ProbeListPane { items: vec!["seed".into()] };
let probe = probe_and_emit("probe_list", &mut pane, &[r#"{"push":"a"}"#, r#"{"push":"b"}"#]);
assert!(probe.responded(), "both pushes move state");
assert_eq!(pane.items.len(), 3);
let rows = nornir_testmatrix::JsonFileSink::new(&file).read_all().unwrap();
assert_eq!(rows.len(), 2, "probe emits render + input rows");
let render = rows.iter().find(|r| r.test_name == "headless_render").unwrap();
assert_eq!(render.suite, "probe_list");
assert_eq!(render.status, nornir_testmatrix::status::PASS, "drew + non-empty state = green");
let input = rows.iter().find(|r| r.test_name == "responds_to_input").unwrap();
assert_eq!(input.status, nornir_testmatrix::status::PASS, "live update_json = green");
unsafe {
std::env::remove_var("NORNIR_TESTMATRIX_OUT");
std::env::remove_var("NORNIR_TESTMATRIX_REPO");
std::env::remove_var("NORNIR_TESTMATRIX_RUN");
}
let _ = std::fs::remove_dir_all(&dir);
}
}