]> git.giacomolli.uk - onechat/blobdiff - tree_test.go
Write only ASCII characters
[onechat] / tree_test.go
index 3188f10e36da7a5fae8955635c39f35b0a8989cc..ece7c6fbc17e7a4d7e18aa0e320cd705484155c1 100644 (file)
@@ -55,12 +55,10 @@ func TestTreeBuildsEachNodeOnce(t *testing.T) {
        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(testNode(0, 1, "unii: b")))
+       require.NoError(t, tree.Add(testNode(0, 1, "reply: b")))
        require.NoError(t, tree.Add(testNode(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", readNode(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, 1, "reply: b"),
                testNode(0, 0, "user: a"),
-               testNode(1, 0, "user: a\nunii: b"),
+               testNode(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.Equal(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,15 +89,93 @@ 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, readNode(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{
+               testNode(0, 1, "reply: b"),
+               testNode(0, 0, "user: a"),
+               testNode(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())
+       for _, node := range nodes {
+               require.Equal(t, node, readNode(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(testNode(0, 0, "user: a")))
+       require.NoError(t, tree.Add(testNode(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", readNode(t, tree, 0, 1).Text)
+       require.NoError(t, tree.Add(testNode(1, 0, "user: a\nreply: b")))
+}
+
+// isBuilt tells if the node (l, i) of tree is built.
+func isBuilt(t *testing.T, tree *Tree, level, index int) bool {
+       t.Helper()
+
+       built, err := tree.Built(level, index)
+       require.NoError(t, err)
+
+       return built
+}
+
+// readNode reads the node (l, i) of tree, which must be built.
+func readNode(t *testing.T, tree *Tree, level, index int) Node {
+       t.Helper()
+
+       node, built, err := tree.Node(level, index)
+       require.NoError(t, err)
+       require.True(t, built, "node (%d, %d)", level, index)
+
+       return node
 }
 
 func testNode(level, index int, text string) Node {