cat / 012
clone

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

cat/module-path
012 / internal / notebook / notebook_test.go
notebook_test.go 9.7K
1package notebook23import (4 "slices"5 "testing"6)78func TestName(t *testing.T) {9 for _, tc := range []struct{ src, name string }{10 {"sales = open sales.csv", "sales"},11 {"big=\n$sales | first", "big"},12 {"ls | where name == x", ""},13 {"a == b", ""},14 {"a =~ b", ""},15 {"in = ls", ""},16 {"9x = ls", ""},17 {"let x = 1", ""},18 {"__s = 1", ""},19 {"files = ls\n$files | length", ""},20 {"files = ls\nn = $files | length", "n"},21 {"n = ls\n| where size > 1kb", "n"},22 {"n = ls |\n where size > 1kb", "n"},23 {"n = ls\n# keep the big ones\n| where size > 1kb", "n"},24 {"n = ls\n\n| length", ""},25 {"n = ls; $n | length", ""},26 {"n = [\n1\n2\n]", "n"},27 {"n = ls # all of them", "n"},28 {"# n = ls", ""},29 } {30 if got := (Cell{Source: tc.src}).Name(); got != tc.name {31 t.Errorf("Name(%q) = %q, want %q", tc.src, got, tc.name)32 }33 }34 if (Cell{Kind: Note, Source: "x = y"}).Name() != "" {35 t.Error("a note cell has a name")36 }37}3839func TestWithName(t *testing.T) {40 for _, tc := range []struct{ src, name, want string }{41 {"ls", "files", "files = ls"},42 {"files = ls", "", "ls"},43 {"files = ls", "all", "all = ls"},44 {"files = ls\n$files | length", "n", "files = ls\nn = $files | length"},45 {"files = ls\nn = $files | length", "", "files = ls\n$files | length"},46 {"", "x", "x = "},47 {"# just a note", "x", "# just a note\nx = "},48 } {49 if got := WithName(tc.src, tc.name); got != tc.want {50 t.Errorf("WithName(%q, %q) = %q, want %q", tc.src, tc.name, got, tc.want)51 }52 }53}5455// The owner's cell: files is the cell's own, read by its second line, not56// a cell it reads; the cell has no name, and hands files to later cells.57func TestOwnersCell(t *testing.T) {58 src := "files = ls | where type == file\n$files | where size > 1kb | sort-by size --reverse"59 p := Parse(src)60 if p.Name() != "" || len(p.Refs()) != 0 || !slices.Equal(p.Assigned(), []string{"files"}) {61 t.Errorf("name %q refs %v assigned %v", p.Name(), p.Refs(), p.Assigned())62 }63 cmd, exports, _ := p.Command()64 want := "let files = ls | where type == file\n{__out: ($files | where size > 1kb | sort-by size --reverse), files: $files}"65 if cmd != want || !slices.Equal(exports, []string{"files"}) {66 t.Errorf("command %q exports %v", cmd, exports)67 }68 if got := p.StreamCommand(); got != "let files = ls | where type == file\n$files | where size > 1kb | sort-by size --reverse" {69 t.Errorf("stream command %q", got)70 }71 cs := cells(src, "$files | length")72 order, cycle := Order(cs, []int{0, 1})73 if !slices.Equal(order, []int{0, 1}) || len(cycle) != 0 {74 t.Errorf("order %v cycle %v", order, cycle)75 }76 run, err := Prepare(cs, 0, func(int) *Output { return nil })77 if err != nil || run.Command != want || len(run.Tables) != 0 {78 t.Errorf("prepare %+v %v", run, err)79 }80 // The second cell reads files from the first's run.81 outs := map[int]*Output{1: {NUON: []byte("[]"), Seq: 4, Vars: map[string][]byte{"files": []byte("[[name]; [a]]")}}}82 out := func(id int) *Output { return outs[id] }83 run, err = Prepare(cs, 1, out)84 if err != nil || string(run.Tables["files"]) != "[[name]; [a]]" || run.Reads["files"] != 4 {85 t.Errorf("prepare the reader %+v %v", run, err)86 }87 // An output read from a file keeps no variables: the first cell runs88 // before the second.89 outs[1].Vars = nil90 if got := Inputs(cs, 1, out); !slices.Equal(got, []int{0}) {91 t.Errorf("inputs %v", got)92 }93 if _, err := Prepare(cs, 1, out); err == nil {94 t.Error("prepared a cell reading a variable its cell didn't hand back")95 }96}9798func TestComments(t *testing.T) {99 src := "# x = ls\n# $y | $sheet.A1:B2 | $selection\nls # $z\n| where name == \"a#b\" # $w\n| where name != 'c # $v'\n| where name != r#'d # $u'#\n| append [1#2]\n| append $\"($q # $t\n)\""100 p := Parse(src)101 if p.Name() != "" || !slices.Equal(p.Refs(), []string{"q"}) || p.ReadsSelection() || len(p.Ranges) != 0 {102 t.Errorf("name %q refs %v selection %v ranges %v", p.Name(), p.Refs(), p.ReadsSelection(), p.Ranges)103 }104 var comments []string105 for _, c := range p.Comments {106 comments = append(comments, src[c[0]:c[1]])107 }108 if !slices.Equal(comments, []string{"# x = ls", "# $y | $sheet.A1:B2 | $selection", "# $z", "# $w", "# $t"}) {109 t.Errorf("comments %q", comments)110 }111 if len(p.Stmts) != 1 {112 t.Errorf("statements %+v", p.Stmts)113 }114 cmd, _, _ := p.Command()115 if len(cmd) != len(src) || cmd[:8] != " " {116 t.Errorf("command %q", cmd)117 }118 // Inside brackets a comment may follow a , or a :.119 if got := Parse("[1,#x\n2]").Comments; len(got) != 1 {120 t.Errorf("comments in a list %v", got)121 }122 if got := Parse("echo x,#y").Comments; len(got) != 0 {123 t.Errorf("a comment in a word %v", got)124 }125}126127func TestStatements(t *testing.T) {128 src := "a = ls\nb = $a | length; c = $b + 1\nlet d = 2\n$c + $d + $e"129 p := Parse(src)130 if !slices.Equal(p.Assigned(), []string{"a", "b", "c"}) || !slices.Equal(p.Refs(), []string{"e"}) {131 t.Errorf("assigned %v refs %v", p.Assigned(), p.Refs())132 }133 cmd, exports, _ := p.Command()134 want := "let a = ls\nlet b = $a | length; let c = $b + 1\nlet d = 2\n{__out: ($c + $d + $e), a: $a, b: $b, c: $c}"135 if cmd != want || !slices.Equal(exports, []string{"a", "b", "c"}) {136 t.Errorf("command %q exports %v", cmd, exports)137 }138 // The last statement's name is the output: it's not exported apart.139 cmd, exports, _ = Parse("x = 1\nx = $x + 1").Command()140 if cmd != "let x = 1\n$x + 1" || len(exports) != 0 {141 t.Errorf("command %q exports %v", cmd, exports)142 }143 // $x on the line assigning x reads the x from before.144 if got := Parse("x = $x | first").Refs(); !slices.Equal(got, []string{"x"}) {145 t.Errorf("refs %v", got)146 }147}148149// A variable of the cell's own shadows another cell's name: the cell150// doesn't read that cell.151func TestLocalShadows(t *testing.T) {152 cs := cells("sales = open sales.csv", "sales = [[a]; [1]]\n$sales | length", "n = $sales | length")153 if got := Reads(cs, Vars(cs), 1); len(got) != 0 {154 t.Errorf("the shadowing cell reads %v", got)155 }156 if got := Reads(cs, Vars(cs), 2); !slices.Equal(got, []int{0}) {157 t.Errorf("the third cell reads %v", got)158 }159 if got := Dependents(cs, 0); !slices.Equal(got, []int{2}) {160 t.Errorf("dependents %v", got)161 }162 if Vars(cs)["sales"] != 0 {163 t.Errorf("sales is the first cell's, as its name: %v", Vars(cs))164 }165 // A cell reading another's name before assigning it reads that cell.166 cs = cells("sales = ls", "big = $sales | first\nsales = 1\n$sales")167 if got := Reads(cs, Vars(cs), 1); !slices.Equal(got, []int{0}) {168 t.Errorf("reads %v", got)169 }170}171172func TestRanges(t *testing.T) {173 src := "$sheet.A1:C9 | append $sheet.'Q1 data'!B2:B5 | append $sheet.A1:C9.name | $x$sheet.A1 | $sheet. | append \"$sheet.A1\""174 p := Parse(src)175 var spans []string176 for _, s := range p.Ranges {177 spans = append(spans, src[s[0]:s[1]])178 }179 if !slices.Equal(spans, []string{"$sheet.A1:C9", "$sheet.'Q1 data'!B2:B5", "$sheet.A1:C9"}) {180 t.Errorf("ranges %q", spans)181 }182 got, _, refs := p.Command()183 want := "$__sheet1 | append $__sheet2 | append $__sheet1.name | $x$sheet.A1 | $sheet. | append \"$sheet.A1\""184 if got != want {185 t.Errorf("command = %q", got)186 }187 if len(refs) != 2 || refs[0].Ref != "A1:C9" || refs[1].Ref != "'Q1 data'!B2:B5" || refs[1].Var != "__sheet2" {188 t.Errorf("refs %+v", refs)189 }190 if !slices.Equal(p.Refs(), []string{"x"}) {191 t.Errorf("refs %v", p.Refs())192 }193 if !Parse("$selection | math sum").ReadsSelection() || Parse("$selections").ReadsSelection() {194 t.Error("ReadsSelection")195 }196 if got := Parse("$r1 | where x == $in.a and $nu.home | $r_2.x | $selection | $sheet.A1").Refs(); !slices.Equal(got, []string{"r1", "r_2"}) {197 t.Errorf("refs %v", got)198 }199}200201func cells(srcs ...string) []Cell {202 out := make([]Cell, len(srcs))203 for i, s := range srcs {204 out[i] = Cell{ID: i + 1, Source: s}205 }206 return out207}208209func TestOrder(t *testing.T) {210 cs := cells("big = $files | first", "files = ls", "# note", "$big | length", "a = $b", "b = $a")211 cs[2].Kind = Note212 order, cycle := Order(cs, []int{0, 1, 2, 3, 4, 5})213 if !slices.Equal(order, []int{1, 0, 3}) || !slices.Equal(cycle, []int{4, 5}) {214 t.Errorf("order %v cycle %v", order, cycle)215 }216 if got := Dependents(cs, 1); !slices.Equal(got, []int{0, 3}) {217 t.Errorf("dependents %v", got)218 }219 outs := map[int]*Output{2: {NUON: []byte("x")}}220 if got := Inputs(cs, 3, func(id int) *Output { return outs[id] }); !slices.Equal(got, []int{0}) {221 t.Errorf("inputs %v", got)222 }223 dup := cells("x = ls", "X = ps")224 if Taken(dup, 1) == "" || Taken(dup, 0) != "" {225 t.Error("Taken")226 }227}228229func TestStale(t *testing.T) {230 cs := cells("files = ls", "big = $files | first", "$big", "ps")231 outs := map[int]*Output{}232 out := func(id int) *Output { return outs[id] }233 run := func(i int, seq int) {234 o := &Output{NUON: []byte("x"), Seq: seq, Count: seq, Source: cs[i].Source, Reads: map[string]int{}}235 for _, n := range cs[i].Parse().Refs() {236 if j, ok := Names(cs)[n]; ok && outs[cs[j].ID] != nil {237 o.Reads[n] = outs[cs[j].ID].Seq238 }239 }240 outs[cs[i].ID] = o241 }242 run(0, 1)243 run(1, 2)244 run(2, 3)245 run(3, 4)246 if st := Stale(cs, out); len(st) != 0 {247 t.Fatalf("stale after running all: %v", st)248 }249 run(0, 5) // files ran again: big and what reads it are stale250 if st := Stale(cs, out); !st[2] || !st[3] || st[1] || st[4] {251 t.Errorf("stale %v", st)252 }253 run(1, 6)254 run(2, 7)255 cs[3].Source = "ps | first" // edited since it ran256 if st := Stale(cs, out); len(st) != 1 || !st[4] {257 t.Errorf("stale after editing %v", st)258 }259}260261func TestSettleAndKept(t *testing.T) {262 cs := cells("files = ls", "$files | first", "ps")263 outs := map[int]*Output{1: {NUON: []byte("12345"), Seq: 1}, 2: {NUON: []byte("123"), Seq: 2}, 3: {NUON: []byte("1234567"), Seq: 3}}264 Settle(cs, outs)265 if st := Stale(cs, func(id int) *Output { return outs[id] }); len(st) != 0 {266 t.Errorf("stale after settling: %v", st)267 }268 kept := Kept(cs, func(id int) *Output { return outs[id] }, Caps{Cell: 6, Total: 9})269 if !kept[1] || !kept[2] || kept[3] {270 t.Errorf("kept %v", kept)271 }272}