use crate::engine::named_range::{NameScope, NamedDefinition};
use crate::engine::{Engine, EvalConfig};
use crate::reference::{CellRef, RangeRef};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;
fn work(e: &Engine<TestWorkbook>) -> u64 {
let host = e.graph.authority_host();
let st = &host.store().stats;
host.symbol_sync_work()
+ st.index_work
+ st.plan_work
+ st.slot_work
+ st.symbol_work
+ crate::engine::authority::identity::run_slots_walked()
}
fn engine(formulas: u32) -> Engine<TestWorkbook> {
let mut e = Engine::new(TestWorkbook::new(), EvalConfig::default());
for i in 1..=formulas {
let r = 2 * i;
e.set_cell_value("Sheet1", r, 1, LiteralValue::Number(f64::from(i)))
.unwrap();
e.set_cell_formula("Sheet1", r, 2, parse("=A1*2+1").unwrap())
.unwrap();
}
e.graph.authority().unwrap();
e
}
fn measure(
formulas: u32,
n: u32,
setup: &dyn Fn(&mut Engine<TestWorkbook>, u32),
op: &dyn Fn(&mut Engine<TestWorkbook>, u32),
) -> u64 {
let mut e = engine(formulas);
setup(&mut e, n);
e.graph.authority().unwrap();
let (builds, before) = (e.graph.authority_host().builds(), work(&e));
for i in 0..n {
op(&mut e, i);
}
e.graph.authority().unwrap();
assert_eq!(
e.graph.authority_host().builds(),
builds,
"a symbol revision rebuilt the authority"
);
let done = work(&e) - before;
e.graph.authority_host().store().check().unwrap();
done
}
fn assert_linear(
what: &str,
setup: &dyn Fn(&mut Engine<TestWorkbook>, u32),
op: &dyn Fn(&mut Engine<TestWorkbook>, u32),
) {
let small = measure(0, 100, setup, op);
let large = measure(0, 400, setup, op);
let with_formulas = measure(2000, 100, setup, op);
let per = |w: u64, n: u64| w as f64 / n as f64;
assert!(small > 0, "{what}: no work counted");
assert!(
per(large, 400) <= 1.5 * per(small, 100),
"{what}: per-op work grows with the symbol count: {small} for 100, {large} for 400"
);
assert!(
per(with_formulas, 100) <= 1.5 * per(small, 100),
"{what}: per-op work grows with the formula count: {small} without, {with_formulas} with 2000 formulas"
);
}
fn no_setup(_: &mut Engine<TestWorkbook>, _: u32) {}
fn define_literal(e: &mut Engine<TestWorkbook>, i: u32) {
e.define_name(
&format!("Name_{i}"),
NamedDefinition::Literal(LiteralValue::Number(f64::from(i))),
NameScope::Workbook,
)
.unwrap();
}
#[test]
fn unused_literal_names_scale_linearly() {
assert_linear("define", &no_setup, &define_literal);
}
#[test]
fn unused_name_redefinition_and_deletion_scale_linearly() {
let setup = |e: &mut Engine<TestWorkbook>, n: u32| {
for i in 0..n {
define_literal(e, i);
}
};
assert_linear("update", &setup, &|e, i| {
e.update_name(
&format!("Name_{i}"),
NamedDefinition::Literal(LiteralValue::Number(-1.0)),
NameScope::Workbook,
)
.unwrap();
});
assert_linear("delete", &setup, &|e, i| {
e.delete_name(&format!("Name_{i}"), NameScope::Workbook)
.unwrap();
});
}
#[test]
fn unused_cell_and_formula_names_scale_linearly() {
assert_linear("cell names", &no_setup, &|e, i| {
let sid = e.sheet_id("Sheet1").unwrap();
e.define_name(
&format!("CellName_{i}"),
NamedDefinition::Cell(CellRef::new_absolute(sid, 5000 + i, 3)),
NameScope::Workbook,
)
.unwrap();
});
assert_linear("formula names", &no_setup, &|e, i| {
e.define_name(
&format!("FormulaName_{i}"),
NamedDefinition::Formula {
ast: parse(format!("=Sheet1!$D${}*2", 5000 + i)).unwrap(),
dependencies: vec![],
range_deps: vec![],
},
NameScope::Workbook,
)
.unwrap();
});
}
#[test]
fn unused_sources_scale_linearly() {
assert_linear("sources", &no_setup, &|e, i| {
e.define_source_scalar(&format!("Src_{i}"), Some(1))
.unwrap();
e.define_source_table(&format!("SrcT_{i}"), Some(1))
.unwrap();
});
}
#[test]
fn unused_tables_scale_linearly() {
assert_linear("tables", &no_setup, &|e, i| {
let sid = e.sheet_id("Sheet1").unwrap();
let r = 5000 + i * 3;
e.define_table(
&format!("Tbl_{i}"),
RangeRef::new(
CellRef::new_absolute(sid, r, 10),
CellRef::new_absolute(sid, r + 1, 11),
),
true,
vec!["a".into(), "b".into()],
false,
)
.unwrap();
});
}
#[test]
fn late_symbols_bind_their_waiting_readers_without_a_rebuild() {
let mut e = engine(10);
let sid = e.sheet_id("Sheet1").unwrap();
e.set_cell_value("Sheet1", 1, 5, LiteralValue::Number(3.0))
.unwrap();
e.set_cell_formula("Sheet1", 1, 6, parse("=Later+1").unwrap())
.unwrap();
e.set_cell_formula("Sheet1", 2, 6, parse("=Outer*10").unwrap())
.unwrap();
e.set_cell_formula("Sheet1", 3, 6, parse("=SUM(LateTbl[a])").unwrap())
.unwrap();
e.evaluate_all().unwrap();
let builds = e.graph.authority_host().builds();
e.define_name(
"Later",
NamedDefinition::Cell(CellRef::new_absolute(sid, 0, 4)),
NameScope::Workbook,
)
.unwrap();
e.define_name(
"Outer",
NamedDefinition::Formula {
ast: parse("=Later*2").unwrap(),
dependencies: vec![],
range_deps: vec![],
},
NameScope::Workbook,
)
.unwrap();
e.set_cell_value("Sheet1", 10, 12, LiteralValue::Number(5.0))
.unwrap();
e.define_table(
"LateTbl",
RangeRef::new(
CellRef::new_absolute(sid, 8, 11),
CellRef::new_absolute(sid, 9, 11),
),
true,
vec!["a".into()],
false,
)
.unwrap();
e.evaluate_all().unwrap();
assert_eq!(
e.get_cell_value("Sheet1", 1, 6),
Some(LiteralValue::Number(4.0))
);
assert_eq!(
e.get_cell_value("Sheet1", 2, 6),
Some(LiteralValue::Number(60.0))
);
assert_eq!(
e.get_cell_value("Sheet1", 3, 6),
Some(LiteralValue::Number(5.0))
);
e.set_cell_value("Sheet1", 1, 5, LiteralValue::Number(7.0))
.unwrap();
e.set_cell_value("Sheet1", 10, 12, LiteralValue::Number(6.0))
.unwrap();
e.evaluate_all().unwrap();
assert_eq!(
e.get_cell_value("Sheet1", 1, 6),
Some(LiteralValue::Number(8.0))
);
assert_eq!(
e.get_cell_value("Sheet1", 2, 6),
Some(LiteralValue::Number(140.0))
);
assert_eq!(
e.get_cell_value("Sheet1", 3, 6),
Some(LiteralValue::Number(6.0))
);
assert_eq!(e.graph.authority_host().builds(), builds);
e.delete_name("Later", NameScope::Workbook).unwrap();
e.define_name(
"Later",
NamedDefinition::Literal(LiteralValue::Number(1.0)),
NameScope::Workbook,
)
.unwrap();
e.evaluate_all().unwrap();
assert_eq!(
e.get_cell_value("Sheet1", 2, 6),
Some(LiteralValue::Number(20.0))
);
e.update_name(
"Later",
NamedDefinition::Literal(LiteralValue::Number(2.0)),
NameScope::Workbook,
)
.unwrap();
e.evaluate_all().unwrap();
assert_eq!(
e.get_cell_value("Sheet1", 2, 6),
Some(LiteralValue::Number(40.0))
);
assert_eq!(e.graph.authority_host().builds(), builds);
e.graph.authority_host().store().check().unwrap();
}