flowrs-engine/crates/flowrs-core/tests/new_node_codegen.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

141 lines
5.6 KiB
Rust

//! The collection, channel and struct nodes must emit Rust that actually compiles.
//!
//! Emitting *something* is not the bar — `node_coverage` measures that, and it passed for
//! these long before they worked. This compiles the whole emitted program, which is what
//! caught every bug in them: bare `map`/`tx`/`instance` identifiers where an input was not
//! connected (E0425), `HashMap::new()` with nothing to infer from (E0282), a struct type
//! nothing declared (E0422), and a `FieldSet` assigning through a non-`mut` binding
//! (E0594). Each of those emitted confidently and failed only at `rustc`.
use flowrs_core::graph::{Graph, WireMode};
use std::process::Command;
fn check(label: &str, g: &Graph) {
let src = match g.to_rust() {
Ok(s) => s,
Err(e) => panic!("{label} should emit, but codegen refused: {e}"),
};
let dir = std::env::temp_dir().join("flowrs_probe_wired");
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("Cargo.toml"),
"[workspace]\n[package]\nname=\"p\"\nversion=\"0.0.0\"\nedition=\"2021\"\n",
)
.unwrap();
std::fs::write(dir.join("src/main.rs"), &src).unwrap();
let out = Command::new("cargo")
.args(["check", "--quiet", "--offline", "--message-format=short"])
.current_dir(&dir)
// `CARGO_TARGET_DIR` removed so this check does not depend on ambient config.
//
// `graph_build` in flowrs-mcp had a demonstrated false pass from an inherited
// target dir: its scratch crates are all `flowrs_graph 0.0.0`, and one shared
// target made them a single build unit, so cargo found a later program already
// fresh and exited 0 without reading it. These harnesses use the same package
// identity, so the same shape is possible here.
//
// Tested, and it did NOT reproduce: priming a shared target with a passing
// harness and then running this one with its own codegen broken still failed
// correctly. This is isolation against a hazard that has bitten once nearby,
// not a fix for a bug demonstrated here.
.env_remove("CARGO_TARGET_DIR")
.output()
.unwrap();
if out.status.success() {
return;
}
let e = String::from_utf8_lossy(&out.stderr);
// A dependency that will not resolve offline is an environment limit, not a defect.
if e.contains("failed to download") || e.contains("no matching package") {
eprintln!("skipped {label}: dependencies unavailable offline");
return;
}
let diags: Vec<&str> = e.lines().filter(|l| l.contains("error")).take(6).collect();
panic!(
"{label} emits Rust that does not compile:\n {}\n--- source ---\n{src}",
diags.join("\n ")
);
}
fn lit(g: &mut Graph, ty: &str, v: &str) -> flowrs_core::NodeId {
let n = g.add_node(ty, 0.0, 0.0);
g.nodes
.get_mut(&n)
.unwrap()
.properties
.insert("value".into(), v.into());
n
}
#[test]
fn the_collection_channel_and_struct_nodes_compile_when_wired() {
// HashMapNew -> HashMapInsert -> HashMapGet
let mut g = Graph::new();
let m = g.add_node("HashMapNew", 0.0, 0.0);
let k = lit(&mut g, "StringLiteral", "alpha");
let v = lit(&mut g, "IntLiteral", "7");
let ins = g.add_node("HashMapInsert", 0.0, 0.0);
g.add_wire(m, "map", ins, "map", WireMode::Move).unwrap();
g.add_wire(k, "value", ins, "key", WireMode::Move).unwrap();
g.add_wire(v, "value", ins, "value", WireMode::Move)
.unwrap();
check("HashMapInsert(wired)", &g);
let k2 = lit(&mut g, "StringLiteral", "alpha");
let get = g.add_node("HashMapGet", 0.0, 0.0);
g.add_wire(ins, "map", get, "map", WireMode::Move).unwrap();
g.add_wire(k2, "value", get, "key", WireMode::Move).unwrap();
check("HashMapGet(wired)", &g);
// ChannelNew -> Send / Recv
let mut g = Graph::new();
let ch = g.add_node("ChannelNew", 0.0, 0.0);
let val = lit(&mut g, "IntLiteral", "3");
let snd = g.add_node("Send", 0.0, 0.0);
g.add_wire(ch, "sender", snd, "sender", WireMode::Move)
.unwrap();
g.add_wire(val, "value", snd, "value", WireMode::Move)
.unwrap();
let rcv = g.add_node("Recv", 0.0, 0.0);
g.add_wire(ch, "receiver", rcv, "receiver", WireMode::Move)
.unwrap();
check("Send+Recv(wired)", &g);
// StructNew -> FieldGet / FieldSet
let mut g = Graph::new();
let sn = g.add_node("StructNew", 0.0, 0.0);
{
let p = &mut g.nodes.get_mut(&sn).unwrap().properties;
p.insert("type".into(), "Settings".into());
p.insert("gain".into(), "3".into());
p.insert("name".into(), "probe".into());
}
let fg = g.add_node("FieldGet", 0.0, 0.0);
g.nodes
.get_mut(&fg)
.unwrap()
.properties
.insert("field".into(), "gain".into());
g.add_wire(sn, "instance", fg, "instance", WireMode::Move)
.unwrap();
check("FieldGet(wired)", &g);
let mut g2 = Graph::new();
let sn2 = g2.add_node("StructNew", 0.0, 0.0);
{
let p = &mut g2.nodes.get_mut(&sn2).unwrap().properties;
p.insert("type".into(), "Settings".into());
p.insert("gain".into(), "3".into());
}
let nv = lit(&mut g2, "IntLiteral", "9");
let fs = g2.add_node("FieldSet", 0.0, 0.0);
g2.nodes
.get_mut(&fs)
.unwrap()
.properties
.insert("field".into(), "gain".into());
g2.add_wire(sn2, "instance", fs, "instance", WireMode::Move)
.unwrap();
g2.add_wire(nv, "value", fs, "value", WireMode::Move)
.unwrap();
check("FieldSet(wired)", &g2);
}