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

159 lines
4.7 KiB
Rust

//! What the graph computes, versus where it is drawn.
//!
//! `Graph::semantic_key` is a cache key: the editor memoizes T0 evaluation on it so that
//! dragging a node does not re-run the dataflow at pointer rate. A key that misses a
//! semantic field serves a STALE value, which is a worse failure than the slowness it
//! exists to fix — so every field that changes what the graph computes gets a test that
//! fails if the key ignores it.
use flowrs_core::graph::ir::WireMode;
use flowrs_core::{Graph, NodeId};
fn two_nodes() -> (Graph, NodeId, NodeId) {
let mut g = Graph::new();
let a = g.add_node("IntLiteral", 0.0, 0.0);
let b = g.add_node("PrintLn", 100.0, 0.0);
(g, a, b)
}
#[test]
fn moving_a_node_does_not_change_what_it_computes() {
let (mut g, a, _) = two_nodes();
let before = g.semantic_key();
g.get_node_mut(a).unwrap().position = (900.0, -450.0);
assert_eq!(
g.semantic_key(),
before,
"position is not an input to any computation; if it moves the key, every drag \
re-runs the graph",
);
}
#[test]
fn changing_a_property_changes_the_key() {
let (mut g, a, _) = two_nodes();
let before = g.semantic_key();
g.get_node_mut(a)
.unwrap()
.properties
.insert("value".into(), "7".into());
assert_ne!(
g.semantic_key(),
before,
"a literal's value is what it computes"
);
}
#[test]
fn a_property_changing_value_changes_the_key() {
let (mut g, a, _) = two_nodes();
g.get_node_mut(a)
.unwrap()
.properties
.insert("value".into(), "7".into());
let before = g.semantic_key();
g.get_node_mut(a)
.unwrap()
.properties
.insert("value".into(), "8".into());
assert_ne!(
g.semantic_key(),
before,
"7 and 8 must not hash alike — the classic stale-cache bug is a key that only \
notices a property appearing, not changing",
);
}
#[test]
fn connecting_a_wire_changes_the_key() {
let (mut g, a, b) = two_nodes();
let before = g.semantic_key();
g.add_wire(a, "value", b, "value", WireMode::Move)
.expect("the two default ports should connect");
assert_ne!(g.semantic_key(), before, "a wire changes what flows where");
}
#[test]
fn a_wires_mode_changes_the_key() {
let (mut g, a, b) = two_nodes();
let w = g
.add_wire(a, "value", b, "value", WireMode::Move)
.expect("connect");
let before = g.semantic_key();
g.wires.get_mut(&w).unwrap().mode = WireMode::Borrow;
assert_ne!(
g.semantic_key(),
before,
"move vs borrow is a real difference in the generated program",
);
}
#[test]
fn adding_and_removing_a_node_changes_the_key() {
let (mut g, _, _) = two_nodes();
let before = g.semantic_key();
let c = g.add_node("FloatLiteral", 0.0, 0.0);
let with_node = g.semantic_key();
assert_ne!(with_node, before, "a new node changes the graph");
g.remove_node(c);
assert_eq!(
g.semantic_key(),
before,
"removing it should return the key to where it was",
);
}
#[test]
fn the_key_is_stable_across_repeated_calls() {
let (g, _, _) = two_nodes();
assert_eq!(
g.semantic_key(),
g.semantic_key(),
"an unstable key would make the cache never hit — HashMap iteration order is the \
usual culprit, which is why nodes, wires and properties are all sorted first",
);
}
/// The cache holds one entry, so a graph that returns to a previous shape must return to
/// its previous key or the memo silently stops hitting.
#[test]
fn an_unstable_key_would_show_up_as_order_dependence() {
let mut a = Graph::new();
let n1 = a.add_node("IntLiteral", 0.0, 0.0);
let n2 = a.add_node("FloatLiteral", 0.0, 0.0);
a.get_node_mut(n1)
.unwrap()
.properties
.insert("x".into(), "1".into());
a.get_node_mut(n1)
.unwrap()
.properties
.insert("y".into(), "2".into());
a.get_node_mut(n2)
.unwrap()
.properties
.insert("z".into(), "3".into());
let mut b = Graph::new();
let m1 = b.add_node("IntLiteral", 0.0, 0.0);
let m2 = b.add_node("FloatLiteral", 0.0, 0.0);
// Same content, inserted in a different order.
b.get_node_mut(m2)
.unwrap()
.properties
.insert("z".into(), "3".into());
b.get_node_mut(m1)
.unwrap()
.properties
.insert("y".into(), "2".into());
b.get_node_mut(m1)
.unwrap()
.properties
.insert("x".into(), "1".into());
assert_eq!(
a.semantic_key(),
b.semantic_key(),
"two graphs with identical content must key alike regardless of insertion order",
);
}