store_paths_test.mx raw
1 // The store paths the happy-path engine tests cannot reach: every failure
2 // return in Open (a broken bit of the directory layout) and the stale
3 // checkpoint recovery, which rebuilds the indexes from the WAL.
4 package store
5
6 import (
7 "os"
8 "testing"
9
10 "git.smesh.lol/smesh/pkg/nostr/filter"
11 "git.smesh.lol/smesh/pkg/store/checkpoint"
12 )
13
14 // engIndexNames is the index file set Open creates, in Open's order.
15 var engIndexNames = []string{
16 "eid", "fpc", "sei", "ca", "exp", "kc", "pc", "kpc",
17 "tc", "tkc", "tpc", "tkp", "wrd", "pks", "spk",
18 "epg", "peg", "eeg", "gee", "ppg", "gpp",
19 }
20
21 func TestOpenFailsOnABrokenIndexFile(t *testing.T) {
22 for i := 0; i < len(engIndexNames); i++ {
23 dir, ok := engTmp(t)
24 if !ok {
25 return
26 }
27 // A directory where the index file belongs makes sorted.Open fail, so
28 // Open must report that index's error and not hand back an engine.
29 if err := os.MkdirAll(dir|"/idx/"|engIndexNames[i]|".dat", 0755); err != nil {
30 t.Fatal(err)
31 }
32 if g, oerr := Open(dir); oerr == nil {
33 g.Close()
34 t.Fatalf("Open must fail when idx/%s.dat is a directory", engIndexNames[i])
35 }
36 os.RemoveAll(dir)
37 }
38 }
39
40 func TestOpenFailsOnABrokenWAL(t *testing.T) {
41 // A regular file where the wal directory belongs: wal.Open's MkdirAll and
42 // then ReadDir/OpenFile must fail.
43 dir, ok := engTmp(t)
44 if !ok {
45 return
46 }
47 if err := os.WriteFile(dir|"/wal", []byte("not a directory"), 0644); err != nil {
48 t.Fatal(err)
49 }
50 if g, oerr := Open(dir); oerr == nil {
51 g.Close()
52 t.Fatal("Open must fail when the wal path is a file")
53 }
54 os.RemoveAll(dir)
55
56 // A segment that cannot be opened is reported too.
57 dir2, ok2 := engTmp(t)
58 if !ok2 {
59 return
60 }
61 if err := os.MkdirAll(dir2|"/wal/seg-0000000000000000.dat", 0755); err != nil {
62 t.Fatal(err)
63 }
64 if g, oerr := Open(dir2); oerr == nil {
65 g.Close()
66 t.Fatal("Open must fail when a wal segment cannot be opened")
67 }
68 os.RemoveAll(dir2)
69 }
70
71 func TestOpenFailsOnABrokenCheckpoint(t *testing.T) {
72 dir, ok := engTmp(t)
73 if !ok {
74 return
75 }
76 if err := os.MkdirAll(dir|"/checkpoint.dat", 0755); err != nil {
77 t.Fatal(err)
78 }
79 if g, oerr := Open(dir); oerr == nil {
80 g.Close()
81 t.Fatal("Open must fail when checkpoint.dat cannot be read")
82 }
83 os.RemoveAll(dir)
84 }
85
86 func TestOpenFailsWhenTheDirectoryCannotBeCreated(t *testing.T) {
87 dir, ok := engTmp(t)
88 if !ok {
89 return
90 }
91 // A path through a regular file cannot become a directory.
92 file := dir | "/file"
93 if err := os.WriteFile(file, []byte("x"), 0644); err != nil {
94 t.Fatal(err)
95 }
96 if g, oerr := Open(file | "/sub"); oerr == nil {
97 g.Close()
98 t.Fatal("Open must fail when the store directory cannot be created")
99 }
100 os.RemoveAll(dir)
101 }
102
103 func TestOpenRecoversFromAStaleCheckpoint(t *testing.T) {
104 g, dir, ok := engOpenTmp(t)
105 if !ok {
106 return
107 }
108 ev := engEvent(0x31, 0x32, 1, 1700000000, engTag("t", "stale"), "stale checkpoint")
109 if !engSaveAll(t, g, []*event.E{ev}) {
110 g.Close()
111 os.RemoveAll(dir)
112 return
113 }
114 if cerr := g.Close(); cerr != nil {
115 t.Fatal(cerr)
116 }
117
118 // A checkpoint offset beyond the segment's size is stale: Open must clear
119 // the indexes and rebuild them from the WAL instead of failing.
120 ck, cok := checkpoint.Open(dir | "/checkpoint.dat")
121 if cok != nil {
122 t.Fatal(cok)
123 }
124 if serr := ck.Set(1 << 20); serr != nil {
125 t.Fatal(serr)
126 }
127
128 g2, ok2 := engOpen(t, dir)
129 if !ok2 {
130 return
131 }
132 defer g2.Close()
133 res, qok := engQuery(t, g2, filter.New())
134 if !qok {
135 return
136 }
137 if len(res) != 1 {
138 t.Fatalf("the rebuilt store returned %d events, want the one in the WAL", int32(len(res)))
139 }
140 if string(res[0].Content) != "stale checkpoint" {
141 t.Fatalf("rebuilt event content = %s", res[0].Content)
142 }
143 os.RemoveAll(dir)
144 }
145