kevo/pkg/sstable/sstable_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

182 lines
4.5 KiB
Go

package sstable
import (
"fmt"
"os"
"path/filepath"
"testing"
)
func TestBasics(t *testing.T) {
// Create a temporary directory for the test
tempDir := t.TempDir()
sstablePath := filepath.Join(tempDir, "test.sst")
// Create a new SSTable writer
writer, err := NewWriter(sstablePath)
if err != nil {
t.Fatalf("Failed to create SSTable writer: %v", err)
}
// Add some key-value pairs
numEntries := 100
keyValues := make(map[string]string, numEntries)
for i := 0; i < numEntries; i++ {
key := fmt.Sprintf("key%05d", i)
value := fmt.Sprintf("value%05d", i)
keyValues[key] = value
err := writer.Add([]byte(key), []byte(value))
if err != nil {
t.Fatalf("Failed to add entry: %v", err)
}
}
// Finish writing
err = writer.Finish()
if err != nil {
t.Fatalf("Failed to finish SSTable: %v", err)
}
// Check that the file exists and has some data
info, err := os.Stat(sstablePath)
if err != nil {
t.Fatalf("Failed to stat file: %v", err)
}
if info.Size() == 0 {
t.Errorf("File is empty")
}
// Open the SSTable for reading
reader, err := OpenReader(sstablePath)
if err != nil {
t.Fatalf("Failed to open SSTable: %v", err)
}
defer reader.Close()
// Verify the number of entries
if reader.numEntries != uint32(numEntries) {
t.Errorf("Expected %d entries, got %d", numEntries, reader.numEntries)
}
// Print file information
t.Logf("SSTable file size: %d bytes", reader.ioManager.GetFileSize())
t.Logf("Index offset: %d", reader.indexOffset)
t.Logf("Index size: %d", reader.indexSize)
t.Logf("Entries in table: %d", reader.numEntries)
// Check what's in the index
indexIter := reader.indexBlock.Iterator()
t.Log("Index entries:")
count := 0
for indexIter.SeekToFirst(); indexIter.Valid(); indexIter.Next() {
if count < 10 { // Log the first 10 entries only
locator, err := ParseBlockLocator(indexIter.Key(), indexIter.Value())
if err != nil {
t.Errorf("Failed to parse block locator: %v", err)
continue
}
t.Logf(" Index key: %s, block offset: %d, block size: %d",
string(locator.Key), locator.Offset, locator.Size)
// Read the block and see what keys it contains
blockReader, err := reader.blockFetcher.FetchBlock(locator.Offset, locator.Size)
if err == nil {
blockIter := blockReader.Iterator()
t.Log(" Block contents:")
keysInBlock := 0
for blockIter.SeekToFirst(); blockIter.Valid() && keysInBlock < 10; blockIter.Next() {
t.Logf(" Key: %s, Value: %s",
string(blockIter.Key()), string(blockIter.Value()))
keysInBlock++
}
if keysInBlock >= 10 {
t.Logf(" ... and more keys")
}
}
}
count++
}
t.Logf("Total index entries: %d", count)
// Read some keys
for i := 0; i < numEntries; i += 10 {
key := fmt.Sprintf("key%05d", i)
expectedValue := keyValues[key]
value, err := reader.Get([]byte(key))
if err != nil {
t.Errorf("Failed to get key %s: %v", key, err)
continue
}
if string(value) != expectedValue {
t.Errorf("Value mismatch for key %s: expected %s, got %s",
key, expectedValue, value)
}
}
// Try to read a non-existent key
_, err = reader.Get([]byte("nonexistent"))
if err != ErrNotFound {
t.Errorf("Expected ErrNotFound for non-existent key, got: %v", err)
}
}
func TestCorruption(t *testing.T) {
// Create a temporary directory for the test
tempDir := t.TempDir()
sstablePath := filepath.Join(tempDir, "test.sst")
// Create a new SSTable writer
writer, err := NewWriter(sstablePath)
if err != nil {
t.Fatalf("Failed to create SSTable writer: %v", err)
}
// Add some key-value pairs
for i := 0; i < 100; i++ {
key := []byte(fmt.Sprintf("key%05d", i))
value := []byte(fmt.Sprintf("value%05d", i))
err := writer.Add(key, value)
if err != nil {
t.Fatalf("Failed to add entry: %v", err)
}
}
// Finish writing
err = writer.Finish()
if err != nil {
t.Fatalf("Failed to finish SSTable: %v", err)
}
// Corrupt the file
file, err := os.OpenFile(sstablePath, os.O_RDWR, 0)
if err != nil {
t.Fatalf("Failed to open file for corruption: %v", err)
}
// Write some garbage at the end to corrupt the footer
_, err = file.Seek(-8, os.SEEK_END)
if err != nil {
t.Fatalf("Failed to seek: %v", err)
}
_, err = file.Write([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF})
if err != nil {
t.Fatalf("Failed to write garbage: %v", err)
}
file.Close()
// Try to open the corrupted file
_, err = OpenReader(sstablePath)
if err == nil {
t.Errorf("Expected error when opening corrupted file, but got none")
}
}