]> git.giacomolli.uk - onechat/blob - view_test.go
Remove two timing gaps from the memory tests
[onechat] / view_test.go
1 package main
2
3 import (
4 "slices"
5 "strings"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9 )
10
11 func TestViewMergesInTheOrderOfTaelinsPush(t *testing.T) {
12 tree := builtTree{}
13 view := newView(0, 0)
14 var states *pushEntry
15 for tick := range 20001 {
16 states = push(tick, states)
17 starts := states.starts()
18 view.high = len(starts)
19 view.low = len(starts)
20 view.Append(tree)
21
22 lines := make([]int, len(view.lines))
23 for n, line := range view.lines {
24 lines[n] = line.index << line.level
25 }
26
27 require.Equal(t, starts, lines, "t=%d", tick)
28 }
29 }
30
31 func TestViewMergesInOneBatchFromHighToLow(t *testing.T) {
32 tree := builtTree{}
33 view := newView(10, 5)
34 sizes := make([]int, 0, 20)
35 for range 20 {
36 view.Append(tree)
37 sizes = append(sizes, view.Size(tree))
38 }
39
40 require.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 5, 6, 7, 8, 9, 10, 5, 6, 7, 8}, sizes)
41 }
42
43 func TestViewMergesOnlyBuiltParents(t *testing.T) {
44 tree := partTree{
45 sizes: map[nodeKey]int{
46 {0, 0}: 1,
47 {0, 1}: 1,
48 {0, 2}: 1,
49 {0, 3}: 1,
50 {0, 4}: 1,
51 {0, 5}: 1,
52 },
53 }
54
55 view := newView(3, 2)
56 for range 4 {
57 view.Append(tree)
58 }
59
60 require.Equal(t, []nodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, view.lines)
61 require.True(t, view.merging)
62
63 tree.sizes[nodeKey{1, 0}] = 1
64 view.Append(tree)
65 require.Equal(t, []nodeKey{{1, 0}, {0, 2}, {0, 3}, {0, 4}}, view.lines)
66 require.True(t, view.merging)
67
68 tree.sizes[nodeKey{1, 1}] = 1
69 tree.sizes[nodeKey{1, 2}] = 1
70 tree.sizes[nodeKey{2, 0}] = 1
71 view.Append(tree)
72 require.Equal(t, []nodeKey{{2, 0}, {1, 2}}, view.lines)
73 require.False(t, view.merging)
74 }
75
76 func TestViewRendersPendingLinesWithoutTheirMessage(t *testing.T) {
77 tree := partTree{
78 sizes: map[nodeKey]int{
79 {0, 0}: 3,
80 {0, 1}: 3,
81 {1, 0}: 7,
82 },
83 }
84
85 view, err := restoreView(viewState{
86 Lines: [][2]int{{1, 0}, {0, 2}},
87 }, chatHigh, chatLow, tree)
88 require.NoError(t, err)
89 require.Equal(t, "<chat>\n0+2|aaa aaa\n2+1|"+pending+"\n</chat>", view.Render(tree))
90 require.Equal(t, 7+len(pending), view.Size(tree))
91 }
92
93 func TestViewCoarsenTakesTheCoarserLines(t *testing.T) {
94 view, err := restoreView(viewState{
95 Lines: [][2]int{{1, 0}, {0, 2}, {0, 3}, {0, 4}, {0, 5}},
96 }, 100, 100, builtTree{})
97 require.NoError(t, err)
98
99 other, err := restoreView(viewState{
100 Lines: [][2]int{{0, 0}, {0, 1}, {1, 1}, {0, 4}, {0, 5}},
101 }, 100, 100, builtTree{})
102 require.NoError(t, err)
103
104 view.Coarsen(other, builtTree{})
105 require.Equal(t, []nodeKey{{1, 0}, {1, 1}, {0, 4}, {0, 5}}, view.lines)
106
107 view.low = 2
108 view.Coarsen(other, builtTree{})
109 require.Equal(t, []nodeKey{{2, 0}, {1, 2}}, view.lines)
110 require.False(t, view.merging)
111 }
112
113 func TestViewContextStopsAtTheFirstPendingLine(t *testing.T) {
114 tree := partTree{
115 sizes: map[nodeKey]int{
116 {0, 0}: 1,
117 {0, 1}: 1,
118 {1, 0}: 1,
119 {0, 3}: 1,
120 },
121 }
122
123 view, err := restoreView(viewState{
124 Lines: [][2]int{{1, 0}, {0, 2}, {0, 3}},
125 }, 100, 100, tree)
126 require.NoError(t, err)
127 require.Equal(t, "<chat>\n</chat>", view.Context(tree, 0))
128 require.Equal(t, "<chat>\n0+2|a\n</chat>", view.Context(tree, 2))
129 require.Equal(t, "<chat>\n0+2|a\n</chat>", view.Context(tree, 3))
130 }
131
132 func TestRestoreViewRejectsAGap(t *testing.T) {
133 _, err := restoreView(viewState{
134 Lines: [][2]int{{0, 0}, {0, 2}},
135 }, chatHigh, chatLow, builtTree{})
136 require.ErrorContains(t, err, "does not start at message 1")
137 }
138
139 // pushEntry is one entry of Taelin's rollback state list, newest first. Under
140 // push alone, life stays 0, so the entry leaves it out.
141 type pushEntry struct {
142 keep bool
143 state int
144 older *pushEntry
145 }
146
147 // push is the push of rollback_state_list.js.
148 func push(state int, states *pushEntry) *pushEntry {
149 if states == nil {
150 return &pushEntry{
151 state: state,
152 }
153 }
154
155 if !states.keep {
156 return &pushEntry{
157 keep: true,
158 state: states.state,
159 older: states.older,
160 }
161 }
162
163 return &pushEntry{
164 state: state,
165 older: push(states.state, states.older),
166 }
167 }
168
169 // starts returns the states of the list, oldest first.
170 func (e *pushEntry) starts() []int {
171 var starts []int
172 for entry := e; entry != nil; entry = entry.older {
173 starts = append(starts, entry.state)
174 }
175
176 slices.Reverse(starts)
177
178 return starts
179 }
180
181 // builtTree is a tree with each node built, with 1 byte of text.
182 type builtTree struct{}
183
184 func (builtTree) Node(level, index int) (Node, bool) {
185 return testNode(level, index, "a"), true
186 }
187
188 // partTree is a tree with the built nodes of its sizes.
189 type partTree struct {
190 sizes map[nodeKey]int
191 }
192
193 func (p partTree) Node(level, index int) (Node, bool) {
194 size, built := p.sizes[nodeKey{level, index}]
195 if !built {
196 return Node{}, false
197 }
198
199 text := strings.Repeat("a", size)
200 if size == 7 {
201 text = "aaa\naaa"
202 }
203
204 return testNode(level, index, text), true
205 }