//! 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 { 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(", ") ); }