kevo/pkg/common/iterator/bounded/bounded_test.go
Jeremy Tregunna 6fc3be617d
Some checks failed
Go Tests / Run Tests (1.24.2) (push) Has been cancelled
feat: Initial release of kevo storage engine.
Adds a complete LSM-based storage engine with these features:
- Single-writer based architecture for the storage engine
- WAL for durability, and hey it's configurable
- MemTable with skip list implementation for fast read/writes
- SSTable with block-based structure for on-disk level-based storage
- Background compaction with tiered strategy
- ACID transactions
- Good documentation (I hope)
2025-04-20 14:06:50 -06:00

303 lines
6.8 KiB
Go

package bounded
import (
"testing"
)
// mockIterator is a simple in-memory iterator for testing
type mockIterator struct {
data map[string]string
keys []string
index int
}
func newMockIterator(data map[string]string) *mockIterator {
keys := make([]string, 0, len(data))
for k := range data {
keys = append(keys, k)
}
// Sort keys
for i := 0; i < len(keys)-1; i++ {
for j := i + 1; j < len(keys); j++ {
if keys[i] > keys[j] {
keys[i], keys[j] = keys[j], keys[i]
}
}
}
return &mockIterator{
data: data,
keys: keys,
index: -1,
}
}
func (m *mockIterator) SeekToFirst() {
if len(m.keys) > 0 {
m.index = 0
} else {
m.index = -1
}
}
func (m *mockIterator) SeekToLast() {
if len(m.keys) > 0 {
m.index = len(m.keys) - 1
} else {
m.index = -1
}
}
func (m *mockIterator) Seek(target []byte) bool {
targetStr := string(target)
for i, key := range m.keys {
if key >= targetStr {
m.index = i
return true
}
}
m.index = -1
return false
}
func (m *mockIterator) Next() bool {
if m.index >= 0 && m.index < len(m.keys)-1 {
m.index++
return true
}
m.index = -1
return false
}
func (m *mockIterator) Key() []byte {
if m.index >= 0 && m.index < len(m.keys) {
return []byte(m.keys[m.index])
}
return nil
}
func (m *mockIterator) Value() []byte {
if m.index >= 0 && m.index < len(m.keys) {
key := m.keys[m.index]
return []byte(m.data[key])
}
return nil
}
func (m *mockIterator) Valid() bool {
return m.index >= 0 && m.index < len(m.keys)
}
func (m *mockIterator) IsTombstone() bool {
return false
}
func TestBoundedIterator_NoBounds(t *testing.T) {
// Create a mock iterator with some data
mockIter := newMockIterator(map[string]string{
"a": "1",
"b": "2",
"c": "3",
"d": "4",
"e": "5",
})
// Create bounded iterator with no bounds
boundedIter := NewBoundedIterator(mockIter, nil, nil)
// Test SeekToFirst
boundedIter.SeekToFirst()
if !boundedIter.Valid() {
t.Fatal("Expected iterator to be valid after SeekToFirst")
}
// Should be at "a"
if string(boundedIter.Key()) != "a" {
t.Errorf("Expected key 'a', got '%s'", string(boundedIter.Key()))
}
// Test iterating through all keys
expected := []string{"a", "b", "c", "d", "e"}
for i, exp := range expected {
if !boundedIter.Valid() {
t.Fatalf("Iterator should be valid at position %d", i)
}
if string(boundedIter.Key()) != exp {
t.Errorf("Position %d: Expected key '%s', got '%s'", i, exp, string(boundedIter.Key()))
}
if i < len(expected)-1 {
if !boundedIter.Next() {
t.Fatalf("Next() should return true at position %d", i)
}
}
}
// After all elements, Next should return false
if boundedIter.Next() {
t.Error("Expected Next() to return false after all elements")
}
// Test SeekToLast
boundedIter.SeekToLast()
if !boundedIter.Valid() {
t.Fatal("Expected iterator to be valid after SeekToLast")
}
// Should be at "e"
if string(boundedIter.Key()) != "e" {
t.Errorf("Expected key 'e', got '%s'", string(boundedIter.Key()))
}
}
func TestBoundedIterator_WithBounds(t *testing.T) {
// Create a mock iterator with some data
mockIter := newMockIterator(map[string]string{
"a": "1",
"b": "2",
"c": "3",
"d": "4",
"e": "5",
})
// Create bounded iterator with bounds b to d (inclusive b, exclusive d)
boundedIter := NewBoundedIterator(mockIter, []byte("b"), []byte("d"))
// Test SeekToFirst
boundedIter.SeekToFirst()
if !boundedIter.Valid() {
t.Fatal("Expected iterator to be valid after SeekToFirst")
}
// Should be at "b" (start of range)
if string(boundedIter.Key()) != "b" {
t.Errorf("Expected key 'b', got '%s'", string(boundedIter.Key()))
}
// Test iterating through the range
expected := []string{"b", "c"}
for i, exp := range expected {
if !boundedIter.Valid() {
t.Fatalf("Iterator should be valid at position %d", i)
}
if string(boundedIter.Key()) != exp {
t.Errorf("Position %d: Expected key '%s', got '%s'", i, exp, string(boundedIter.Key()))
}
if i < len(expected)-1 {
if !boundedIter.Next() {
t.Fatalf("Next() should return true at position %d", i)
}
}
}
// After last element in range, Next should return false
if boundedIter.Next() {
t.Error("Expected Next() to return false after last element in range")
}
// Test SeekToLast
boundedIter.SeekToLast()
if !boundedIter.Valid() {
t.Fatal("Expected iterator to be valid after SeekToLast")
}
// Should be at "c" (last element in range)
if string(boundedIter.Key()) != "c" {
t.Errorf("Expected key 'c', got '%s'", string(boundedIter.Key()))
}
}
func TestBoundedIterator_Seek(t *testing.T) {
// Create a mock iterator with some data
mockIter := newMockIterator(map[string]string{
"a": "1",
"b": "2",
"c": "3",
"d": "4",
"e": "5",
})
// Create bounded iterator with bounds b to d (inclusive b, exclusive d)
boundedIter := NewBoundedIterator(mockIter, []byte("b"), []byte("d"))
// Test seeking within bounds
tests := []struct {
target string
expectValid bool
expectKey string
}{
{"a", true, "b"}, // Before range, should go to start bound
{"b", true, "b"}, // At range start
{"bc", true, "c"}, // Between b and c
{"c", true, "c"}, // Within range
{"d", false, ""}, // At range end (exclusive)
{"e", false, ""}, // After range
}
for i, test := range tests {
found := boundedIter.Seek([]byte(test.target))
if found != test.expectValid {
t.Errorf("Test %d: Seek(%s) returned %v, expected %v",
i, test.target, found, test.expectValid)
}
if test.expectValid {
if string(boundedIter.Key()) != test.expectKey {
t.Errorf("Test %d: Seek(%s) key is '%s', expected '%s'",
i, test.target, string(boundedIter.Key()), test.expectKey)
}
}
}
}
func TestBoundedIterator_SetBounds(t *testing.T) {
// Create a mock iterator with some data
mockIter := newMockIterator(map[string]string{
"a": "1",
"b": "2",
"c": "3",
"d": "4",
"e": "5",
})
// Create bounded iterator with no initial bounds
boundedIter := NewBoundedIterator(mockIter, nil, nil)
// Position at 'c'
boundedIter.Seek([]byte("c"))
// Set bounds that include 'c'
boundedIter.SetBounds([]byte("b"), []byte("e"))
// Iterator should still be valid at 'c'
if !boundedIter.Valid() {
t.Fatal("Iterator should remain valid after setting bounds that include current position")
}
if string(boundedIter.Key()) != "c" {
t.Errorf("Expected key to remain 'c', got '%s'", string(boundedIter.Key()))
}
// Set bounds that exclude 'c'
boundedIter.SetBounds([]byte("d"), []byte("f"))
// Iterator should no longer be valid
if boundedIter.Valid() {
t.Fatal("Iterator should be invalid after setting bounds that exclude current position")
}
// SeekToFirst should position at 'd'
boundedIter.SeekToFirst()
if !boundedIter.Valid() {
t.Fatal("Iterator should be valid after SeekToFirst")
}
if string(boundedIter.Key()) != "d" {
t.Errorf("Expected key 'd', got '%s'", string(boundedIter.Key()))
}
}