123 lines
4.9 KiB
Rust
123 lines
4.9 KiB
Rust
//! The closure-bodied, user-code, match, game and LOOP nodes must emit Rust that builds.
|
|
//!
|
|
//! Emitting *something* is not the bar — `node_coverage` measures that. This compiles the
|
|
//! whole program, and additionally asserts that a loop's body lands INSIDE its braces,
|
|
//! which is the part a compile check alone would not catch: a body emitted after the
|
|
//! closing brace still compiles, and is silently not a loop body at all.
|
|
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_phase1");
|
|
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);
|
|
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---\n{src}---",
|
|
diags.join("\n ")
|
|
);
|
|
}
|
|
|
|
fn prop(g: &mut Graph, n: flowrs_core::NodeId, k: &str, v: &str) {
|
|
g.nodes
|
|
.get_mut(&n)
|
|
.unwrap()
|
|
.properties
|
|
.insert(k.into(), v.into());
|
|
}
|
|
|
|
#[test]
|
|
fn closure_usercode_match_and_game_nodes_compile() {
|
|
// Standalone: no inputs needed.
|
|
for ty in ["CodeBlockNode", "Sprite", "Paddle", "Spawn"] {
|
|
let mut g = Graph::new();
|
|
g.add_node(ty, 0.0, 0.0);
|
|
check(ty, &g);
|
|
}
|
|
// CodeBlock with real source.
|
|
let mut g = Graph::new();
|
|
let c = g.add_node("CodeBlock", 0.0, 0.0);
|
|
prop(&mut g, c, "src", "let a = 2; a * 3");
|
|
check("CodeBlock(src)", &g);
|
|
|
|
// Iterator adapters, wired from Range.
|
|
for (ty, src) in [("Map", "x * 2"), ("Filter", "*x > 2")] {
|
|
let mut g = Graph::new();
|
|
let a = g.add_node("IntLiteral", 0.0, 0.0);
|
|
prop(&mut g, a, "value", "0");
|
|
let b = g.add_node("IntLiteral", 0.0, 0.0);
|
|
prop(&mut g, b, "value", "5");
|
|
let r = g.add_node("Range", 0.0, 0.0);
|
|
g.add_wire(a, "value", r, "start", WireMode::Move).unwrap();
|
|
g.add_wire(b, "value", r, "end", WireMode::Move).unwrap();
|
|
let n = g.add_node(ty, 0.0, 0.0);
|
|
prop(&mut g, n, "src", src);
|
|
g.add_wire(r, "iterator", n, "iterator", WireMode::Move)
|
|
.unwrap();
|
|
check(&format!("{ty}(wired)"), &g);
|
|
}
|
|
// Fold needs an init too.
|
|
let mut g = Graph::new();
|
|
let a = g.add_node("IntLiteral", 0.0, 0.0);
|
|
prop(&mut g, a, "value", "0");
|
|
let b = g.add_node("IntLiteral", 0.0, 0.0);
|
|
prop(&mut g, b, "value", "5");
|
|
let r = g.add_node("Range", 0.0, 0.0);
|
|
g.add_wire(a, "value", r, "start", WireMode::Move).unwrap();
|
|
g.add_wire(b, "value", r, "end", WireMode::Move).unwrap();
|
|
let init = g.add_node("IntLiteral", 0.0, 0.0);
|
|
prop(&mut g, init, "value", "0");
|
|
let f = g.add_node("Fold", 0.0, 0.0);
|
|
prop(&mut g, f, "src", "acc + x as i64");
|
|
g.add_wire(r, "iterator", f, "iterator", WireMode::Move)
|
|
.unwrap();
|
|
g.add_wire(init, "value", f, "init", WireMode::Move)
|
|
.unwrap();
|
|
check("Fold(wired)", &g);
|
|
|
|
// Match over a literal.
|
|
for ty in ["Match", "MatchNode"] {
|
|
let mut g = Graph::new();
|
|
let v = g.add_node("IntLiteral", 0.0, 0.0);
|
|
prop(&mut g, v, "value", "2");
|
|
let m = g.add_node(ty, 0.0, 0.0);
|
|
prop(&mut g, m, "src", "1 => \"one\", _ => \"other\"");
|
|
g.add_wire(v, "value", m, "value", WireMode::Move).unwrap();
|
|
check(&format!("{ty}(wired)"), &g);
|
|
}
|
|
}
|