Some checks failed
Go Tests / Run Tests (1.24.2) (push) Has been cancelled
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)
173 lines
4.3 KiB
Go
173 lines
4.3 KiB
Go
package sstable
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestReaderBasics(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)
|
|
}
|
|
|
|
// 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
|
|
indexValue := indexIter.Value()
|
|
locator, err := ParseBlockLocator(indexIter.Key(), indexValue)
|
|
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 TestReaderCorruption(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")
|
|
}
|
|
}
|