mod common;
use common::{TestRuntime, TestValue};
const CHAIN_LEN: usize = 5_000;
fn build_linear_chain(rt: &TestRuntime, len: usize) -> Vec<graphrefly_core::NodeId> {
let mut ids = Vec::with_capacity(len);
let s = rt.state(Some(TestValue::Int(0)));
ids.push(s.id);
let mut prev = s.id;
for _ in 1..len {
let next = rt.derived(&[prev], |deps| match &deps[0] {
TestValue::Int(n) => Some(TestValue::Int(*n)),
_ => None,
});
ids.push(next);
prev = next;
}
ids
}
#[test]
fn complete_cascades_through_5000_node_chain_without_stack_overflow() {
let rt = TestRuntime::new();
let ids = build_linear_chain(&rt, CHAIN_LEN);
let leaf_rec = rt.subscribe_recorder(*ids.last().expect("non-empty chain"));
let baseline = leaf_rec.snapshot().len();
rt.core.complete(ids[0]);
let post = leaf_rec.snapshot();
assert!(
post.iter()
.skip(baseline)
.any(|e| matches!(e, common::RecordedEvent::Complete)),
"leaf should observe COMPLETE after cascade through 5000-node chain"
);
}
#[test]
fn teardown_cascades_through_5000_node_chain_without_stack_overflow() {
let rt = TestRuntime::new();
let ids = build_linear_chain(&rt, CHAIN_LEN);
let leaf_rec = rt.subscribe_recorder(*ids.last().expect("non-empty chain"));
let baseline = leaf_rec.snapshot().len();
rt.core.teardown(ids[0]);
let post = leaf_rec.snapshot();
let new = &post[baseline..];
assert!(
new.iter()
.any(|e| matches!(e, common::RecordedEvent::Teardown)),
"leaf should observe TEARDOWN after cascade through 5000-node chain"
);
assert!(
new.iter()
.any(|e| matches!(e, common::RecordedEvent::Complete)),
"leaf should observe COMPLETE (auto-prepended by teardown_inner) after cascade"
);
}
#[test]
fn invalidate_cascades_through_5000_node_chain_without_stack_overflow() {
let rt = TestRuntime::new();
let ids = build_linear_chain(&rt, CHAIN_LEN);
let leaf_rec = rt.subscribe_recorder(*ids.last().expect("non-empty chain"));
let baseline = leaf_rec.snapshot().len();
rt.core.invalidate(ids[0]);
let post = leaf_rec.snapshot();
let new = &post[baseline..];
assert!(
new.iter()
.any(|e| matches!(e, common::RecordedEvent::Invalidate)),
"leaf should observe INVALIDATE after cascade through 5000-node chain"
);
for &id in &ids {
assert_eq!(
rt.core.cache_of(id),
graphrefly_core::NO_HANDLE,
"node {id:?} cache should be cleared after invalidate cascade"
);
}
}
#[test]
fn error_cascades_through_5000_node_chain_without_stack_overflow() {
let rt = TestRuntime::new();
let ids = build_linear_chain(&rt, CHAIN_LEN);
let leaf_rec = rt.subscribe_recorder(*ids.last().expect("non-empty chain"));
let baseline = leaf_rec.snapshot().len();
let err = rt.binding.intern(TestValue::Str("oops".to_string()));
rt.core.error(ids[0], err);
let post = leaf_rec.snapshot();
let new = &post[baseline..];
assert!(
new.iter()
.any(|e| matches!(e, common::RecordedEvent::Error(_))),
"leaf should observe ERROR after cascade through 5000-node chain"
);
}