]> git.giacomolli.uk - onechat/blobdiff - tree_test.go
Format short daemon events with slog
[onechat] / tree_test.go
index 3188f10e36da7a5fae8955635c39f35b0a8989cc..d927a9eda6bbafec3fe257504af28cecfe1aafd2 100644 (file)
@@ -31,14 +31,14 @@ func TestFreeLeafHoldsAShortMessageWordForWord(t *testing.T) {
 }
 
 func TestFreeMergeJoinsShortSiblings(t *testing.T) {
-       left := testNode(1, 2, "left")
-       right := testNode(1, 3, "right")
+       left := treeTestNode(1, 2, "left")
+       right := treeTestNode(1, 3, "right")
 
        node, free := freeMerge(left, right)
        require.True(t, free)
-       require.Equal(t, testNode(2, 1, "left\nright"), node)
+       require.Equal(t, treeTestNode(2, 1, "left\nright"), node)
 
-       right = testNode(1, 3, strings.Repeat("r", nodeBytes-len("left")))
+       right = treeTestNode(1, 3, strings.Repeat("r", nodeBytes-len("left")))
        _, free = freeMerge(left, right)
        require.False(t, free)
 }
@@ -51,16 +51,14 @@ func TestTreeBuildsEachNodeOnce(t *testing.T) {
                require.NoError(t, tree.Close())
        }()
 
-       require.NoError(t, tree.Add(testNode(0, 0, "user: a")))
-       require.ErrorContains(t, tree.Add(testNode(0, 0, "user: b")), "already built")
-       require.ErrorContains(t, tree.Add(testNode(1, 0, "a b")), "child that is not built")
+       require.NoError(t, tree.Add(treeTestNode(0, 0, "user: a")))
+       require.ErrorContains(t, tree.Add(treeTestNode(0, 0, "user: b")), "already built")
+       require.ErrorContains(t, tree.Add(treeTestNode(1, 0, "a b")), "child that is not built")
 
-       require.NoError(t, tree.Add(testNode(0, 1, "unii: b")))
-       require.NoError(t, tree.Add(testNode(1, 0, "a b")))
+       require.NoError(t, tree.Add(treeTestNode(0, 1, "reply: b")))
+       require.NoError(t, tree.Add(treeTestNode(1, 0, "a b")))
 
-       node, built := tree.Node(0, 0)
-       require.True(t, built)
-       require.Equal(t, "user: a", node.Text)
+       require.Equal(t, "user: a", treeReadNode(t, tree, 0, 0).Text)
 }
 
 func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
@@ -70,9 +68,9 @@ func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
        require.NoError(t, err)
 
        nodes := []Node{
-               testNode(0, 1, "unii: b"),
-               testNode(0, 0, "user: a"),
-               testNode(1, 0, "user: a\nunii: b"),
+               treeTestNode(0, 1, "reply: b"),
+               treeTestNode(0, 0, "user: a"),
+               treeTestNode(1, 0, "user: a\nreply: b"),
        }
        for _, node := range nodes {
                require.NoError(t, tree.Add(node))
@@ -82,7 +80,7 @@ func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
 
        data, err := os.ReadFile(filepath.Join(root, "tree", "2026-01-01.jsonl"))
        require.NoError(t, err)
-       require.Equal(t, `{"l":0,"i":1,"text":"unii: b","size":7}`+"\n", strings.SplitAfter(string(data), "\n")[0])
+       require.JSONEq(t, `{"l":0,"i":1,"text":"reply: b","size":8}`+"\n", strings.SplitAfter(string(data), "\n")[0])
 
        tree, err = openTree(t.Context(), root, 2, clock.Now)
        require.NoError(t, err)
@@ -91,22 +89,72 @@ func TestTreeReopenLoadsTheSameNodes(t *testing.T) {
                require.NoError(t, tree.Close())
        }()
 
-       require.Len(t, tree.nodes, len(nodes))
+       require.Equal(t, len(nodes), tree.Count())
+
        for _, node := range nodes {
-               loaded, built := tree.Node(node.Level, node.Index)
-               require.True(t, built)
-               require.Equal(t, node, loaded)
+               require.Equal(t, node, treeReadNode(t, tree, node.Level, node.Index))
        }
+}
+
+func TestTreeBuildsAMissingIndexAgain(t *testing.T) {
+       root := t.TempDir()
+       clock := newTestClock()
+       tree, err := openTree(t.Context(), root, 2, clock.Now)
+       require.NoError(t, err)
+
+       nodes := []Node{
+               treeTestNode(0, 1, "reply: b"),
+               treeTestNode(0, 0, "user: a"),
+               treeTestNode(1, 0, "user: a\nreply: b"),
+       }
+       for _, node := range nodes {
+               require.NoError(t, tree.Add(node))
+       }
+
+       require.NoError(t, tree.Close())
+       require.NoError(t, os.RemoveAll(filepath.Join(root, "index")))
 
        _, err = openTree(t.Context(), root, 1, clock.Now)
        require.ErrorContains(t, err, "covers messages after the log")
-}
+       require.NoError(t, os.RemoveAll(filepath.Join(root, "index")))
+
+       tree, err = openTree(t.Context(), root, 2, clock.Now)
+       require.NoError(t, err)
+
+       defer func() {
+               require.NoError(t, tree.Close())
+       }()
+
+       require.Equal(t, len(nodes), tree.Count())
 
-func testNode(level, index int, text string) Node {
-       return Node{
-               Level: level,
-               Index: index,
-               Text:  text,
-               Size:  len(text),
+       for _, node := range nodes {
+               require.Equal(t, node, treeReadNode(t, tree, node.Level, node.Index))
        }
 }
+
+func TestTreeIndexesTheNodesAfterACrash(t *testing.T) {
+       root := t.TempDir()
+       clock := newTestClock()
+       tree, err := openTree(t.Context(), root, 2, clock.Now)
+       require.NoError(t, err)
+
+       require.NoError(t, tree.Add(treeTestNode(0, 0, "user: a")))
+       require.NoError(t, tree.Add(treeTestNode(0, 1, "reply: b")))
+       require.NoError(t, tree.Close())
+
+       // A crash after the line write and in the middle of the index writes.
+       order := filepath.Join(root, "index", "tree.idx")
+       require.NoError(t, os.Truncate(order, treeRecordBytes+5))
+       require.NoError(t, os.Truncate(filepath.Join(root, "index", "tree-0.idx"), positionBytes))
+
+       tree, err = openTree(t.Context(), root, 2, clock.Now)
+       require.NoError(t, err)
+
+       defer func() {
+               require.NoError(t, tree.Close())
+       }()
+
+       require.Equal(t, 2, tree.Count())
+       require.Equal(t, "reply: b", treeReadNode(t, tree, 0, 1).Text)
+       require.NoError(t, tree.Add(treeTestNode(1, 0, "user: a\nreply: b")))
+}