flowrs-engine/crates/flowrs-core/tests/node_coverage.rs
flowRS fd958c0547 flowRS engine, cut from flowRS 3451b065
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:04:39 -05:00

78 lines
3.4 KiB
Rust

//! Which primitive node types actually do something.
//!
//! A node you can drag out of the palette that then does nothing — in the interpreter AND
//! in the generated program — is the worst failure mode this product has. It does not warn,
//! it does not error, it just quietly produces no value, and the graph around it goes on
//! looking correct. `codegen` degrades to `// TODO: unsupported node type` and the T0
//! interpreter yields no value at all.
//!
//! This test does not assert that every node is implemented — many are genuinely not, and
//! pretending otherwise by deleting the test would be worse. It pins the CURRENT set, so:
//!
//! * implementing a node makes the count fall and the test tells you to update it,
//! * adding a node type without codegen makes the count rise and the test fails,
//!
//! which is the difference between a known, bounded gap and a slow silent leak.
use flowrs_core::graph::{Graph, nodes::init_node_types};
/// Node types with no codegen: `Graph::to_rust()` degrades to a TODO comment for them.
fn unsupported_by_codegen() -> Vec<String> {
let mut dead = Vec::new();
for ty in init_node_types() {
let mut g = Graph::new();
g.add_node(&ty.name, 0.0, 0.0);
let code = match g.to_rust() {
Ok(c) => c,
// A node that refuses BY NAME, for a reason that is true of THIS graph rather
// than of the node type, is implemented. An unwired `HashMapInsert` has no map
// to insert into; a `Break` outside a loop has nothing to break out of. Both
// are correct behaviour on a one-node graph, and the opposite of the silent
// nothing this test exists to count. So is a widget that refuses its default
// configuration (`Widget::refusal`): a `neural.detect` with no output layout
// declared says which layouts it decodes. The reasons are matched explicitly
// so a genuine codegen error still counts as dead.
Err(e)
if e.contains("is not connected")
|| e.contains("only valid inside a loop")
|| e.contains("does not run as configured") =>
{
continue;
}
Err(_) => {
dead.push(ty.name.clone());
continue;
}
};
if code.contains(&format!("// TODO: unsupported node type `{}`", ty.name)) {
dead.push(ty.name.clone());
}
}
dead
}
#[test]
fn the_set_of_unimplemented_node_types_is_known_and_not_growing() {
let dead = unsupported_by_codegen();
let total = init_node_types().len();
// 36 -> 16 -> 0. Every node type in the palette now emits code. The ratchet that got
// us here compared against a pinned count in both directions; at zero the "you can
// lower the constant" half is vacuous, so what remains is the half that still bites:
// a node type added without codegen sits in the palette and does nothing.
assert!(
dead.is_empty(),
"{} of {total} node types emit no code. A node type was added without codegen — \
it will sit in the palette and do nothing:\n {}",
dead.len(),
dead.join("\n ")
);
eprintln!(
"node_coverage: {}/{} node types emit code; {} do not:\n {}",
total - dead.len(),
total,
dead.len(),
dead.join(", ")
);
}