github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/wal/wal_test.go (about) 1 package wal 2 3 import ( 4 "bytes" 5 "path/filepath" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/gnolang/gno/tm2/pkg/amino" 13 auto "github.com/gnolang/gno/tm2/pkg/autofile" 14 tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time" 15 "github.com/gnolang/gno/tm2/pkg/log" 16 "github.com/gnolang/gno/tm2/pkg/random" 17 ) 18 19 const ( 20 walTestFlushInterval = time.Duration(100) * time.Millisecond 21 ) 22 23 type TestMessage struct { 24 Duration time.Duration 25 Height int64 26 Round int64 27 Data []byte 28 } 29 30 func (TestMessage) AssertWALMessage() {} 31 32 var _ = amino.RegisterPackage(amino.NewPackage( 33 "github.com/gnolang/gno/tm2/pkg/bft/wal", 34 "wal", 35 amino.GetCallersDirname(), 36 ). 37 WithTypes( 38 TestMessage{}, 39 )) 40 41 func TestWALWriterReader(t *testing.T) { 42 t.Parallel() 43 44 now := tmtime.Now() 45 msgs := []TimedWALMessage{ 46 {Time: now, Msg: TestMessage{Duration: time.Second, Height: 1, Round: 1}}, 47 {Time: now, Msg: TestMessage{Duration: time.Second, Height: 1, Round: 2}}, 48 } 49 50 b := new(bytes.Buffer) 51 52 for _, msg := range msgs { 53 msg := msg 54 55 b.Reset() 56 57 enc := NewWALWriter(b, maxTestMsgSize) 58 err := enc.Write(msg) 59 require.NoError(t, err) 60 61 dec := NewWALReader(b, maxTestMsgSize) 62 decoded, meta, err := dec.ReadMessage() 63 require.NoError(t, err) 64 require.Nil(t, meta) 65 66 assert.Equal(t, msg.Time.UTC(), decoded.Time) 67 assert.Equal(t, msg.Msg, decoded.Msg) 68 } 69 } 70 71 const maxTestMsgSize int64 = 64 * 1024 72 73 func makeTempWAL(t *testing.T, maxMsgSize int64, walChunkSize int64) (wal *baseWAL) { 74 t.Helper() 75 76 // Create WAL file. 77 walFile := filepath.Join(t.TempDir(), "wal") 78 79 // Create WAL. 80 wal, err := NewWAL(walFile, maxTestMsgSize, auto.GroupHeadSizeLimit(walChunkSize)) 81 if err != nil { 82 panic(err) 83 } 84 err = wal.Start() 85 if err != nil { 86 panic(err) 87 } 88 89 t.Cleanup(func() { 90 // WAL cleanup. 91 wal.Stop() 92 // wait for the wal to finish shutting down so we 93 // can safely remove the directory 94 wal.Wait() 95 }) 96 97 return wal 98 } 99 100 func TestWALWrite(t *testing.T) { 101 t.Parallel() 102 103 // Create WAL 104 const walChunkSize = 100000 105 wal := makeTempWAL(t, maxTestMsgSize, walChunkSize) 106 107 // 1) Write returns an error if msg is too big 108 msg := TestMessage{ 109 Data: random.RandBytes(int(maxTestMsgSize)), 110 } 111 err := wal.Write(msg) 112 if assert.Error(t, err) { 113 assert.Contains(t, err.Error(), "msg is too big") 114 } 115 116 // 2) Write returns no error if msg is not too big. 117 overhead := 1024 // sufficiently large. 118 msg = TestMessage{ 119 Data: random.RandBytes(int(maxTestMsgSize) - overhead), 120 } 121 err = wal.Write(msg) 122 assert.NoError(t, err) 123 } 124 125 func TestWALSearchForHeight(t *testing.T) { 126 t.Parallel() 127 128 // Create WAL 129 const numHeight, numRounds, dataSize = 100, 10000, 10 130 const walChunkSize = 100000 131 if numHeight*numRounds*dataSize < walChunkSize*3 { 132 panic("invalid walChunkSize, it should be an order of magnitude or more smaller than the product") 133 } 134 wal := makeTempWAL(t, maxTestMsgSize, walChunkSize) 135 136 // Generate WAL messages. 137 for h := 1; h < numHeight; h++ { 138 err := wal.WriteMetaSync(MetaMessage{Height: int64(h)}) 139 assert.NoError(t, err) 140 for r := 1; r < numRounds; r++ { 141 err := wal.Write(TestMessage{Height: int64(h), Round: int64(r), Data: random.RandBytes(dataSize)}) 142 assert.NoError(t, err) 143 } 144 } 145 wal.FlushAndSync() 146 147 // Search for height. 148 for h := 1; h < numHeight; h++ { 149 // Search for h. 150 gr, found, err := wal.SearchForHeight(int64(h), nil) 151 assert.NoError(t, err, "expected not to err on height %d", h) 152 assert.True(t, found, "expected to find end height for %d", h) 153 assert.NotNil(t, gr) 154 155 // Read next message. 156 dec := NewWALReader(gr, maxTestMsgSize) 157 msg, meta, err := dec.ReadMessage() 158 assert.NoError(t, err, "expected to decode a message") 159 assert.Nil(t, meta, "expected no meta") 160 rs, ok := msg.Msg.(TestMessage) 161 assert.True(t, ok, "expected message of type TestMessage") 162 assert.Equal(t, rs.Height, int64(h), "wrong height") 163 164 // Cleanup 165 dec.Close() 166 } 167 } 168 169 func TestWALPeriodicSync(t *testing.T) { 170 t.Parallel() 171 172 // Create WAL 173 const numHeight, numRounds, dataSize = 100, 10000, 10 174 const walChunkSize = 100000 175 const sleepInterval = 5 * time.Second // NOTE: no longer needed. 176 if numHeight*numRounds*dataSize < walChunkSize { 177 panic("invalid walChunkSize, it should be an order of magnitude or more smaller than the product") 178 } 179 wal := makeTempWAL(t, maxTestMsgSize, walChunkSize) 180 181 // Is this needed? 182 wal.SetFlushInterval(walTestFlushInterval) 183 wal.SetLogger(log.NewNoopLogger()) 184 185 // Take snapshot of starting state. 186 startInfo := wal.Group().ReadGroupInfo() 187 assert.True(t, startInfo.TotalSize < 1024, "WAL should start short (w/ initial Height 1 meta)") 188 189 // Generate WAL messages. 190 for h := 1; h < numHeight; h++ { 191 err := wal.WriteMetaSync(MetaMessage{Height: int64(h)}) 192 assert.NoError(t, err) 193 for r := 1; r < numRounds; r++ { 194 err := wal.Write(TestMessage{Height: int64(h), Round: int64(r), Data: random.RandBytes(dataSize)}) 195 assert.NoError(t, err) 196 } 197 } 198 // NOTE: but this isn't guaranteed so don't test like below: 199 // assert.NotZero(t, wal.Group().Buffered()) 200 wal.FlushAndSync() 201 202 // Sleep for a while, while WAL files are being created. 203 time.Sleep(sleepInterval) 204 205 // Take snapshot of ending state. 206 endInfo := wal.Group().ReadGroupInfo() 207 assert.NotEqual(t, 0, endInfo.TotalSize, "WAL should end not empty") 208 209 // The data should have been flushed by the periodic sync 210 assert.Zero(t, wal.Group().Buffered()) 211 212 // Try searching for the last height. 213 h := int64(numHeight - 1) 214 gr, found, err := wal.SearchForHeight(h, nil) 215 assert.NoError(t, err, "expected not to err on height %d", h) 216 assert.True(t, found, "expected to find end height for %d", h) 217 assert.NotNil(t, gr) 218 if gr != nil { 219 gr.Close() 220 } 221 } 222 223 /* 224 var initOnce sync.Once 225 226 func registerInterfacesOnce() { 227 initOnce.Do(func() { 228 var _ = wire.RegisterInterface( 229 struct{ WALMessage }{}, 230 wire.ConcreteType{[]byte{}, 0x10}, 231 ) 232 }) 233 } 234 */ 235 236 func benchmarkWalRead(b *testing.B, n int) { 237 // registerInterfacesOnce() 238 b.Helper() 239 240 buf := new(bytes.Buffer) 241 enc := NewWALWriter(buf, int64(n)+64) // n + overhead. 242 243 msg := TestMessage{ 244 Height: 1, 245 Round: 1, 246 Data: random.RandBytes(n), 247 } 248 enc.Write(TimedWALMessage{Msg: msg, Time: time.Now().Round(time.Second).UTC()}) 249 250 encoded := buf.Bytes() 251 252 b.ResetTimer() 253 for i := 0; i < b.N; i++ { 254 buf.Reset() 255 buf.Write(encoded) 256 dec := NewWALReader(buf, maxTestMsgSize) 257 if _, _, err := dec.ReadMessage(); err != nil { 258 b.Fatal(err) 259 } 260 } 261 b.ReportAllocs() 262 } 263 264 func BenchmarkWalRead512B(b *testing.B) { 265 benchmarkWalRead(b, 512) 266 } 267 268 func BenchmarkWalRead10KB(b *testing.B) { 269 benchmarkWalRead(b, 10*1024) 270 } 271 272 func BenchmarkWalRead100KB(b *testing.B) { 273 b.Skip("TODO: benchmark failing") 274 benchmarkWalRead(b, 100*1024) 275 } 276 277 func BenchmarkWalRead1MB(b *testing.B) { 278 b.Skip("TODO: benchmark failing") 279 benchmarkWalRead(b, 1024*1024) 280 } 281 282 func BenchmarkWalRead10MB(b *testing.B) { 283 b.Skip("TODO: benchmark failing") 284 benchmarkWalRead(b, 10*1024*1024) 285 } 286 287 func BenchmarkWalRead100MB(b *testing.B) { 288 b.Skip("TODO: benchmark failing") 289 benchmarkWalRead(b, 100*1024*1024) 290 } 291 292 func BenchmarkWalRead1GB(b *testing.B) { 293 b.Skip("TODO: benchmark failing") 294 benchmarkWalRead(b, 1024*1024*1024) 295 }