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) {
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 {