78 lines
3.4 KiB
Rust
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(", ")
|
|
);
|
|
}
|