cat / 012
clone

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

Speed gate and stress cases for scrolling and summing linked sources

Dami committed cac0494
commit
cac049458be88b3dcd336aadb2df18b4dc109ca6
parent
9373b4dd607bfd4f0e389ecc164aa6f07d37c4a6
author
Dami <dami.etoile@gmail.com>
kind
test risk score 1.5 of 3, Jev's estimate
7 files changed, +198 -24
Minternal/paged/host.go22++++++++---
Minternal/ui/sources_test.go18+--------
Minternal/ui/speed_test.go52++++++++++++++++++++++++++
Minternal/ui/srccmds.go3+-
Ainternal/ui/srcshapes_test.go58+++++++++++++++++++++++++++++
Ainternal/ui/srcstress_test.go59++++++++++++++++++++++++++++++
Minternal/ui/testdata/speed.json10+++++
M internal/paged/host.go +16 -6
M internal/ui/sources_test.go +2 -16
M internal/ui/speed_test.go +52 -0
@@ -65,6 +65,56 @@ func outputCase(rows int) speedCase {
6565 }}6666 }6767 68+// sourceFrameCase is Page Down and Page Up in turn through to their69+// frames at 200 x 60, halfway down a linked source's tab, the pages70+// they show already read, as scrolling within what's read is.71+func sourceFrameCase() speedCase {72+ return speedCase{fmt.Sprintf("frame/source-%dx4", speedSourceRows), func() func() {73+ m := speedSourceModel()74+ m.srcView().MoveTo(speedSourceRows/2, 2)75+ keys := [2]tea.KeyPressMsg{{Code: tea.KeyPgDown}, {Code: tea.KeyPgUp}}76+ for _, k := range keys { // both windows read77+ m.Update(k)78+ settleSources(m)79+ }80+ t := newFakeTerm(200, 60)81+ i := 082+ return func() {83+ m.Update(keys[i%2])84+ t.frame(m)85+ i++86+ }87+ }}88+}89+90+// sourceSumCase is a linked source read again and a SUM over one of its91+// columns worked out again, as the file changing makes it, run to the92+// end rather than in the background.93+func sourceSumCase() speedCase {94+ return speedCase{fmt.Sprintf("source/sum-%d", speedSourceRows), func() func() {95+ m := speedSourceModel()96+ info, _ := m.sheet.Source()97+ m.showSheet(m.book().Sheet(0))98+ m.sheet.Set(sheet.Addr{}, "=SUM("+info.Name+"[amount])")99+ settleSources(m)100+ return func() {101+ m.src.host.Reload(info.Name)102+ if !settleSources(m) || m.sheet.Value(sheet.Addr{}).Kind != sheet.Number {103+ panic("the SUM over the source never settled")104+ }105+ }106+ }}107+}108+109+// speedSourceModel is a model showing the speed gate's source.110+func speedSourceModel() *Model {111+ path, err := speedSources()112+ if err != nil {113+ panic(err)114+ }115+ return sourceModel(path, 200, 60)116+}117+68118 // editCase is typing into one cell of a stress shape and the69119 // incremental recalculation that follows, two entries in turn.70120 func editCase(sh stress.Shape) speedCase {
@@ -108,6 +158,8 @@ func speedCases() []speedCase {
108158 frameCase("scale-8192x26", scaled),109159 frameCase("charts-8", charts),110160 outputCase(100000),161+ sourceFrameCase(),162+ sourceSumCase(),111163 editCase(fanin),112164 editCase(shape("chain-8192", func() *sheet.Sheet { return stress.Chain(stress.Rows) }, "A1", "2")),113165 editCase(shape("criteria-10xSUMIF8192", func() *sheet.Sheet { return stress.Criteria(stress.Rows, 10) }, "A4001", "7")),
M internal/ui/srccmds.go +1 -2
A internal/ui/srcshapes_test.go +58 -0
@@ -0,0 +1,58 @@
1+package ui2+3+import (4+ "os"5+ "path/filepath"6+ "sync"7+8+ "github.com/FelineStateMachine/012/internal/sheet"9+ "github.com/FelineStateMachine/012/internal/stress"10+)11+12+// Linked sources for the speed gate (speed_test.go) and the stress13+// benchmarks (srcstress_test.go): a model showing a source's tab, its14+// jobs run as they come.15+16+// sourceModel is a model w x h showing the tab of the Parquet file at17+// path linked as a source, opened, its first rows read.18+func sourceModel(path string, w, h int) *Model {19+ m := sized(sheet.New(), w, h)20+ m.addSource(sheet.LinkSource{Path: path})21+ settleSources(m)22+ return m23+}24+25+// settleSources runs what the sources have queued, and what that26+// queues, until nothing is, reporting whether it settled.27+func settleSources(m *Model) bool {28+ for range 100 {29+ run(m, m.syncSources())30+ if m.src.host == nil || !m.src.host.Pending() && m.src.running == 0 && !m.pagesPending() {31+ return true32+ }33+ }34+ return false35+}36+37+// pagesPending reports whether a view is being built.38+func (m *Model) pagesPending() bool {39+ for _, p := range m.src.pages {40+ if p.Building() {41+ return true42+ }43+ }44+ return false45+}46+47+// speedSources is the Parquet source of rows rows the speed gate48+// links, made once in a folder of the system's temporary one.49+var speedSources = sync.OnceValues(func() (string, error) {50+ dir := filepath.Join(os.TempDir(), "012-speed-sources")51+ if err := os.MkdirAll(dir, 0o755); err != nil {52+ return "", err53+ }54+ return stress.SalesParquet(dir, speedSourceRows)55+})56+57+// speedSourceRows is the rows of the speed gate's source.58+const speedSourceRows = 200_000
A internal/ui/srcstress_test.go +59 -0
@@ -0,0 +1,59 @@
1+//go:build stress2+3+package ui4+5+import (6+ "fmt"7+ "testing"8+9+ tea "charm.land/bubbletea/v2"10+11+ "github.com/FelineStateMachine/012/internal/stress"12+)13+14+// BenchmarkSourceScroll scrolls a linked source of ten million rows15+// (stress.SalesParquet) through to its frames: Page Down and Up within16+// the rows read, halfway down, as scrolling is between pages; and17+// jumping to rows all over the source, as dragging the scrollbar does,18+// reading the pages each jump shows (run to the end here, in the19+// background on the screen) before the frame that shows them. The row20+// numbers and the scrollbar are over every row, whatever the grid's.21+func BenchmarkSourceScroll(b *testing.B) {22+ path, err := stress.SalesParquet(stress.GeneratedDir(b.TempDir()), 10_000_000)23+ if err != nil {24+ b.Fatal(err)25+ }26+ defer stress.BenchLock(b)()27+ cases := []struct {28+ name string29+ keys []tea.KeyPressMsg30+ read bool31+ }{32+ {"pgdown", []tea.KeyPressMsg{{Code: tea.KeyPgDown}, {Code: tea.KeyPgUp}}, false},33+ {"jump", nil, true},34+ }35+ for _, c := range cases {36+ for _, sz := range termSizes[:2] {37+ b.Run(fmt.Sprintf("%s/%dx%d", c.name, sz.w, sz.h), func(b *testing.B) {38+ m := sourceModel(path, sz.w, sz.h)39+ m.srcView().MoveTo(5_000_000, 2)40+ for _, k := range c.keys {41+ m.Update(k)42+ settleSources(m)43+ }44+ t := newFakeTerm(sz.w, sz.h)45+ i := 046+ for b.Loop() {47+ if c.read {48+ m.srcView().MoveTo(int64(i)*1_234_567%10_000_000, 2)49+ settleSources(m)50+ } else {51+ m.Update(c.keys[i%2])52+ }53+ t.frame(m)54+ i++55+ }56+ })57+ }58+ }59+}
M internal/ui/testdata/speed.json +10 -0