]> git.giacomolli.uk - onechat/blob - tree_test.go
2f23d4d22d3eebcd182fb20a06a10da895dff9fc
[onechat] / tree_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/stretchr/testify/require"
10 )
11
12 func TestFreeLeafHoldsAShortMessageWordForWord(t *testing.T) {
13 message := Message{
14 ID: 7,
15 Kind: "user",
16 Text: strings.Repeat("a", nodeBytes-len("user: ")),
17 }
18
19 node, free := freeLeaf(message)
20 require.True(t, free)
21 require.Equal(t, Node{
22 Level: 0,
23 Index: 7,
24 Text: "user: " + message.Text,
25 Size: nodeBytes,
26 }, node)
27
28 message.Text += "a"
29 _, free = freeLeaf(message)
30 require.False(t, free)
31 }
32
33 func TestFreeMergeJoinsShortSiblings(t *testing.T) {
34 left := treeTestNode(1, 2, "left")
35 right := treeTestNode(1, 3, "right")
36
37 node, free := freeMerge(left, right)
38 require.True(t, free)
39 require.Equal(t, treeTestNode(2, 1, "left\nright"), node)
40
41 right = treeTestNode(1, 3, strings.Repeat("r", nodeBytes-len("left")))
42 _, free = freeMerge(left, right)
43 require.False(t, free)
44 }
45
46 func TestTreeBuildsEachNodeOnce(t *testing.T) {
47 tree, err := openTree(t.Context(), t.TempDir(), 2, newTestClock().Now)
48 require.NoError(t, err)
49
50 defer func() {
51 require.NoError(t, tree.Close())
52 }()
53
54 require.NoError(t, tree.Add(treeTestNode(0, 0, "user: a")))
55 require.ErrorContains(t, tree.Add(treeTestNode(0, 0, "user: b")), "already built")
56 require.ErrorContains(t, tree.Add(treeTestNode(1, 0, "a b")), "child that is not built")
57
58 require.NoError(t, tree.Add(treeTestNode(0, 1, "reply: b")))
59 require.NoError(t, tree.Add(treeTestNode(1, 0, "a b")))
60
61 require.Equal(t, "user: a", treeReadNode(t, tree, 0, 0).Text)
62 }
63
64 func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
65 root := t.TempDir()
66 clock := newTestClock()
67 tree, err := openTree(t.Context(), root, 2, clock.Now)
68 require.NoError(t, err)
69
70 nodes := []Node{
71 treeTestNode(0, 1, "reply: b"),
72 treeTestNode(0, 0, "user: a"),
73 treeTestNode(1, 0, "user: a\nreply: b"),
74 }
75
76 for _, node := range nodes {
77 require.NoError(t, tree.Add(node))
78 }
79
80 require.NoError(t, tree.Close())
81
82 data, err := os.ReadFile(filepath.Join(root, "tree", "2026-01-01.jsonl"))
83 require.NoError(t, err)
84 require.JSONEq(t, `{"l":0,"i":1,"text":"reply: b","size":8}`+"\n", strings.SplitAfter(string(data), "\n")[0])
85
86 tree, err = openTree(t.Context(), root, 2, clock.Now)
87 require.NoError(t, err)
88
89 defer func() {
90 require.NoError(t, tree.Close())
91 }()
92
93 require.Equal(t, len(nodes), tree.Count())
94
95 for _, node := range nodes {
96 require.Equal(t, node, treeReadNode(t, tree, node.Level, node.Index))
97 }
98 }
99
100 func TestTreeBuildsAMissingIndexAgain(t *testing.T) {
101 root := t.TempDir()
102 clock := newTestClock()
103 tree, err := openTree(t.Context(), root, 2, clock.Now)
104 require.NoError(t, err)
105
106 nodes := []Node{
107 treeTestNode(0, 1, "reply: b"),
108 treeTestNode(0, 0, "user: a"),
109 treeTestNode(1, 0, "user: a\nreply: b"),
110 }
111
112 for _, node := range nodes {
113 require.NoError(t, tree.Add(node))
114 }
115
116 require.NoError(t, tree.Close())
117 require.NoError(t, os.RemoveAll(filepath.Join(root, "index")))
118
119 _, err = openTree(t.Context(), root, 1, clock.Now)
120 require.ErrorContains(t, err, "covers messages after the log")
121 require.NoError(t, os.RemoveAll(filepath.Join(root, "index")))
122
123 tree, err = openTree(t.Context(), root, 2, clock.Now)
124 require.NoError(t, err)
125
126 defer func() {
127 require.NoError(t, tree.Close())
128 }()
129
130 require.Equal(t, len(nodes), tree.Count())
131
132 for _, node := range nodes {
133 require.Equal(t, node, treeReadNode(t, tree, node.Level, node.Index))
134 }
135 }
136
137 func TestTreeIndexesTheNodesAfterACrash(t *testing.T) {
138 root := t.TempDir()
139 clock := newTestClock()
140 tree, err := openTree(t.Context(), root, 2, clock.Now)
141 require.NoError(t, err)
142
143 require.NoError(t, tree.Add(treeTestNode(0, 0, "user: a")))
144 require.NoError(t, tree.Add(treeTestNode(0, 1, "reply: b")))
145 require.NoError(t, tree.Close())
146
147 // A crash after the line write and in the middle of the index writes.
148 order := filepath.Join(root, "index", "tree.idx")
149 require.NoError(t, os.Truncate(order, treeRecordBytes+5))
150 require.NoError(t, os.Truncate(filepath.Join(root, "index", "tree-0.idx"), positionBytes))
151
152 tree, err = openTree(t.Context(), root, 2, clock.Now)
153 require.NoError(t, err)
154
155 defer func() {
156 require.NoError(t, tree.Close())
157 }()
158
159 require.Equal(t, 2, tree.Count())
160 require.Equal(t, "reply: b", treeReadNode(t, tree, 0, 1).Text)
161 require.NoError(t, tree.Add(treeTestNode(1, 0, "user: a\nreply: b")))
162 }