1package sheet23import (4 "fmt"5 "math"6 "testing"7)89// bruteStats sums r cell by cell, as RangeStats did before the index.10func bruteStats(s *Sheet, r Rect) Stats {11 var st Stats12 for row := r.From.Row; row <= r.To.Row; row++ {13 for col := r.From.Col; col <= r.To.Col; col++ {14 if c := s.Cell(Addr{Col: col, Row: row}); !c.Blank() {15 st.add(c.Value)16 }17 }18 }19 return st20}2122func sameStats(a, b Stats) bool {23 return a.Count == b.Count && a.Nums == b.Nums && math.Abs(a.Sum-b.Sum) <= 1e-9*math.Max(1, math.Abs(b.Sum))24}2526// TestRangeStatsIndex checks the block index against summing cell by27// cell, over selections that start and end inside blocks and on their28// edges, as cells are set, recalculated, deleted and undone.29func TestRangeStatsIndex(t *testing.T) {30 s := New()31 for row := range 2000 {32 for col := range 10 {33 s.Load(Addr{Col: col, Row: row}, fmt.Sprint(row*10+col), Format{}, Style{})34 }35 }36 s.Load(at("L1"), "=SUM(A1:A2000)", Format{}, Style{})37 s.Load(at("K5"), "text", Format{}, Style{})38 s.RecalcAll()39 rects := []Rect{40 NewRect(at("A1"), at("L8192")),41 NewRect(at("A1"), at("IV8192")),42 NewRect(at("B2"), at("K1999")),43 NewRect(at("A64"), at("C129")), // rows 63..128: one whole block44 NewRect(at("A65"), at("C128")), // rows 64..127: exactly one block45 NewRect(at("C10"), at("F100")), // no whole block46 NewRect(at("A1"), at("A8192")), // one column47 NewRect(at("A100"), at("J120")), // small: cell by cell48 }49 check := func(when string) {50 t.Helper()51 for _, r := range rects {52 if got, want := s.RangeStats(r), bruteStats(s, r); !sameStats(got, want) {53 t.Errorf("%s: RangeStats(%s) = %+v, want %+v", when, r, got, want)54 }55 }56 }57 check("built")58 if !s.cells.statsUsed {59 t.Fatal("the statistics index wasn't built")60 }61 s.Set(at("A1"), "1000000") // recalculates L1 as well62 check("after an edit")63 s.Set(at("C70"), "")64 check("after clearing a cell")65 s.Set(at("Z8000"), "7")66 check("after a cell outside the data")67 s.Undo()68 s.Undo()69 check("after undo")70 s.Redo()71 check("after redo")72}7374func TestRangeStatsSparse(t *testing.T) {75 s := New()76 s.Set(at("A1"), "2")77 s.Set(at("IV8192"), "3")78 if got := s.RangeStats(NewRect(at("A1"), at("IV8192"))); got != (Stats{Sum: 5, Count: 2, Nums: 2}) {79 t.Errorf("whole sparse sheet = %+v", got)80 }81 if s.cells.statsUsed {82 t.Error("a sparse sheet built the statistics index")83 }84}8586// On the full grid the index takes memory only where there is data: a87// whole-sheet selection over cells scattered down a million rows and88// across 16,384 columns sums as cell by cell would, after edits too.89func TestRangeStatsFullGrid(t *testing.T) {90 s := New()91 for i := range 20000 {92 s.Load(Addr{Col: (i * 7919) % MaxCols, Row: (i * 104729) % MaxRows}, fmt.Sprint(i%97), Format{}, Style{})93 }94 s.RecalcAll()95 brute := func(r Rect) Stats {96 var st Stats97 for a, c := range s.cells.all() {98 if r.Contains(a) && !c.Blank() {99 st.add(c.Value)100 }101 }102 return st103 }104 all := Rect{To: Addr{Col: MaxCols - 1, Row: MaxRows - 1}}105 for _, r := range []Rect{all, {From: Addr{Col: 3, Row: 70}, To: Addr{Col: 9000, Row: 900000}}} {106 if got, want := s.RangeStats(r), brute(r); !sameStats(got, want) {107 t.Errorf("RangeStats(%s) = %+v, want %+v", r, got, want)108 }109 }110 s.Set(Addr{Col: 5, Row: 5}, "1000")111 s.Set(Addr{Col: (7 * 7919) % MaxCols, Row: (7 * 104729) % MaxRows}, "")112 if got, want := s.RangeStats(all), brute(all); !sameStats(got, want) {113 t.Errorf("after edits RangeStats = %+v, want %+v", got, want)114 }115 if !s.cells.statsUsed {116 t.Error("the index wasn't used")117 }118}