141 lines
5.6 KiB
Rust
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);
|
|
}
|