use crate::engine::graph::editor::vertex_editor::VertexEditor;
use crate::engine::{Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use crate::traits::EvaluationContext;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::ReferenceType;
use formualizer_parse::parser::parse;
fn range_limit_config(limit: usize) -> EvalConfig {
EvalConfig {
range_expansion_limit: limit,
..Default::default()
}
}
#[test]
fn infinite_column_empty_sheet_sum_count_are_zero() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
let ast_sum = parse("=SUM(A:A)").unwrap();
engine.set_cell_formula("Sheet1", 1, 2, ast_sum).unwrap();
let ast_cnt = parse("=COUNT(A:A)").unwrap();
engine.set_cell_formula("Sheet1", 2, 2, ast_cnt).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2).unwrap(),
LiteralValue::Number(0.0)
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 2).unwrap(),
LiteralValue::Number(0.0)
);
}
#[test]
fn infinite_column_sparse_sum_and_count_correct() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
engine
.set_cell_value("Sheet1", 1000, 1, LiteralValue::Int(5))
.unwrap();
engine
.set_cell_value("Sheet1", 500_000, 1, LiteralValue::Int(2))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 2, parse("=SUM(A:A)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 2, 2, parse("=COUNT(A:A)").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2).unwrap(),
LiteralValue::Number(17.0)
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 2).unwrap(),
LiteralValue::Number(3.0)
);
}
#[test]
fn whole_column_includes_far_formula_rows_when_arrow_has_earlier_values() {
let wb = TestWorkbook::new();
let cfg = EvalConfig {
enable_parallel: false,
..Default::default()
};
let mut engine = Engine::new(wb, cfg);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
engine
.set_cell_value("Sheet1", 1, 2, LiteralValue::Int(2))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 3, parse("=SUM(A:A)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 200, 1, parse("=B1").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3).unwrap(),
LiteralValue::Number(3.0)
);
}
#[test]
fn whole_column_recalc_tracks_formula_cells_beyond_direct_dirty_propagation() {
let wb = TestWorkbook::new();
let cfg = EvalConfig {
enable_parallel: false,
..Default::default()
};
let mut engine = Engine::new(wb, cfg);
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
engine
.set_cell_value("Sheet1", 1, 2, LiteralValue::Int(2))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 3, parse("=SUM(A:A)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 200, 1, parse("=B1").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3).unwrap(),
LiteralValue::Number(3.0)
);
engine
.set_cell_value("Sheet1", 1, 2, LiteralValue::Int(5))
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3).unwrap(),
LiteralValue::Number(6.0)
);
}
#[test]
fn infinite_row_sum_and_count_correct() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
let mut sum: i64 = 0;
let mut count: i64 = 0;
for c in 2..=27 {
let v = (c - 1) as i64; sum += v;
count += 1;
engine
.set_cell_value("Sheet1", 1, c, LiteralValue::Int(v))
.unwrap();
}
engine
.set_cell_formula("Sheet1", 2, 1, parse("=SUM(1:1)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 3, 1, parse("=COUNT(1:1)").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 2, 1).unwrap(),
LiteralValue::Number(sum as f64)
);
assert_eq!(
engine.get_cell_value("Sheet1", 3, 1).unwrap(),
LiteralValue::Number(count as f64)
);
}
#[test]
fn partial_ranges_column_tail_and_head_bounds() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(10))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 1, LiteralValue::Int(5))
.unwrap();
engine
.set_cell_value("Sheet1", 20, 1, LiteralValue::Int(7))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 2, parse("=SUM(A1:A)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 2, 2, parse("=COUNT(A1:A)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 3, 2, parse("=SUM(A:A10)").unwrap())
.unwrap();
engine
.set_cell_formula("Sheet1", 4, 2, parse("=COUNT(A:A10)").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2).unwrap(),
LiteralValue::Number(22.0)
);
assert_eq!(
engine.get_cell_value("Sheet1", 2, 2).unwrap(),
LiteralValue::Number(3.0)
);
assert_eq!(
engine.get_cell_value("Sheet1", 3, 2).unwrap(),
LiteralValue::Number(15.0)
);
assert_eq!(
engine.get_cell_value("Sheet1", 4, 2).unwrap(),
LiteralValue::Number(2.0)
);
}
#[test]
fn vlookup_with_open_ended_column_range_resolves() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
engine
.set_cell_value(
"Sheet1",
1,
1,
LiteralValue::Text("Professional".to_string()),
)
.unwrap();
engine
.set_cell_value("Sheet1", 1, 2, LiteralValue::Int(123))
.unwrap();
engine
.set_cell_formula(
"Sheet1",
1,
3,
parse("=VLOOKUP(\"Professional\", A:B, 2, FALSE())").unwrap(),
)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3).unwrap(),
LiteralValue::Number(123.0)
);
}
#[test]
fn invalidation_on_growth_column_and_row() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
for r in 1..=10u32 {
engine
.set_cell_value("Sheet1", r, 1, LiteralValue::Int(1))
.unwrap();
}
engine
.set_cell_formula("Sheet1", 1, 3, parse("=SUM(A:A)").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3).unwrap(),
LiteralValue::Number(10.0)
);
engine
.set_cell_value("Sheet1", 1000, 1, LiteralValue::Int(1))
.unwrap();
let _res = engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 3).unwrap(),
LiteralValue::Number(11.0)
);
for c in 1..=10u32 {
engine
.set_cell_value("Sheet1", 1, c, LiteralValue::Int(1))
.unwrap();
}
engine
.set_cell_formula("Sheet1", 3, 1, parse("=SUM(1:1)").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 3, 1).unwrap(),
LiteralValue::Number(10.0)
);
engine
.set_cell_value("Sheet1", 1, 1000, LiteralValue::Int(1))
.unwrap();
let _res2 = engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 3, 1).unwrap(),
LiteralValue::Number(11.0)
);
}
#[test]
fn invalidation_on_shrink_via_empty() {
let wb = TestWorkbook::new();
let mut engine = Engine::new(wb, range_limit_config(16));
for r in 1..=10u32 {
engine
.set_cell_value("Sheet1", r, 1, LiteralValue::Int(1))
.unwrap();
}
engine
.set_cell_formula("Sheet1", 1, 2, parse("=SUM(A:A)").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2).unwrap(),
LiteralValue::Number(10.0)
);
engine
.set_cell_value("Sheet1", 10, 1, LiteralValue::Empty)
.unwrap();
let _res = engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2).unwrap(),
LiteralValue::Number(9.0)
);
}
#[test]
fn unbounded_ranges_resolve_with_expected_dims() {
let engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
let sheet = "Sheet1";
let r1 = ReferenceType::range(Some(sheet.into()), None, Some(1), None, Some(1));
let rv1 = engine.resolve_range_view(&r1, sheet).unwrap();
let (r1_rows, r1_cols) = rv1.dims();
assert_eq!(r1_cols, 1);
assert!(r1_rows >= 1_000_000, "expected full column height");
let r2 = ReferenceType::range(Some(sheet.into()), Some(1), None, Some(1), None);
let rv2 = engine.resolve_range_view(&r2, sheet).unwrap();
let (r2_rows, r2_cols) = rv2.dims();
assert_eq!(r2_rows, 1);
assert!(r2_cols >= 10_000, "expected wide row");
let r3 = ReferenceType::range(Some(sheet.into()), Some(1), Some(1), None, Some(1));
let rv3 = engine.resolve_range_view(&r3, sheet).unwrap();
let (r3_rows, r3_cols) = rv3.dims();
assert_eq!(r3_cols, 1);
assert!(r3_rows >= 1_000_000);
let r4 = ReferenceType::range(Some(sheet.into()), None, Some(1), Some(10), Some(1));
let rv4 = engine.resolve_range_view(&r4, sheet).unwrap();
let (r4_rows, r4_cols) = rv4.dims();
assert_eq!(r4_cols, 1);
assert_eq!(r4_rows, 10);
}
#[test]
fn used_region_growth_shrink_has_zero_stripe_churn() {
let cfg = EvalConfig {
enable_parallel: false, ..Default::default()
};
let mut engine = Engine::new(TestWorkbook::new(), cfg);
let sheet = "Sheet1";
engine
.set_cell_value(sheet, 1, 1, LiteralValue::Number(5.0))
.unwrap();
let ast = parse("=SUM(A:A)").unwrap();
engine.set_cell_formula(sheet, 1, 3, ast).unwrap();
let _ = engine.evaluate_all().unwrap();
engine.graph.reset_instr();
engine
.set_cell_value(sheet, 1000, 1, LiteralValue::Number(1.0))
.unwrap();
let _ = engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value(sheet, 1, 3).unwrap(),
LiteralValue::Number(6.0)
);
let instr1 = engine.graph.instr();
assert_eq!(instr1.stripe_inserts, 0);
assert_eq!(instr1.stripe_removes, 0);
assert_eq!(instr1.edges_added, 0);
engine.graph.reset_instr();
engine
.set_cell_value(sheet, 1, 1, LiteralValue::Empty)
.unwrap();
let _ = engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value(sheet, 1, 3).unwrap(),
LiteralValue::Number(1.0)
);
let instr2 = engine.graph.instr();
assert_eq!(instr2.stripe_inserts, 0);
assert_eq!(instr2.stripe_removes, 0);
assert_eq!(instr2.edges_added, 0);
}
#[test]
fn edge_churn_on_insert_delete_rows_is_bounded() {
let cfg = EvalConfig {
enable_parallel: false, ..Default::default()
};
let mut engine = Engine::new(TestWorkbook::new(), cfg);
let sheet = engine.default_sheet_name().to_string();
for r in 1..=100u32 {
engine
.set_cell_value(&sheet, r, 1, LiteralValue::Int(1))
.unwrap();
}
engine
.set_cell_formula(&sheet, 1, 3, parse("=SUM(A:A)").unwrap())
.unwrap();
let _ = engine.evaluate_all().unwrap();
engine.graph.reset_instr();
let sheet_id = engine.default_sheet_id();
{
let mut editor = VertexEditor::new(&mut engine.graph);
editor.insert_rows(sheet_id, 50, 5).unwrap();
}
let _ = engine.evaluate_all().unwrap();
let instr = engine.graph.instr();
assert!(
instr.stripe_inserts <= 10,
"too many stripe inserts: {}",
instr.stripe_inserts
);
assert!(
instr.stripe_removes <= 10,
"too many stripe removes: {}",
instr.stripe_removes
);
assert!(
instr.edges_added <= 50,
"too many edges added: {}",
instr.edges_added
);
engine.graph.reset_instr();
{
let mut editor = VertexEditor::new(&mut engine.graph);
editor.delete_rows(sheet_id, 20, 5).unwrap();
}
let _ = engine.evaluate_all().unwrap();
let instr2 = engine.graph.instr();
assert!(instr2.stripe_inserts <= 10);
assert!(instr2.stripe_removes <= 10);
assert!(instr2.edges_added <= 50);
}
#[test]
fn edge_churn_on_insert_delete_columns_is_bounded() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
let sheet = engine.default_sheet_name().to_string();
for c in 2..=11u32 {
engine
.set_cell_value(&sheet, 1, c, LiteralValue::Int(1))
.unwrap();
}
engine
.set_cell_formula(&sheet, 3, 1, parse("=SUM(1:1)").unwrap())
.unwrap();
let _ = engine.evaluate_all().unwrap();
engine.graph.reset_instr();
let sheet_id = engine.default_sheet_id();
{
let mut editor = VertexEditor::new(&mut engine.graph);
editor.insert_columns(sheet_id, 5, 3).unwrap();
}
let _ = engine.evaluate_all().unwrap();
let instr = engine.graph.instr();
assert!(instr.stripe_inserts <= 10);
assert!(instr.stripe_removes <= 10);
assert!(instr.edges_added <= 50);
engine.graph.reset_instr();
{
let mut editor = VertexEditor::new(&mut engine.graph);
editor.delete_columns(sheet_id, 4, 2).unwrap();
}
let _ = engine.evaluate_all().unwrap();
let instr2 = engine.graph.instr();
assert!(instr2.stripe_inserts <= 10);
assert!(instr2.stripe_removes <= 10);
assert!(instr2.edges_added <= 50);
}