nornir_testmatrix/lib.rs
1//! # nornir-testmatrix
2//!
3//! The portable core of nornir's **multi-aspect test matrix** — extracted from
4//! `nornir` (EPIC L) so any leaf repo (znippy, skade, lbzip2-rs …) can run the
5//! same matrix with a *tiny* dep set: `std` + `serde`/`serde_json` + `anyhow`.
6//! No iceberg, arrow, eframe, tantivy, or skade.
7//!
8//! ## What it does
9//! 1. **Runner** ([`runner`]) — wrap a repo's *native* Rust test framework
10//! (`cargo nextest run --message-format libtest-json` if nextest is on PATH,
11//! else plain `cargo test`), parse each test's pass/fail + duration, enforce a
12//! stall watchdog. Pure subprocess driving — no test registration.
13//! 2. **Multi-aspect engine** ([`aspect`]) — beyond unit tests, run MANY
14//! *aspects* of a repo's health ([`Aspect`]): build, doctest, clippy, fmt,
15//! audit, bench-smoke, coverage, feature-powerset, msrv, examples. Each
16//! shells the right `cargo` subcommand, parses its result, and emits
17//! [`TestResultRow`]s carrying an `aspect` tag + a numeric `metric` (coverage
18//! %, warning count, advisory count …). A missing tool is a **skip**, never a
19//! hard fail.
20//! 3. **Model** ([`model`]) — the pure row/summary types ([`TestResultRow`],
21//! [`RunSummary`], [`TestSelector`], the [`status`] tags) + the renderers
22//! ([`render_matrix`], [`summarize_runs`], [`rows_to_json`]).
23//! 4. **Sinks** ([`sink`]) — the [`TestSink`] trait + built-in [`JsonFileSink`]
24//! and [`NullSink`] so a leaf repo with no warehouse can still persist its
25//! matrix. nornir implements `TestSink` over its Iceberg warehouse.
26//! 5. **Discovery** ([`discover`]) — the autonom (AUT2) anti-drift core: pure
27//! enumerators that build the testable [`Surface`] from data (facett
28//! components, viz tabs × {thin,fat}, CLI subcommands, MCP tools, and the
29//! unreached-function set from `symbol_facts − test-reachable(call_edges)`),
30//! then [`compute_gap`] differences it against coverage so the gate can
31//! enforce `Gap == ∅`. No warehouse here — the caller feeds the rows.
32//! 6. **Functional-status mode** ([`functional`]) — a component reports whether
33//! it *actually works* (`functional_status(component, check, ok, detail)`)
34//! from its own headless-render / self-test path. The
35//! [`Aspect::Functional`] runner drains those into rows so a broken render is
36//! a RED matrix row WITHOUT eyeballing a GUI. Gated behind the **`testmatrix`
37//! cargo feature**: release builds (feature off) strip the emit to a no-op.
38//!
39//! ## How a leaf repo uses it
40//! ```no_run
41//! use nornir_testmatrix::{run_full_matrix, Aspect, JsonFileSink, TestSink};
42//! use std::path::Path;
43//!
44//! let aspects = [Aspect::Build, Aspect::Unit, Aspect::Doctest, Aspect::Clippy, Aspect::Fmt];
45//! let rows = run_full_matrix(Path::new("."), &aspects);
46//! let sink = JsonFileSink::new("target/nornir-testmatrix.json");
47//! sink.append(&rows).unwrap();
48//! ```
49
50pub mod aspect;
51pub mod coverage;
52pub mod discover;
53pub mod functional;
54pub mod model;
55pub mod runner;
56pub mod sink;
57
58// ─── flat re-exports (the crate's public façade) ───────────────────────────
59
60pub use aspect::{
61 aspect_label, parse_aspect, parse_funnel_decompose, run_aspect, run_full_matrix, Aspect,
62 AspectOutcome,
63};
64pub use coverage::{
65 rows_for, seed_allowlist, stale_allowlist_entries, AllowEntry, Allowlist, CoverageRow,
66 CoverageSummary, GateReport, Verdict,
67};
68pub use discover::{
69 cli_commands, compute_gap, facett_components, mcp_tools, test_reachable, unreached_functions,
70 viz_tabs, CallEdge, FacetRow, Gap, Mode, Surface, SurfaceKind, SurfaceNode, SymbolRow,
71};
72pub use functional::{drain_functional_rows, functional_row, functional_status};
73pub use model::{
74 listed_rows, new_run_id, parse_cargo_test_list, parse_nextest_list, render_matrix,
75 rows_to_json, short_run, status, summarize_runs, RunSummary, TestResultRow, TestSelector,
76};
77pub use runner::{
78 detect_runner, list_tests, run_matrix, stall_secs, MatrixRun, Runner, TestCase,
79 DEFAULT_STALL_SECS,
80};
81pub use sink::{JsonFileSink, NullSink, TestSink};