use graphrefly_core::{
EqualsMode, NodeFnOrOp, NodeId, NodeOpts, NodeRegistration, OperatorOp, OperatorOpts,
PausableMode, RegisterError, SetPausableModeError, NO_HANDLE,
};
mod common;
use common::{TestRuntime, TestValue};
#[test]
fn register_operator_with_zero_deps_errors_operator_without_deps() {
let rt = TestRuntime::new();
let fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result =
rt.core()
.register_operator(&[], OperatorOp::Map { fn_id }, OperatorOpts::default());
assert_eq!(result, Err(RegisterError::OperatorWithoutDeps));
}
#[test]
fn register_with_initial_on_non_state_shape_errors() {
let rt = TestRuntime::new();
let s = rt.state(Some(TestValue::Int(0)));
let fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let h = rt.binding.intern(TestValue::Int(99));
let result = rt.core().register(NodeRegistration {
deps: vec![s.id],
fn_or_op: Some(NodeFnOrOp::Fn(fn_id)),
opts: NodeOpts {
initial: h,
..NodeOpts::default()
},
});
assert_eq!(result, Err(RegisterError::InitialOnlyForStateNodes));
}
#[test]
fn register_derived_with_unknown_dep_errors() {
let rt = TestRuntime::new();
let bogus = NodeId::new(99_999);
let fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt
.core()
.register_derived(&[bogus], fn_id, EqualsMode::Identity, false);
assert_eq!(result, Err(RegisterError::UnknownDep(bogus)));
}
#[test]
fn register_operator_scan_with_no_handle_seed_errors() {
let rt = TestRuntime::new();
let s = rt.state(Some(TestValue::Int(0)));
let dummy_fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt.core().register_operator(
&[s.id],
OperatorOp::Scan {
fn_id: dummy_fn_id,
seed: NO_HANDLE,
},
OperatorOpts::default(),
);
assert_eq!(result, Err(RegisterError::OperatorSeedSentinel));
}
#[test]
fn register_operator_reduce_with_no_handle_seed_errors() {
let rt = TestRuntime::new();
let s = rt.state(Some(TestValue::Int(0)));
let dummy_fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt.core().register_operator(
&[s.id],
OperatorOp::Reduce {
fn_id: dummy_fn_id,
seed: NO_HANDLE,
},
OperatorOpts::default(),
);
assert_eq!(result, Err(RegisterError::OperatorSeedSentinel));
}
#[test]
fn set_pausable_mode_on_unknown_node_errors() {
let rt = TestRuntime::new();
let known = rt.state(Some(TestValue::Int(0)));
rt.core()
.set_pausable_mode(known.id, PausableMode::Off)
.expect("baseline mode set");
let bogus = NodeId::new(99_999);
let result = rt.core().set_pausable_mode(bogus, PausableMode::ResumeAll);
assert_eq!(result, Err(SetPausableModeError::UnknownNode(bogus)));
rt.core()
.set_pausable_mode(known.id, PausableMode::Off)
.expect("re-set Off on known node");
}
#[test]
fn register_operator_scan_with_unknown_dep_does_not_leak_seed() {
let rt = TestRuntime::new();
let bogus = NodeId::new(99_999);
let seed = rt.binding.intern(TestValue::Int(7));
let pre_refcount = rt.binding.refcount_of(seed);
let dummy_fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt.core().register_operator(
&[bogus],
OperatorOp::Scan {
fn_id: dummy_fn_id,
seed,
},
OperatorOpts::default(),
);
assert_eq!(result, Err(RegisterError::UnknownDep(bogus)));
let post_refcount = rt.binding.refcount_of(seed);
assert_eq!(
post_refcount, pre_refcount,
"seed refcount must return to pre-call value on Err — \
scratch's retain_handle in Phase 2 must be paired with a \
release_handles in the early-return path of Phase 3"
);
}
#[test]
fn register_operator_reduce_with_terminal_dep_does_not_leak_seed() {
let rt = TestRuntime::new();
let s = rt.state(Some(TestValue::Int(1)));
let _rec = rt.subscribe_recorder(s.id);
rt.core().complete(s.id);
let seed = rt.binding.intern(TestValue::Int(13));
let pre_refcount = rt.binding.refcount_of(seed);
let dummy_fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt.core().register_operator(
&[s.id],
OperatorOp::Reduce {
fn_id: dummy_fn_id,
seed,
},
OperatorOpts::default(),
);
assert_eq!(result, Err(RegisterError::TerminalDep(s.id)));
let post_refcount = rt.binding.refcount_of(seed);
assert_eq!(
post_refcount, pre_refcount,
"seed refcount must return to pre-call value on TerminalDep Err"
);
}
#[test]
fn register_operator_scan_sentinel_seed_takes_no_retain() {
let rt = TestRuntime::new();
let s = rt.state(Some(TestValue::Int(0)));
let baseline_live = rt.binding.live_handles();
let dummy_fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt.core().register_operator(
&[s.id],
OperatorOp::Scan {
fn_id: dummy_fn_id,
seed: NO_HANDLE,
},
OperatorOpts::default(),
);
assert_eq!(result, Err(RegisterError::OperatorSeedSentinel));
assert_eq!(
rt.binding.live_handles(),
baseline_live,
"OperatorSeedSentinel must return BEFORE retain_handle so live handle count is unchanged"
);
}
#[test]
fn register_err_does_not_add_node_to_graph() {
let rt = TestRuntime::new();
let _s = rt.state(Some(TestValue::Int(0)));
let pre_count = rt.core().node_count();
let bogus = NodeId::new(99_999);
let fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt
.core()
.register_derived(&[bogus], fn_id, EqualsMode::Identity, false);
assert_eq!(result, Err(RegisterError::UnknownDep(bogus)));
assert_eq!(
rt.core().node_count(),
pre_count,
"node_count must be unchanged after a failed register"
);
}
#[test]
fn register_operator_last_with_default_with_unknown_dep_does_not_leak_default() {
let rt = TestRuntime::new();
let bogus = NodeId::new(99_999);
let default = rt.binding.intern(TestValue::Int(42));
let pre_refcount = rt.binding.refcount_of(default);
let result = rt.core().register_operator(
&[bogus],
OperatorOp::Last { default },
OperatorOpts::default(),
);
assert_eq!(result, Err(RegisterError::UnknownDep(bogus)));
let post_refcount = rt.binding.refcount_of(default);
assert_eq!(
post_refcount, pre_refcount,
"Last `default` refcount must return to pre-call value on Err"
);
}
#[test]
fn op_without_deps_takes_precedence_over_seed_sentinel() {
let rt = TestRuntime::new();
let dummy_fn_id = rt
.binding
.register_fn(|deps: &[TestValue]| deps.first().cloned());
let result = rt.core().register_operator(
&[],
OperatorOp::Scan {
fn_id: dummy_fn_id,
seed: NO_HANDLE,
},
OperatorOpts::default(),
);
assert_eq!(
result,
Err(RegisterError::OperatorWithoutDeps),
"Phase 1 (OperatorWithoutDeps) must short-circuit before Phase 2 (OperatorSeedSentinel)"
);
}