use crate::engine::named_range::{NameScope, NamedDefinition};
use crate::engine::{
CycleConfig, CyclePolicy, Engine, EvalConfig, FormulaIngestBatch, FormulaIngestRecord,
FormulaPlaneMode,
};
use crate::test_workbook::TestWorkbook;
use formualizer_common::{ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::parse;
use std::sync::Arc;
fn iterate_cfg(max_iterations: u32, max_change: f64) -> EvalConfig {
EvalConfig {
temporal_egress: crate::engine::TemporalEgress::Serial,
..EvalConfig::default().with_cycle(CycleConfig::iterate(max_iterations, max_change))
}
}
fn deterministic_iterate_cfg(max_iterations: u32, max_change: f64) -> EvalConfig {
use crate::engine::DeterministicMode;
use crate::timezone::TimeZoneSpec;
let mut cfg = iterate_cfg(max_iterations, max_change);
cfg.deterministic_mode = DeterministicMode::Enabled {
timestamp_utc: chrono::DateTime::parse_from_rfc3339("2026-06-09T12:00:00Z")
.unwrap()
.with_timezone(&chrono::Utc),
timezone: TimeZoneSpec::Utc,
};
cfg
}
fn iterate_engine(max_iterations: u32, max_change: f64) -> Engine<TestWorkbook> {
Engine::new(TestWorkbook::new(), iterate_cfg(max_iterations, max_change))
}
fn set_formula(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, f: &str) {
engine
.set_cell_formula(sheet, row, col, parse(f).expect("parse"))
.expect("set formula");
}
fn set_value(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, v: LiteralValue) {
engine
.set_cell_value(sheet, row, col, v)
.expect("set value");
}
fn num(engine: &Engine<TestWorkbook>, sheet: &str, row: u32, col: u32) -> f64 {
match engine.get_cell_value(sheet, row, col) {
Some(LiteralValue::Number(n)) => n,
Some(LiteralValue::Int(i)) => i as f64,
other => panic!("expected number at {sheet} r{row}c{col}, got {other:?}"),
}
}
fn err_kind(engine: &Engine<TestWorkbook>, sheet: &str, row: u32, col: u32) -> ExcelErrorKind {
match engine.get_cell_value(sheet, row, col) {
Some(LiteralValue::Error(e)) => e.kind,
other => panic!("expected error at {sheet} r{row}c{col}, got {other:?}"),
}
}
fn reuse_telemetry(engine: &Engine<TestWorkbook>) -> (usize, usize, usize) {
let t = engine.last_cycle_telemetry();
(t.iterated_sccs, t.reused_sccs, t.reused_scc_members)
}
fn build_exact_pair(engine: &mut Engine<TestWorkbook>) {
set_formula(engine, "Sheet1", 1, 1, "=IF(B1>2,7,B1+1)");
set_formula(engine, "Sheet1", 1, 2, "=A1");
}
fn build_input_driven_pair(engine: &mut Engine<TestWorkbook>, row: u32, input: f64) {
set_value(engine, "Sheet1", row, 4, LiteralValue::Number(input));
set_formula(
engine,
"Sheet1",
row,
1,
&format!("=IF(B{row}>=D{row},D{row},B{row}+1)"),
);
set_formula(engine, "Sheet1", row, 2, &format!("=A{row}"));
}
static EPOCH_LOCK: std::sync::RwLock<()> = std::sync::RwLock::new(());
fn epoch_stable() -> std::sync::RwLockReadGuard<'static, ()> {
EPOCH_LOCK.read().unwrap_or_else(|e| e.into_inner())
}
fn epoch_bumper() -> std::sync::RwLockWriteGuard<'static, ()> {
EPOCH_LOCK.write().unwrap_or_else(|e| e.into_inner())
}
#[test]
fn exact_fixed_point_is_retained_and_reused_on_the_next_recalc() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
build_exact_pair(&mut engine);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 7.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 7.0);
let t = engine.last_cycle_telemetry();
assert_eq!(t.iterated_sccs, 1);
assert_eq!(t.converged_sccs, 1);
assert_eq!(t.capped_sccs, 0);
assert_eq!(t.settle_passes_total, 5);
assert_eq!(t.reused_sccs, 0, "nothing was retained before this recalc");
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
let t = engine.last_cycle_telemetry();
assert_eq!(t.settle_passes_total, 0, "no pass may run");
assert_eq!(t.converged_sccs, 0);
assert_eq!(t.capped_sccs, 0);
assert_eq!(num(&engine, "Sheet1", 1, 1), 7.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 7.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
}
#[test]
fn editing_one_sccs_input_reruns_only_that_scc() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
build_input_driven_pair(&mut engine, 1, 3.0);
build_input_driven_pair(&mut engine, 3, 4.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 3, 1), 4.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 4);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 2, 4));
set_value(&mut engine, "Sheet1", 1, 4, LiteralValue::Number(6.0));
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 1, 2));
assert_eq!(num(&engine, "Sheet1", 1, 1), 6.0, "SCC #1 re-converged");
assert_eq!(num(&engine, "Sheet1", 1, 2), 6.0);
assert_eq!(num(&engine, "Sheet1", 3, 1), 4.0, "SCC #3 untouched");
assert_eq!(num(&engine, "Sheet1", 3, 2), 4.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 4);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 2, 4));
}
#[test]
fn error_valued_fixed_point_is_retained_and_reused() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 2, "=1/C1");
set_formula(&mut engine, "Sheet1", 1, 3, "=B1");
engine.evaluate_all().unwrap();
assert_eq!(err_kind(&engine, "Sheet1", 1, 2), ExcelErrorKind::Div);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
assert_eq!(err_kind(&engine, "Sheet1", 1, 2), ExcelErrorKind::Div);
assert_eq!(err_kind(&engine, "Sheet1", 1, 3), ExcelErrorKind::Div);
}
#[test]
fn text_fixed_point_is_retained_and_reused() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 2, "=IF(C1=\"\",\"x\",C1)");
set_formula(&mut engine, "Sheet1", 1, 3, "=B1");
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Text("x".to_string()))
);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3),
Some(LiteralValue::Text("x".to_string()))
);
}
#[test]
fn boolean_fixed_point_is_retained_and_reused() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 2, "=OR(C1,TRUE)");
set_formula(&mut engine, "Sheet1", 1, 3, "=B1");
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Boolean(true))
);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3),
Some(LiteralValue::Boolean(true))
);
}
#[test]
fn demand_driven_requests_serve_retained_members_without_scc_work() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
build_exact_pair(&mut engine);
set_formula(&mut engine, "Sheet1", 1, 4, "=B1*3"); engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 4), 21.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_cell("Sheet1", 1, 1).unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
assert_eq!(num(&engine, "Sheet1", 1, 1), 7.0);
engine.evaluate_cell("Sheet1", 1, 4).unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
assert_eq!(num(&engine, "Sheet1", 1, 4), 21.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
}
#[test]
fn a_hundred_no_change_recalcs_do_no_work_and_keep_the_values() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
build_exact_pair(&mut engine);
engine.evaluate_all().unwrap();
let retained = engine.baseline_stats().retained_scc_members;
assert_eq!(retained, 2);
for i in 0..100 {
engine.evaluate_all().unwrap();
let t = engine.last_cycle_telemetry();
assert_eq!(t.settle_passes_total, 0, "recalc {i}");
assert_eq!(t.iterated_sccs, 0, "recalc {i}");
assert_eq!(t.reused_sccs, 1, "recalc {i}");
assert_eq!(num(&engine, "Sheet1", 1, 1), 7.0, "recalc {i}");
assert_eq!(num(&engine, "Sheet1", 1, 2), 7.0, "recalc {i}");
assert_eq!(
engine.baseline_stats().retained_scc_members,
retained,
"recalc {i}"
);
}
}
#[test]
fn retained_scc_feeds_a_formula_plane_span_family_in_authoritative_mode() {
let _epoch = epoch_stable();
let cfg = iterate_cfg(100, 0.001)
.with_formula_plane_mode(FormulaPlaneMode::AuthoritativeExperimental);
let mut engine = Engine::new(TestWorkbook::default(), cfg);
set_value(&mut engine, "Sheet1", 1, 7, LiteralValue::Number(3.0));
set_formula(&mut engine, "Sheet1", 1, 5, "=IF(F1>=G1,G1,F1+1)");
set_formula(&mut engine, "Sheet1", 1, 6, "=E1");
set_formula(&mut engine, "Sheet1", 1, 8, "=E1");
let mut records = Vec::new();
let mut independent = Vec::new();
for row in 1..=120u32 {
set_value(
&mut engine,
"Sheet1",
row,
1,
LiteralValue::Number(row as f64),
);
set_value(&mut engine, "Sheet1", row, 2, LiteralValue::Number(2.0));
let f = format!("=B{row}*2+A{row}+$H$1");
let ast = parse(&f).unwrap();
let ast_id = engine.intern_formula_ast(&ast);
records.push(FormulaIngestRecord::new(
row,
3,
ast_id,
Some(Arc::<str>::from(f.as_str())),
));
let g = format!("=A{row}*2");
let ast = parse(&g).unwrap();
let ast_id = engine.intern_formula_ast(&ast);
independent.push(FormulaIngestRecord::new(
row,
10,
ast_id,
Some(Arc::<str>::from(g.as_str())),
));
}
engine
.ingest_formula_batches(vec![FormulaIngestBatch::new(
"Sheet1",
records.into_iter().chain(independent).collect(),
)])
.unwrap();
assert_eq!(engine.baseline_stats().formula_plane_active_span_count, 2);
engine.evaluate_all().unwrap();
let st = engine.baseline_stats();
assert_eq!(st.formula_plane_cycle_member_span_demotions, 1);
assert_eq!(st.formula_plane_active_span_count, 1);
assert_eq!(num(&engine, "Sheet1", 120, 10), 240.0);
assert_eq!(num(&engine, "Sheet1", 1, 5), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 3), 8.0); assert_eq!(num(&engine, "Sheet1", 120, 3), 4.0 + 120.0 + 3.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
assert_eq!(num(&engine, "Sheet1", 120, 3), 4.0 + 120.0 + 3.0);
set_value(&mut engine, "Sheet1", 1, 7, LiteralValue::Number(9.0));
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 5), 9.0);
assert_eq!(num(&engine, "Sheet1", 1, 6), 9.0);
assert_eq!(num(&engine, "Sheet1", 1, 3), 4.0 + 1.0 + 9.0);
assert_eq!(num(&engine, "Sheet1", 120, 3), 4.0 + 120.0 + 9.0);
}
fn assert_never_retained(name: &str, build: impl Fn(&mut Engine<TestWorkbook>), cfg: EvalConfig) {
let mut engine = Engine::new(TestWorkbook::new(), cfg);
build(&mut engine);
engine.evaluate_all().unwrap();
assert_eq!(
engine.baseline_stats().retained_scc_members,
0,
"{name}: nothing may be retained"
);
engine.evaluate_all().unwrap();
assert_eq!(
reuse_telemetry(&engine),
(1, 0, 0),
"{name}: the SCC must re-run"
);
}
#[test]
fn tolerance_only_convergence_is_not_retained() {
let _epoch = epoch_stable();
assert_never_retained(
"tolerance-only",
|engine| {
set_formula(engine, "Sheet1", 1, 1, "=0.5*B1+10");
set_formula(engine, "Sheet1", 1, 2, "=A1");
},
iterate_cfg(100, 0.001),
);
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=0.5*B1+10");
set_formula(&mut engine, "Sheet1", 1, 2, "=A1");
engine.evaluate_all().unwrap();
let t = engine.last_cycle_telemetry();
assert_eq!(t.converged_sccs, 1);
assert!(
t.max_abs_delta_at_stop > 0.0 && t.max_abs_delta_at_stop < 0.001,
"residual {}",
t.max_abs_delta_at_stop
);
assert!((num(&engine, "Sheet1", 1, 1) - 20.0).abs() < 0.001);
}
#[test]
fn capped_divergent_pair_is_not_retained() {
let _epoch = epoch_stable();
assert_never_retained(
"divergent pair",
|engine| {
set_formula(engine, "Sheet1", 1, 1, "=A2+1");
set_formula(engine, "Sheet1", 2, 1, "=A1+1");
},
iterate_cfg(10, 0.001),
);
}
#[test]
fn accumulator_still_adds_its_input_exactly_once_per_recalc() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(1, 0.001);
set_value(&mut engine, "Sheet1", 1, 1, LiteralValue::Number(5.0));
set_formula(&mut engine, "Sheet1", 1, 2, "=B1+A1");
for (recalc, expected) in [(1u32, 5.0), (2, 10.0), (3, 15.0)] {
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), expected, "recalc {recalc}");
assert_eq!(reuse_telemetry(&engine), (1, 0, 0), "recalc {recalc}");
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
}
}
#[test]
fn volatile_member_is_never_retained_even_on_an_exact_fixed_point() {
let _epoch = epoch_stable();
assert_never_retained(
"RAND in cycle",
|engine| {
set_formula(engine, "Sheet1", 1, 2, "=0*RAND()+0*C1");
set_formula(engine, "Sheet1", 1, 3, "=B1");
},
iterate_cfg(100, 0.001),
);
assert_never_retained(
"NOW in cycle (deterministic)",
|engine| {
set_formula(engine, "Sheet1", 1, 2, "=NOW()+0*C1");
set_formula(engine, "Sheet1", 1, 3, "=B1");
},
deterministic_iterate_cfg(100, 0.001),
);
}
#[test]
fn dynamic_reference_member_is_never_retained() {
let _epoch = epoch_stable();
assert_never_retained(
"INDIRECT in cycle",
|engine| {
set_formula(engine, "Sheet1", 1, 1, "=0*INDIRECT(\"B1\")");
set_formula(engine, "Sheet1", 1, 2, "=A1");
},
iterate_cfg(100, 0.001),
);
assert_never_retained(
"OFFSET in cycle",
|engine| {
set_formula(engine, "Sheet1", 1, 1, "=0*OFFSET(A1,0,1)");
set_formula(engine, "Sheet1", 1, 2, "=A1");
},
iterate_cfg(100, 0.001),
);
}
#[test]
fn nan_identity_convergence_is_not_retained() {
let _epoch = epoch_stable();
use crate::args::ArgSchema;
use crate::function::{FnCaps, Function};
use crate::traits::{ArgumentHandle, FunctionContext};
#[derive(Debug)]
struct NanProbeFn;
impl Function for NanProbeFn {
fn caps(&self) -> FnCaps {
FnCaps::empty()
}
fn name(&self) -> &'static str {
"NANPROBE"
}
fn arg_schema(&self) -> &'static [ArgSchema] {
static SCHEMA: std::sync::LazyLock<Vec<ArgSchema>> =
std::sync::LazyLock::new(|| vec![ArgSchema::any()]);
&SCHEMA
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
let _ = args[0].value()?;
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
f64::NAN,
)))
}
}
let wb = TestWorkbook::new().with_function(Arc::new(NanProbeFn));
let mut engine = Engine::new(wb, iterate_cfg(100, 0.001));
set_formula(&mut engine, "Sheet1", 1, 1, "=NANPROBE(B1)");
set_formula(&mut engine, "Sheet1", 1, 2, "=A1");
engine.evaluate_all().unwrap();
let t = engine.last_cycle_telemetry();
assert!(
t.nan_converged > 0,
"expected NaN identity, telemetry {t:?}"
);
assert_eq!(t.converged_sccs, 1);
assert_eq!(t.capped_sccs, 0);
assert_eq!(
engine.baseline_stats().retained_scc_members,
0,
"NaN identity is explicitly excluded from retention"
);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
}
#[test]
fn array_result_member_excluded_mid_iteration_is_not_retained() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=SEQUENCE(2)+0*A2");
set_formula(&mut engine, "Sheet1", 2, 1, "=A1");
engine.evaluate_all().unwrap();
assert_eq!(err_kind(&engine, "Sheet1", 1, 1), ExcelErrorKind::Circ);
assert_eq!(err_kind(&engine, "Sheet1", 2, 1), ExcelErrorKind::Circ);
assert_eq!(
engine.baseline_stats().retained_scc_members,
0,
"an excluded member never reaches the retention check"
);
engine.evaluate_all().unwrap();
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
assert_eq!(err_kind(&engine, "Sheet1", 1, 1), ExcelErrorKind::Circ);
}
fn retained_engine(input: f64) -> Engine<TestWorkbook> {
let mut engine = iterate_engine(100, 0.001);
build_input_driven_pair(&mut engine, 1, input);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), input);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
engine
}
#[test]
fn external_precedent_edit_reruns_the_retained_scc() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_value(&mut engine, "Sheet1", 1, 4, LiteralValue::Number(8.0));
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 1), 8.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 8.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2), "retained again");
}
#[test]
fn same_value_rewrite_of_a_precedent_still_reruns_the_scc() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_value(&mut engine, "Sheet1", 1, 4, LiteralValue::Number(3.0));
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
}
#[test]
fn formula_change_inside_a_member_reruns_the_retained_scc() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_formula(&mut engine, "Sheet1", 1, 2, "=A1*10");
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 30.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
}
#[test]
fn overwriting_a_member_with_a_literal_dissolves_the_retained_scc() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_value(&mut engine, "Sheet1", 1, 2, LiteralValue::Number(42.0));
engine.evaluate_all().unwrap();
assert_eq!(engine.last_cycle_telemetry().static_sccs, 0);
assert_eq!(engine.last_cycle_telemetry().iterated_sccs, 0);
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 42.0);
for _ in 0..3 {
let res = engine.evaluate_all().unwrap();
assert_eq!(res.computed_vertices, 0, "no perpetual redirty leak");
assert_eq!(engine.last_cycle_telemetry().static_sccs, 0);
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 42.0);
}
}
#[test]
fn overwriting_a_member_with_a_literal_prunes_the_retained_set() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_value(&mut engine, "Sheet1", 1, 2, LiteralValue::Number(42.0));
engine.evaluate_all().unwrap();
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
for _ in 0..3 {
engine.evaluate_all().unwrap();
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
}
}
#[test]
fn a_new_formula_joining_the_scc_reruns_the_enlarged_scc() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
set_value(&mut engine, "Sheet1", 1, 4, LiteralValue::Number(3.0));
set_formula(&mut engine, "Sheet1", 1, 1, "=IF(B1>=D1,D1,B1+1)");
set_formula(&mut engine, "Sheet1", 1, 2, "=MAX(A1,C1)");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
set_formula(&mut engine, "Sheet1", 1, 3, "=B1*0");
engine.evaluate_all().unwrap();
assert_eq!(engine.last_cycle_telemetry().iterated_sccs, 1);
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 3), 0.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 3);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 3));
}
#[test]
fn defined_name_edits_reach_retained_members_through_the_name_edge() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
engine
.define_name(
"Cap",
NamedDefinition::Literal(LiteralValue::Number(3.0)),
NameScope::Workbook,
)
.unwrap();
set_formula(&mut engine, "Sheet1", 1, 1, "=IF(B1>=Cap,Cap,B1+1)");
set_formula(&mut engine, "Sheet1", 1, 2, "=A1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
engine
.update_name(
"Cap",
NamedDefinition::Literal(LiteralValue::Number(6.0)),
NameScope::Workbook,
)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 1), 6.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 6.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
engine.delete_name("Cap", NameScope::Workbook).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
assert_eq!(err_kind(&engine, "Sheet1", 1, 1), ExcelErrorKind::Name);
}
#[test]
fn downstream_retained_scc_reruns_when_the_upstream_one_moves() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
build_input_driven_pair(&mut engine, 1, 3.0);
set_formula(&mut engine, "Sheet1", 3, 1, "=IF(B3>=B1,B1,B3+1)");
set_formula(&mut engine, "Sheet1", 3, 2, "=A3");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 3, 1), 3.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 4);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 2, 4));
set_value(&mut engine, "Sheet1", 1, 4, LiteralValue::Number(7.0));
engine.evaluate_all().unwrap();
assert_eq!(
reuse_telemetry(&engine),
(2, 0, 0),
"both SCCs must re-run: the downstream one reads the upstream output"
);
assert_eq!(num(&engine, "Sheet1", 1, 1), 7.0);
assert_eq!(num(&engine, "Sheet1", 3, 1), 7.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 2, 4));
}
#[test]
fn structural_edits_reach_retained_members_and_preserve_their_values() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
engine.insert_rows("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 2, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 2, 2), 3.0);
assert_eq!(num(&engine, "Sheet1", 2, 4), 3.0);
engine.delete_rows("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 3.0);
set_value(&mut engine, "Sheet1", 20, 1, LiteralValue::Number(1.0));
engine.evaluate_all().unwrap();
engine.delete_rows("Sheet1", 20, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 2), 3.0);
}
#[test]
fn row_insert_inside_a_retained_members_range_extends_it_and_keeps_values() {
let _epoch = epoch_stable();
let mut engine = iterate_engine(100, 0.001);
set_value(&mut engine, "Sheet1", 1, 2, LiteralValue::Number(1.0));
set_value(&mut engine, "Sheet1", 2, 2, LiteralValue::Number(0.0));
set_value(&mut engine, "Sheet1", 3, 2, LiteralValue::Number(2.0));
set_formula(
&mut engine,
"Sheet1",
1,
1,
"=IF(A1>=SUM(B1:B3),SUM(B1:B3),A1+1)",
);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 1);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 1));
engine.insert_rows("Sheet1", 2, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
reuse_telemetry(&engine),
(1, 0, 0),
"the range edit re-runs it"
);
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 1));
}
#[test]
fn timestamp_pattern_keeps_its_stamp_across_a_row_insert() {
let _epoch = epoch_stable();
let mut engine = Engine::new(TestWorkbook::new(), deterministic_iterate_cfg(100, 0.001));
set_formula(
&mut engine,
"Sheet1",
1,
2,
"=IF(A1=\"\",\"\",IF(B1=\"\",NOW(),B1))",
);
set_value(
&mut engine,
"Sheet1",
1,
1,
LiteralValue::Text("x".to_string()),
);
engine.evaluate_all().unwrap();
let stamped = num(&engine, "Sheet1", 1, 2);
assert!(stamped > 40000.0, "expected a date serial, got {stamped}");
engine.insert_rows("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
num(&engine, "Sheet1", 2, 2),
stamped,
"the stamp must survive the row insert"
);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 2, 2), stamped);
}
#[test]
fn switching_the_cycle_policy_to_error_reruns_retained_members_as_circ() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
engine.config.cycle.policy = CyclePolicy::Error;
engine.evaluate_all().unwrap();
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
assert_eq!(err_kind(&engine, "Sheet1", 1, 1), ExcelErrorKind::Circ);
assert_eq!(err_kind(&engine, "Sheet1", 1, 2), ExcelErrorKind::Circ);
}
#[test]
fn changing_max_change_reruns_retained_members() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
engine.config.cycle.policy = CyclePolicy::Iterate {
max_iterations: 100,
max_change: 0.5,
};
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
}
#[test]
fn changing_the_date_system_reruns_retained_members() {
let _epoch = epoch_stable();
use formualizer_common::DateSystem;
let mut engine = retained_engine(3.0);
let flipped = match engine.config.date_system {
DateSystem::Excel1900 => DateSystem::Excel1904,
_ => DateSystem::Excel1900,
};
engine.config.date_system = flipped;
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0));
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
}
#[test]
fn registry_changes_rerun_only_retained_members_that_call_a_changed_function() {
let _epoch = epoch_bumper();
use crate::args::ArgSchema;
use crate::function::{FnCaps, Function};
use crate::traits::{ArgumentHandle, FunctionContext};
#[derive(Debug)]
struct SccReuseProbe {
delta: f64,
}
impl Function for SccReuseProbe {
fn caps(&self) -> FnCaps {
FnCaps::empty()
}
fn name(&self) -> &'static str {
"SCCREUSEPROBE"
}
fn arg_schema(&self) -> &'static [ArgSchema] {
static SCHEMA: std::sync::LazyLock<Vec<ArgSchema>> =
std::sync::LazyLock::new(|| vec![ArgSchema::any()]);
&SCHEMA
}
fn eval<'a, 'b, 'c>(
&self,
args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
let x = match args[0].value()?.into_literal() {
LiteralValue::Number(n) => n,
LiteralValue::Int(i) => i as f64,
_ => 0.0,
};
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
x + self.delta,
)))
}
}
#[derive(Debug)]
struct SccReuseEpochProbe;
impl Function for SccReuseEpochProbe {
fn caps(&self) -> FnCaps {
FnCaps::empty()
}
fn name(&self) -> &'static str {
"SCCREUSEEPOCHPROBE"
}
fn arg_schema(&self) -> &'static [ArgSchema] {
&[]
}
fn eval<'a, 'b, 'c>(
&self,
_args: &'c [ArgumentHandle<'a, 'b>],
_ctx: &dyn FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Int(0)))
}
}
crate::function_registry::register_function(Arc::new(SccReuseProbe { delta: 1.0 }));
let mut engine = iterate_engine(100, 0.001);
set_value(&mut engine, "Sheet1", 1, 4, LiteralValue::Number(3.0));
set_formula(
&mut engine,
"Sheet1",
1,
1,
"=IF(B1>=D1,D1,SCCREUSEPROBE(B1))",
);
set_formula(&mut engine, "Sheet1", 1, 2, "=A1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
let before = crate::function_registry::semantic_epoch();
crate::function_registry::register_function(Arc::new(SccReuseEpochProbe));
assert!(crate::function_registry::semantic_epoch() > before);
engine.evaluate_all().unwrap();
assert_eq!(
reuse_telemetry(&engine),
(0, 1, 2),
"unrelated registration"
);
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
crate::function_registry::register_function(Arc::new(SccReuseProbe { delta: 0.0 }));
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (1, 0, 0), "changed callee");
assert_eq!(num(&engine, "Sheet1", 1, 1), 3.0);
engine.evaluate_all().unwrap();
assert_eq!(reuse_telemetry(&engine), (0, 1, 2));
}
#[test]
fn deleting_retained_members_stops_all_scc_work_without_panicking() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
assert_eq!(engine.baseline_stats().retained_scc_members, 2);
engine.delete_rows("Sheet1", 1, 1).unwrap();
for _ in 0..3 {
let res = engine.evaluate_all().unwrap();
assert_eq!(res.computed_vertices, 0, "no perpetual redirty leak");
assert_eq!(engine.last_cycle_telemetry().iterated_sccs, 0);
assert_eq!(engine.get_cell_value("Sheet1", 1, 1), None);
assert_eq!(engine.get_cell_value("Sheet1", 1, 2), None);
}
}
#[test]
fn deleting_retained_members_prunes_the_retained_set() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
engine.delete_rows("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
engine.evaluate_all().unwrap();
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
}
#[test]
fn clearing_a_retained_member_dissolves_the_cycle_and_keeps_values_correct() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_value(&mut engine, "Sheet1", 1, 2, LiteralValue::Empty);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 1.0);
assert_eq!(engine.last_cycle_telemetry().iterated_sccs, 0);
for _ in 0..2 {
let res = engine.evaluate_all().unwrap();
assert_eq!(res.computed_vertices, 0, "no perpetual redirty leak");
assert_eq!(num(&engine, "Sheet1", 1, 1), 1.0);
}
}
#[test]
fn clearing_a_retained_member_prunes_the_retained_set() {
let _epoch = epoch_stable();
let mut engine = retained_engine(3.0);
set_value(&mut engine, "Sheet1", 1, 2, LiteralValue::Empty);
engine.evaluate_all().unwrap();
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
for _ in 0..2 {
engine.evaluate_all().unwrap();
assert_eq!(engine.baseline_stats().retained_scc_members, 0);
assert_eq!(engine.last_cycle_telemetry().reused_sccs, 0);
}
}