cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / functions / reader_test.go
reader_test.go 5.3K
1package functions23import (4 "slices"5 "testing"67 "src.rest/cat/012/internal/value"8)910// fakeBook is one sheet of values, "", for testing the Reader alone. It11// counts the values it reads and the cells it scans.12type fakeBook struct {13 cells map[Addr]Value14 read int // values read, as evaluating them would15 scanned int // cells found by Scan16}1718func (b *fakeBook) Cell(sheet string, a Addr) Value {19 if sheet != "" {20 return value.ErrRef21 }22 b.read++23 return b.cells[a]24}2526func (b *fakeBook) Scan(sheet string, r Rect, from Addr, addrs []Addr, vals []Value) int {27 if sheet != "" {28 return -129 }30 var in []Addr31 for a := range b.cells {32 if r.Contains(a) && (a.Row > from.Row || a.Row == from.Row && a.Col >= from.Col) {33 in = append(in, a)34 }35 }36 slices.SortFunc(in, func(x, y Addr) int {37 if x.Row != y.Row {38 return x.Row - y.Row39 }40 return x.Col - y.Col41 })42 n := 043 for _, a := range in {44 if n == len(addrs) {45 break46 }47 addrs[n] = a48 n++49 b.scanned++50 if vals != nil {51 vals[n-1] = b.Cell(sheet, a)52 if vals[n-1].Kind == value.Error {53 break54 }55 }56 }57 return n58}5960func (b *fakeBook) Bounds(string, Rect) (Rect, bool, bool) { return Rect{}, false, true }61func (b *fakeBook) RangeAgg(string, Rect) (Agg, *Value, bool) { return Agg{}, nil, false }62func (b *fakeBook) Fold(_ string, _ Rect, s Agg) (Agg, *Value) { return s, nil }63func (b *fakeBook) Ask(RemoteCall) (RemoteAnswer, Value) { return RemoteAnswer{}, ErrNoRemote }64func (b *fakeBook) Paged(string) bool { return false }65func (b *fakeBook) Stream(StreamCall) (Value, *Array) { return value.ErrValue, nil }6667func newFakeReader(cells map[Addr]Value) (*Reader, *fakeBook) {68 b := &fakeBook{cells: cells}69 depth := 070 return NewReader(b, &depth, false), b71}7273// column is n numbers 1..n down column A, every other row, and a 3-wide74// block of the same beside them in C:E.75func column(n int) map[Addr]Value {76 cells := map[Addr]Value{}77 for i := range n {78 cells[Addr{Row: 2 * i}] = num(float64(i + 1))79 for c := 2; c <= 4; c++ {80 cells[Addr{Col: c, Row: 2 * i}] = num(float64(i + 1))81 }82 }83 return cells84}8586func TestReaderCells(t *testing.T) {87 const n = 300088 rd, _ := newFakeReader(column(n))89 for _, r := range []Rect{90 {To: Addr{Row: 2*n - 1}}, // one column, in chunks91 {From: Addr{Col: 2}, To: Addr{Col: 4, Row: 2*n - 1}}, // three, resuming mid-row92 } {93 width := r.To.Col - r.From.Col + 194 var got []float6495 rd.cells("", r, func(a Addr, v Value) bool {96 got = append(got, v.Num)97 return true98 })99 if len(got) != n*width {100 t.Fatalf("%v: read %d cells, want %d", r, len(got), n*width)101 }102 for i, f := range got {103 if f != float64(i/width+1) {104 t.Fatalf("%v: cell %d is %v, want %v", r, i, f, i/width+1)105 }106 }107 }108 if rd.level != 0 {109 t.Errorf("level %d after reads, want 0", rd.level)110 }111}112113// Ranges are read ahead in chunks, but no cell past the first error is114// evaluated, as when cells were read one at a time.115func TestReaderStopsAtError(t *testing.T) {116 cells := column(100)117 cells[Addr{Row: 2 * 40}] = value.ErrDiv0118 rd, b := newFakeReader(cells)119 seen := 0120 var e Value121 rd.cells("", Rect{To: Addr{Row: 199}}, func(_ Addr, v Value) bool {122 seen++123 e = v124 return v.Kind != value.Error125 })126 if seen != 41 || e != value.ErrDiv0 || b.read != 41 {127 t.Errorf("saw %d cells, evaluated %d, last %v; want 41, 41 and #DIV/0!", seen, b.read, e)128 }129}130131// A lookup that stops early scans about as many cells as it looked at,132// however long the range and however long the last one read was.133func TestReaderStoredStopsEarly(t *testing.T) {134 rd, b := newFakeReader(column(5000))135 whole := Rect{To: Addr{Row: 9999}}136 rd.stored("", whole, func(Addr) bool { return true }) // grows the buffer137 b.scanned = 0138 seen := 0139 rd.stored("", whole, func(Addr) bool { seen++; return seen < 20 })140 if seen != 20 || b.scanned > 2*seen+firstChunk {141 t.Errorf("looked at %d cells and scanned %d; want 20 and at most %d", seen, b.scanned, 2*seen+firstChunk)142 }143 if b.read != 0 {144 t.Errorf("evaluated %d cells, want none", b.read)145 }146}147148// A read nested in another (a cell of the range evaluating a range of its149// own) uses a buffer of its own.150func TestReaderNested(t *testing.T) {151 rd, _ := newFakeReader(column(50))152 outer := 0.0153 rd.cells("", Rect{To: Addr{Row: 99}}, func(_ Addr, v Value) bool {154 inner := 0.0155 rd.cells("", Rect{From: Addr{Col: 2}, To: Addr{Col: 4, Row: 99}}, func(_ Addr, w Value) bool {156 inner += w.Num157 return true158 })159 if inner != 3*50*51/2 {160 t.Fatalf("inner sum %v", inner)161 }162 outer += v.Num163 return true164 })165 if outer != 50*51/2 || rd.level != 0 {166 t.Errorf("outer sum %v, level %d", outer, rd.level)167 }168}169170func TestReaderMissingSheet(t *testing.T) {171 rd, _ := newFakeReader(column(3))172 var got []Value173 rd.cells("Nowhere", Rect{To: Addr{Row: 9}}, func(_ Addr, v Value) bool {174 got = append(got, v)175 return true176 })177 if len(got) != 1 || got[0] != value.ErrRef {178 t.Errorf("got %v, want one #REF!", got)179 }180 rd.stored("Nowhere", Rect{To: Addr{Row: 9}}, func(Addr) bool {181 t.Error("a missing sheet holds a cell")182 return true183 })184}185186func TestFunctionTable(t *testing.T) {187 for _, f := range Funcs() {188 if f.Desc == "" || (f.Max != 0 && f.Args == "") || f.eval == nil {189 t.Errorf("%s is missing its description, signature or implementation", f.Name)190 }191 }192}