Files
node/x/merkledb/view_iterator_test.go
zeekayandHanzo Dev b1cc108c9f gofmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:51:51 -07:00

310 lines
8.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build test
package merkledb
import (
"bytes"
"context"
"maps"
"math/rand"
"slices"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/container/maybe"
"github.com/luxfi/database"
)
func Test_View_Iterator(t *testing.T) {
require := require.New(t)
key1 := []byte("hello1")
value1 := []byte("world1")
key2 := []byte("hello2")
value2 := []byte("world2")
db, err := getBasicDB()
require.NoError(err)
require.NoError(db.Put(key1, value1))
require.NoError(db.Put(key2, value2))
view, err := db.NewView(context.Background(), ViewChanges{})
require.NoError(err)
iterator := view.NewIterator()
require.NotNil(iterator)
defer iterator.Release()
require.True(iterator.Next())
require.Equal(key1, iterator.Key())
require.Equal(value1, iterator.Value())
require.True(iterator.Next())
require.Equal(key2, iterator.Key())
require.Equal(value2, iterator.Value())
require.False(iterator.Next())
require.Nil(iterator.Key())
require.Nil(iterator.Value())
require.NoError(iterator.Error())
}
func Test_View_Iterator_DBClosed(t *testing.T) {
require := require.New(t)
key1 := []byte("hello1")
value1 := []byte("world1")
db, err := getBasicDB()
require.NoError(err)
require.NoError(db.Put(key1, value1))
view, err := db.NewView(context.Background(), ViewChanges{})
require.NoError(err)
iterator := view.NewIterator()
require.NotNil(iterator)
defer iterator.Release()
require.NoError(db.Close())
require.False(iterator.Next())
require.Nil(iterator.Key())
require.Nil(iterator.Value())
err = iterator.Error()
require.ErrorIs(err, ErrInvalid)
}
// Test_View_IteratorStart tests to make sure the iterator can be configured to
// start midway through the database.
func Test_View_IteratorStart(t *testing.T) {
require := require.New(t)
db, err := getBasicDB()
require.NoError(err)
key1 := []byte("hello1")
value1 := []byte("world1")
key2 := []byte("hello2")
value2 := []byte("world2")
require.NoError(db.Put(key1, value1))
require.NoError(db.Put(key2, value2))
view, err := db.NewView(context.Background(), ViewChanges{})
require.NoError(err)
iterator := view.NewIteratorWithStart(key2)
require.NotNil(iterator)
defer iterator.Release()
require.True(iterator.Next())
require.Equal(key2, iterator.Key())
require.Equal(value2, iterator.Value())
require.False(iterator.Next())
require.Nil(iterator.Key())
require.Nil(iterator.Value())
require.NoError(iterator.Error())
}
// Test_View_IteratorPrefix tests to make sure the iterator can be configured to skip
// keys missing the provided prefix.
func Test_View_IteratorPrefix(t *testing.T) {
require := require.New(t)
db, err := getBasicDB()
require.NoError(err)
key1 := []byte("hello")
value1 := []byte("world1")
key2 := []byte("goodbye")
value2 := []byte("world2")
key3 := []byte("joy")
value3 := []byte("world3")
require.NoError(db.Put(key1, value1))
require.NoError(db.Put(key2, value2))
require.NoError(db.Put(key3, value3))
view, err := db.NewView(context.Background(), ViewChanges{})
require.NoError(err)
iterator := view.NewIteratorWithPrefix([]byte("h"))
require.NotNil(iterator)
defer iterator.Release()
require.True(iterator.Next())
require.Equal(key1, iterator.Key())
require.Equal(value1, iterator.Value())
require.False(iterator.Next())
require.Nil(iterator.Key())
require.Nil(iterator.Value())
require.NoError(iterator.Error())
}
// Test_View_IteratorStartPrefix tests to make sure that the iterator can start
// midway through the database while skipping a prefix.
func Test_View_IteratorStartPrefix(t *testing.T) {
require := require.New(t)
db, err := getBasicDB()
require.NoError(err)
key1 := []byte("hello1")
value1 := []byte("world1")
key2 := []byte("z")
value2 := []byte("world2")
key3 := []byte("hello3")
value3 := []byte("world3")
require.NoError(db.Put(key1, value1))
require.NoError(db.Put(key2, value2))
require.NoError(db.Put(key3, value3))
view, err := db.NewView(context.Background(), ViewChanges{})
require.NoError(err)
iterator := view.NewIteratorWithStartAndPrefix(key1, []byte("h"))
require.NotNil(iterator)
defer iterator.Release()
require.True(iterator.Next())
require.Equal(key1, iterator.Key())
require.Equal(value1, iterator.Value())
require.True(iterator.Next())
require.Equal(key3, iterator.Key())
require.Equal(value3, iterator.Value())
require.False(iterator.Next())
require.Nil(iterator.Key())
require.Nil(iterator.Value())
require.NoError(iterator.Error())
}
// Test view iteration by creating a stack of views,
// inserting random key/value pairs into them, and
// iterating over the last view.
func Test_View_Iterator_Random(t *testing.T) {
require := require.New(t)
now := time.Now().UnixNano()
t.Logf("seed: %d", now)
rand := rand.New(rand.NewSource(now)) // #nosec G404
var (
numKeyChanges = 5_000
maxKeyLen = 16
maxValLen = 16
)
keyChanges := []KeyChange{}
for i := 0; i < numKeyChanges; i++ {
key := make([]byte, rand.Intn(maxKeyLen))
_, _ = rand.Read(key)
value := make([]byte, rand.Intn(maxValLen))
_, _ = rand.Read(value)
keyChanges = append(keyChanges, KeyChange{
Key: key,
Value: maybe.Some(value),
})
}
db, err := getBasicDB()
require.NoError(err)
for i := 0; i < numKeyChanges/4; i++ {
require.NoError(db.Put(keyChanges[i].Key, keyChanges[i].Value.Value()))
}
ops := make([]database.BatchOp, 0, numKeyChanges/4)
for i := numKeyChanges / 4; i < 2*numKeyChanges/4; i++ {
ops = append(ops, database.BatchOp{Key: keyChanges[i].Key, Value: keyChanges[i].Value.Value()})
}
view1, err := db.NewView(context.Background(), ViewChanges{BatchOps: ops})
require.NoError(err)
ops = make([]database.BatchOp, 0, numKeyChanges/4)
for i := 2 * numKeyChanges / 4; i < 3*numKeyChanges/4; i++ {
ops = append(ops, database.BatchOp{Key: keyChanges[i].Key, Value: keyChanges[i].Value.Value()})
}
view2, err := view1.NewView(context.Background(), ViewChanges{BatchOps: ops})
require.NoError(err)
ops = make([]database.BatchOp, 0, numKeyChanges/4)
for i := 3 * numKeyChanges / 4; i < numKeyChanges; i++ {
ops = append(ops, database.BatchOp{Key: keyChanges[i].Key, Value: keyChanges[i].Value.Value()})
}
view3, err := view2.NewView(context.Background(), ViewChanges{BatchOps: ops})
require.NoError(err)
// Might have introduced duplicates, so only expect the latest value.
uniqueKeyChanges := make(map[string][]byte)
for _, keyChange := range keyChanges {
uniqueKeyChanges[string(keyChange.Key)] = keyChange.Value.Value()
}
iter := view3.NewIterator()
uniqueKeys := slices.Collect(maps.Keys(uniqueKeyChanges))
slices.Sort(uniqueKeys)
i := 0
for iter.Next() {
expectedKey := uniqueKeys[i]
expectedValue := uniqueKeyChanges[expectedKey]
require.True(bytes.Equal([]byte(expectedKey), iter.Key()))
if len(expectedValue) == 0 {
// Don't differentiate between nil and []byte{}
require.Empty(iter.Value())
} else {
require.Equal(expectedValue, iter.Value())
}
i++
}
require.Len(uniqueKeys, i)
iter.Release()
require.NoError(iter.Error())
// Test with start and prefix.
prefix := []byte{128}
start := []byte{128, 5}
iter = view3.NewIteratorWithStartAndPrefix(start, prefix)
startPrefixUniqueKeys := []string{}
// Remove keys that don't have the prefix/are before the start.
for i := 0; i < len(uniqueKeys); i++ {
if bytes.HasPrefix([]byte(uniqueKeys[i]), prefix) && bytes.Compare([]byte(uniqueKeys[i]), start) >= 0 {
startPrefixUniqueKeys = append(startPrefixUniqueKeys, uniqueKeys[i])
}
}
require.NotEmpty(startPrefixUniqueKeys) // Sanity check to make sure we have some keys to test.
i = 0
for iter.Next() {
expectedKey := startPrefixUniqueKeys[i]
expectedValue := uniqueKeyChanges[expectedKey]
require.Equal([]byte(expectedKey), iter.Key())
if len(expectedValue) == 0 {
// Don't differentiate between nil and []byte{}
require.Empty(iter.Value())
} else {
require.Equal(expectedValue, iter.Value())
}
i++
}
require.Len(startPrefixUniqueKeys, i)
iter.Release()
require.NoError(iter.Error())
}