1package sheet23import (4 "fmt"5 "os"6 "runtime/debug"7 "testing"8)910// withEvalDepth lowers maxEvalDepth for a test.11func withEvalDepth(t *testing.T, n int) {12 t.Helper()13 old := maxEvalDepth14 maxEvalDepth = n15 t.Cleanup(func() { maxEvalDepth = old })16}1718// chain fills column col, rows 1 to n, with =A(row-1)+1 below a 1, so19// the last is n.20func chain(s *Sheet, col, n int) {21 for row := range n {22 in := "1"23 if row > 0 {24 in = "=" + Addr{Col: col, Row: row - 1}.String() + "+1"25 }26 s.Load(Addr{Col: col, Row: row}, in, Format{}, Style{})27 }28}2930// Evaluation past maxEvalDepth puts cells off and comes back to them:31// values come out as if evaluated in one go.32func TestDeepChains(t *testing.T) {33 withEvalDepth(t, 10)34 s := New()35 chain(s, 0, 1000)36 // Deep formulas in a chain: each cell nests 8 levels.37 for row := range 300 {38 in := "=1"39 if row > 0 {40 in = "=((((((((" + Addr{Col: 1, Row: row - 1}.String() + "+1))))))))"41 }42 s.Load(Addr{Col: 1, Row: row}, in, Format{}, Style{})43 }44 s.RecalcAll()45 if got := s.Value(Addr{Row: 999}); got != num(1000) {46 t.Errorf("A1000 = %+v", got)47 }48 if got := s.Value(Addr{Col: 1, Row: 299}); got != num(300) {49 t.Errorf("B300 = %+v", got)50 }51 if s.Book().Circular {52 t.Error("a chain was taken for a cycle")53 }54 // An edit at the top recalculates the chain the same way.55 s.Set(at("A1"), "5")56 if got := s.Value(Addr{Row: 999}); got != num(1004) {57 t.Errorf("after an edit, A1000 = %+v", got)58 }59}6061// A cycle longer than maxEvalDepth is still a cycle.62func TestDeepCycle(t *testing.T) {63 withEvalDepth(t, 10)64 s := New()65 chain(s, 0, 500)66 s.Set(at("A1"), "=A500+1")67 if !s.Book().Circular {68 t.Error("a cycle of 500 cells wasn't found")69 }70 if got := s.Value(at("A250")); got.Kind != Error {71 t.Errorf("A250, in the cycle, = %+v", got)72 }73 s.Set(at("A1"), "1")74 if s.Book().Circular || s.Value(at("A500")) != num(500) {75 t.Errorf("after breaking the cycle: circular %v, A500 = %+v", s.Book().Circular, s.Value(at("A500")))76 }77}7879// A chain through every cell of a full sheet fits in a bounded stack.80// Slow (2 M cells, 1 GB): set SHEET_FULL_CHAIN=1 to run it.81func TestFullSheetChain(t *testing.T) {82 if os.Getenv("SHEET_FULL_CHAIN") == "" {83 t.Skip("set SHEET_FULL_CHAIN=1 to evaluate a chain through all 2 M cells")84 }85 defer debug.SetMaxStack(debug.SetMaxStack(64 << 20))86 s := New()87 for col := range MaxCols {88 for row := range MaxRows {89 in := "1"90 switch {91 case row > 0:92 in = "=" + Addr{Col: col, Row: row - 1}.String() + "+1"93 case col > 0:94 in = "=" + Addr{Col: col - 1, Row: MaxRows - 1}.String() + "+1"95 }96 s.Load(Addr{Col: col, Row: row}, in, Format{}, Style{})97 }98 }99 s.RecalcAll()100 if got, want := s.Value(Addr{Col: MaxCols - 1, Row: MaxRows - 1}), num(MaxCols*MaxRows); got != want {101 t.Errorf("IV8192 = %+v, want %v", got, fmt.Sprint(want.Num))102 }103}104105// Every cell of a cycle is #REF!, even one whose formula would make106// something else of the #REF! it reads (COUNTIF skips errors, IFERROR107// catches them), whichever of its cells is evaluated first: typed in108// either order, or all at once on opening.109func TestCycleIsRefWhateverReadsIt(t *testing.T) {110 for _, reader := range []string{`=COUNTIF(F1:F20,">2")`, "=IFERROR(F13,0)"} {111 for _, order := range [][2]string{{"C5", "F13"}, {"F13", "C5"}} {112 s := New()113 in := map[string]string{"C5": reader, "F13": "=C5+1"}114 for _, a := range order {115 s.Set(at(a), in[a])116 }117 reopened := roundTrip(t, s)118 for _, a := range order {119 if v := s.Value(at(a)); v != ErrRef {120 t.Errorf("%s typed %v: %s = %v", reader, order, a, v)121 }122 if v := reopened.Value(at(a)); v != ErrRef {123 t.Errorf("%s reopened: %s = %v", reader, a, v)124 }125 }126 }127 }128}