use std::sync::Arc;
use crate::engine::{
Engine, EvalConfig, FormulaIngestBatch, FormulaIngestRecord, FormulaPlaneMode,
};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;
const SHEET: &str = "Sheet1";
const N: u32 = 200;
const SPAN_START: u32 = 2;
fn span_end() -> u32 {
SPAN_START + N - 1
}
fn record(
engine: &mut Engine<TestWorkbook>,
row: u32,
col: u32,
formula: &str,
) -> FormulaIngestRecord {
let ast = parse(formula).unwrap();
let ast_id = engine.intern_formula_ast(&ast);
FormulaIngestRecord::new(row, col, ast_id, Some(Arc::<str>::from(formula)))
}
fn build_engine(mode: FormulaPlaneMode) -> Engine<TestWorkbook> {
let cfg = EvalConfig::default().with_formula_plane_mode(mode);
let mut engine = Engine::new(TestWorkbook::default(), cfg);
engine.add_sheet(SHEET).ok();
engine
.set_cell_value(SHEET, 1, 6, LiteralValue::Number(3.0))
.unwrap();
let mut formulas = Vec::with_capacity(N as usize);
for r in SPAN_START..=span_end() {
engine
.set_cell_value(SHEET, r, 1, LiteralValue::Number(r as f64))
.unwrap(); engine
.set_cell_value(SHEET, r, 2, LiteralValue::Number((r * 2) as f64))
.unwrap(); formulas.push(record(&mut engine, r, 3, &format!("=A{r}*B{r}*$F$1"))); }
engine
.ingest_formula_batches(vec![FormulaIngestBatch::new(SHEET, formulas)])
.expect("ingest span formulas");
engine
.set_cell_formula(
SHEET,
1,
5,
parse(format!("=SUM(C{}:C{})", SPAN_START, span_end())).unwrap(),
)
.unwrap();
engine.evaluate_all().expect("baseline evaluate_all");
engine
}
fn snapshot(engine: &Engine<TestWorkbook>, rows_hi: u32) -> Vec<Option<LiteralValue>> {
let mut out = Vec::new();
for r in 1..=rows_hi {
for c in 1..=6u32 {
out.push(engine.get_cell_value(SHEET, r, c));
}
}
out
}
fn assert_structural_op_parity(op: impl Fn(&mut Engine<TestWorkbook>)) {
let rows_hi = span_end() + 8;
let mut off = build_engine(FormulaPlaneMode::Off);
let mut on = build_engine(FormulaPlaneMode::AuthoritativeExperimental);
op(&mut off);
op(&mut on);
off.evaluate_all().expect("post-op evaluate_all (off)");
on.evaluate_all().expect("post-op evaluate_all (on)");
let off_snapshot = snapshot(&off, rows_hi);
let on_snapshot = snapshot(&on, rows_hi);
assert_eq!(
off_snapshot, on_snapshot,
"span-ON vs span-OFF diverged after structural op"
);
}
#[test]
fn split_acceptance_insert_mid_span() {
assert_structural_op_parity(|engine| {
let mid = SPAN_START + N / 2;
engine.insert_rows(SHEET, mid, 1).unwrap();
});
}
#[test]
fn split_acceptance_insert_at_span_first_row() {
assert_structural_op_parity(|engine| {
engine.insert_rows(SHEET, SPAN_START, 1).unwrap();
});
}
#[test]
fn split_acceptance_insert_just_past_span_end() {
assert_structural_op_parity(|engine| {
engine.insert_rows(SHEET, span_end() + 1, 1).unwrap();
});
}
#[test]
fn split_acceptance_insert_count_three_mid_span() {
assert_structural_op_parity(|engine| {
let mid = SPAN_START + N / 2;
engine.insert_rows(SHEET, mid, 3).unwrap();
});
}
#[test]
fn split_acceptance_insert_before_row_zero() {
assert_structural_op_parity(|engine| {
engine.insert_rows(SHEET, 1, 1).unwrap();
});
}
#[test]
fn split_acceptance_delete_overlapping_span_first_row() {
assert_structural_op_parity(|engine| {
engine.delete_rows(SHEET, SPAN_START, 1).unwrap();
});
}
#[test]
fn split_acceptance_delete_overlapping_span_last_row() {
assert_structural_op_parity(|engine| {
engine.delete_rows(SHEET, span_end(), 1).unwrap();
});
}
#[test]
fn split_acceptance_delete_strictly_inside_span() {
assert_structural_op_parity(|engine| {
let mid = SPAN_START + N / 2;
engine.delete_rows(SHEET, mid, 1).unwrap();
});
}
fn expected_c_value(r: u32) -> LiteralValue {
LiteralValue::Number((r as f64) * (2 * r) as f64 * 3.0)
}
fn expected_tail_sum() -> LiteralValue {
let mut sum = 0.0;
for r in SPAN_START..=span_end() {
sum += (r as f64) * (2 * r) as f64 * 3.0;
}
LiteralValue::Number(sum)
}
fn assert_insert_above_header_oracle(mode: FormulaPlaneMode) {
let mut engine = build_engine(mode);
engine.insert_rows(SHEET, 1, 2).unwrap();
engine.evaluate_all().expect("post-insert evaluate_all");
assert_eq!(
engine.get_cell_value(SHEET, 3, 6),
Some(LiteralValue::Number(3.0)),
"scalar value should have physically moved to F3 ({mode:?})"
);
for r in [SPAN_START, SPAN_START + N / 2, span_end()] {
assert_eq!(
engine.get_cell_value(SHEET, r + 2, 3),
Some(expected_c_value(r)),
"C for original row {r} (now row {}) must track $F$1 ({mode:?})",
r + 2
);
}
assert_eq!(
engine.get_cell_value(SHEET, 3, 5),
Some(expected_tail_sum()),
"tail SUM must track the moved span ({mode:?})"
);
}
#[test]
fn oracle_insert_rows_above_absolute_target_span_off() {
assert_insert_above_header_oracle(FormulaPlaneMode::Off);
}
#[test]
fn oracle_insert_rows_above_absolute_target_span_on() {
assert_insert_above_header_oracle(FormulaPlaneMode::AuthoritativeExperimental);
}
fn assert_insert_columns_before_absolute_target_oracle(mode: FormulaPlaneMode) {
const COL_START: u32 = 2;
const COLS: u32 = 120;
let col_end = COL_START + COLS - 1;
let cfg = EvalConfig::default().with_formula_plane_mode(mode);
let mut engine = Engine::new(TestWorkbook::default(), cfg);
engine.add_sheet(SHEET).ok();
engine
.set_cell_value(SHEET, 1, 1, LiteralValue::Number(3.0))
.unwrap();
let mut formulas = Vec::with_capacity(COLS as usize);
for c in COL_START..=col_end {
engine
.set_cell_value(SHEET, 1, c, LiteralValue::Number(c as f64))
.unwrap();
let col_name = crate::reference::Coord::col_to_letters(c - 1);
formulas.push(record(&mut engine, 3, c, &format!("={col_name}1*$A$1")));
}
engine
.ingest_formula_batches(vec![FormulaIngestBatch::new(SHEET, formulas)])
.expect("ingest column family");
engine.evaluate_all().expect("baseline evaluate_all");
for c in [COL_START, COL_START + COLS / 2, col_end] {
assert_eq!(
engine.get_cell_value(SHEET, 3, c),
Some(LiteralValue::Number(c as f64 * 3.0)),
"baseline value at row 3 col {c} ({mode:?})"
);
}
engine.insert_columns(SHEET, 1, 2).unwrap();
engine.evaluate_all().expect("post-insert evaluate_all");
assert_eq!(
engine.get_cell_value(SHEET, 1, 3),
Some(LiteralValue::Number(3.0)),
"scalar value should have physically moved to C1 ({mode:?})"
);
for c in [COL_START, COL_START + COLS / 2, col_end] {
assert_eq!(
engine.get_cell_value(SHEET, 3, c + 2),
Some(LiteralValue::Number(c as f64 * 3.0)),
"formula for original col {c} (now col {}) must track $A$1 ({mode:?})",
c + 2
);
}
}
#[test]
fn oracle_insert_columns_before_absolute_target_span_off() {
assert_insert_columns_before_absolute_target_oracle(FormulaPlaneMode::Off);
}
#[test]
fn oracle_insert_columns_before_absolute_target_span_on() {
assert_insert_columns_before_absolute_target_oracle(
FormulaPlaneMode::AuthoritativeExperimental,
);
}
fn build_abs_target_engine(mode: FormulaPlaneMode, scalar_row: u32) -> Engine<TestWorkbook> {
let cfg = EvalConfig::default().with_formula_plane_mode(mode);
let mut engine = Engine::new(TestWorkbook::default(), cfg);
engine.add_sheet(SHEET).ok();
engine
.set_cell_value(SHEET, scalar_row, 6, LiteralValue::Number(3.0))
.unwrap();
let mut formulas = Vec::with_capacity(N as usize);
for r in SPAN_START..=span_end() {
engine
.set_cell_value(SHEET, r, 1, LiteralValue::Number(r as f64))
.unwrap();
formulas.push(record(&mut engine, r, 3, &format!("=A{r}*$F${scalar_row}")));
}
engine
.ingest_formula_batches(vec![FormulaIngestBatch::new(SHEET, formulas)])
.expect("ingest abs-target span");
engine.evaluate_all().expect("baseline evaluate_all");
engine
}
fn snapshot_rect(
engine: &Engine<TestWorkbook>,
rows_hi: u32,
cols_hi: u32,
) -> Vec<Option<LiteralValue>> {
let mut out = Vec::new();
for r in 1..=rows_hi {
for c in 1..=cols_hi {
out.push(engine.get_cell_value(SHEET, r, c));
}
}
out
}
fn assert_abs_target_op_parity(
scalar_row: u32,
rows_hi: u32,
op: impl Fn(&mut Engine<TestWorkbook>),
) -> Engine<TestWorkbook> {
let mut off = build_abs_target_engine(FormulaPlaneMode::Off, scalar_row);
let mut on = build_abs_target_engine(FormulaPlaneMode::AuthoritativeExperimental, scalar_row);
op(&mut off);
op(&mut on);
off.evaluate_all().expect("post-op evaluate_all (off)");
on.evaluate_all().expect("post-op evaluate_all (on)");
assert_eq!(
snapshot_rect(&off, rows_hi, 6),
snapshot_rect(&on, rows_hi, 6),
"span-ON vs span-OFF diverged after structural op"
);
on
}
#[test]
fn edge_insert_exactly_at_absolute_target_row_below_span() {
let on = assert_abs_target_op_parity(250, 260, |engine| {
engine.insert_rows(SHEET, 250, 1).unwrap();
});
assert_eq!(
on.get_cell_value(SHEET, 251, 6),
Some(LiteralValue::Number(3.0))
);
for r in [SPAN_START, span_end()] {
assert_eq!(
on.get_cell_value(SHEET, r, 3),
Some(LiteralValue::Number(r as f64 * 3.0)),
"row {r} must keep tracking the moved scalar"
);
}
}
#[test]
fn edge_absolute_target_in_inserted_gap_mid_span() {
let on = assert_abs_target_op_parity(100, 210, |engine| {
engine.insert_rows(SHEET, 100, 1).unwrap();
});
assert_eq!(
on.get_cell_value(SHEET, 101, 6),
Some(LiteralValue::Number(3.0))
);
assert_eq!(
on.get_cell_value(SHEET, 2, 3),
Some(LiteralValue::Number(2.0 * 3.0))
);
assert_eq!(
on.get_cell_value(SHEET, span_end() + 1, 3),
Some(LiteralValue::Number(span_end() as f64 * 3.0))
);
}
#[test]
fn edge_delete_absolute_target_yields_ref_error() {
use formualizer_common::ExcelErrorKind;
let on = assert_abs_target_op_parity(1, 205, |engine| {
engine.delete_rows(SHEET, 1, 1).unwrap();
});
for r in [SPAN_START - 1, span_end() - 1] {
let value = on.get_cell_value(SHEET, r, 3);
assert!(
matches!(
&value,
Some(LiteralValue::Error(err)) if err.kind == ExcelErrorKind::Ref
),
"row {r} must be #REF! after the absolute target row was deleted, got {value:?}"
);
}
}
#[test]
fn edge_delete_inside_span_with_displaced_absolute_target() {
let on = assert_abs_target_op_parity(150, 205, |engine| {
engine.delete_rows(SHEET, 50, 1).unwrap();
});
assert_eq!(
on.get_cell_value(SHEET, 149, 6),
Some(LiteralValue::Number(3.0))
);
assert_eq!(
on.get_cell_value(SHEET, 2, 3),
Some(LiteralValue::Number(2.0 * 3.0))
);
assert_eq!(
on.get_cell_value(SHEET, span_end() - 1, 3),
Some(LiteralValue::Number(span_end() as f64 * 3.0))
);
}
#[test]
fn edge_column_insert_before_flagship_span_parity() {
let rows_hi = span_end() + 2;
let mut off = build_engine(FormulaPlaneMode::Off);
let mut on = build_engine(FormulaPlaneMode::AuthoritativeExperimental);
for engine in [&mut off, &mut on] {
engine.insert_columns(SHEET, 1, 2).unwrap();
engine.evaluate_all().expect("post-op evaluate_all");
}
assert_eq!(
snapshot_rect(&off, rows_hi, 9),
snapshot_rect(&on, rows_hi, 9),
"span-ON vs span-OFF diverged after insert_columns"
);
for r in [SPAN_START, span_end()] {
assert_eq!(
on.get_cell_value(SHEET, r, 5),
Some(expected_c_value(r)),
"row {r} at shifted column E"
);
}
}
#[test]
fn edge_column_delete_of_absolute_target_column_parity() {
use formualizer_common::ExcelErrorKind;
let rows_hi = span_end() + 2;
let mut off = build_engine(FormulaPlaneMode::Off);
let mut on = build_engine(FormulaPlaneMode::AuthoritativeExperimental);
for engine in [&mut off, &mut on] {
engine.delete_columns(SHEET, 6, 1).unwrap();
engine.evaluate_all().expect("post-op evaluate_all");
}
assert_eq!(
snapshot_rect(&off, rows_hi, 6),
snapshot_rect(&on, rows_hi, 6),
"span-ON vs span-OFF diverged after delete_columns"
);
let value = on.get_cell_value(SHEET, SPAN_START, 3);
assert!(
matches!(
&value,
Some(LiteralValue::Error(err)) if err.kind == ExcelErrorKind::Ref
),
"expected #REF! after deleting the absolute target column, got {value:?}"
);
}