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  }