All checks were successful
Go Tests / Run Tests (1.24.2) (pull_request) Successful in 9m37s
251 lines
6.4 KiB
Go
251 lines
6.4 KiB
Go
package engine
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/jeremytregunna/kevo/pkg/common/clock"
|
|
"github.com/jeremytregunna/kevo/pkg/common/log"
|
|
"github.com/jeremytregunna/kevo/pkg/wal"
|
|
)
|
|
|
|
// TestReplicationHooks tests that the replication hooks are properly called
|
|
func TestReplicationHooks(t *testing.T) {
|
|
// Set log level to avoid noise in tests
|
|
log.SetLevel(log.LevelError)
|
|
|
|
// Create a temporary directory for testing
|
|
tempDir, err := os.MkdirTemp("", "replication-test")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temporary directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create a new engine
|
|
engine, err := NewEngine(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create engine: %v", err)
|
|
}
|
|
|
|
// Make sure replication manager was created
|
|
if engine.replicationMgr == nil {
|
|
t.Fatal("Replication manager was not created")
|
|
}
|
|
|
|
// Verify NodeID was assigned
|
|
if engine.nodeID == (clock.NodeID{}) {
|
|
t.Fatal("NodeID was not assigned")
|
|
}
|
|
|
|
// Verify Lamport clock was created
|
|
if engine.lamportClock == nil {
|
|
t.Fatal("Lamport clock was not created")
|
|
}
|
|
|
|
// Test adding and removing replicas
|
|
replicaID := "test-replica"
|
|
fakeNodeID := clock.NodeID{}
|
|
engine.replicationMgr.AddReplica(replicaID, fakeNodeID)
|
|
|
|
replicas := engine.replicationMgr.GetReplicaIDs()
|
|
if len(replicas) != 1 || replicas[0] != replicaID {
|
|
t.Fatalf("Expected replica ID %s, got %v", replicaID, replicas)
|
|
}
|
|
|
|
engine.replicationMgr.RemoveReplica(replicaID)
|
|
|
|
replicas = engine.replicationMgr.GetReplicaIDs()
|
|
if len(replicas) != 0 {
|
|
t.Fatalf("Expected no replicas, got %v", replicas)
|
|
}
|
|
|
|
// Test setting leader/replica status
|
|
if engine.replicationMgr.IsLeader() {
|
|
t.Fatal("Replication manager should not be leader by default")
|
|
}
|
|
|
|
engine.replicationMgr.SetLeader(true)
|
|
|
|
if !engine.replicationMgr.IsLeader() {
|
|
t.Fatal("Replication manager should be leader")
|
|
}
|
|
|
|
if engine.replicationMgr.IsReplica() {
|
|
t.Fatal("Replication manager should not be replica when it's a leader")
|
|
}
|
|
|
|
engine.replicationMgr.SetLeader(false)
|
|
|
|
if !engine.replicationMgr.IsReplica() {
|
|
t.Fatal("Replication manager should be replica when it's not a leader")
|
|
}
|
|
|
|
// Close the engine
|
|
if err := engine.Close(); err != nil {
|
|
t.Fatalf("Failed to close engine: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestReplicationIntegration tests that the engine works with replication hooks
|
|
func TestReplicationIntegration(t *testing.T) {
|
|
// Set log level to avoid noise in tests
|
|
log.SetLevel(log.LevelError)
|
|
|
|
// Create a temporary directory for testing
|
|
tempDir, err := os.MkdirTemp("", "replication-integration-test")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temporary directory: %v", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Create a new engine
|
|
engine, err := NewEngine(tempDir)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create engine: %v", err)
|
|
}
|
|
|
|
// Verify the replication manager is initialized
|
|
if engine.replicationMgr == nil {
|
|
t.Fatal("Replication manager was not initialized")
|
|
}
|
|
|
|
// Set as leader to ensure replication hooks are called
|
|
engine.replicationMgr.SetLeader(true)
|
|
|
|
// Perform some operations
|
|
testKey := []byte("test-key")
|
|
testValue := []byte("test-value")
|
|
|
|
// Put operation
|
|
if err := engine.Put(testKey, testValue); err != nil {
|
|
t.Fatalf("Failed to put: %v", err)
|
|
}
|
|
|
|
// Get operation
|
|
value, err := engine.Get(testKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get: %v", err)
|
|
}
|
|
|
|
// Verify value
|
|
if string(value) != string(testValue) {
|
|
t.Fatalf("Expected value %q, got %q", testValue, value)
|
|
}
|
|
|
|
// Close the engine
|
|
if err := engine.Close(); err != nil {
|
|
t.Fatalf("Failed to close engine: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestReplicationInterface verifies that the replication hook interface works correctly
|
|
func TestReplicationInterface(t *testing.T) {
|
|
// Set log level to avoid noise in tests
|
|
log.SetLevel(log.LevelError)
|
|
|
|
// Create a test hook
|
|
callbackCounts := struct {
|
|
singleEntries int
|
|
batchEntries int
|
|
}{}
|
|
|
|
testHook := &syncTestReplicationHook{
|
|
onEntryCallback: func(entry *wal.Entry, ts clock.Timestamp) error {
|
|
callbackCounts.singleEntries++
|
|
return nil
|
|
},
|
|
onBatchCallback: func(entries []*wal.Entry, ts clock.Timestamp) error {
|
|
callbackCounts.batchEntries += len(entries)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// Create a test entry
|
|
entry := &wal.Entry{
|
|
SequenceNumber: 1,
|
|
Type: wal.OpTypePut,
|
|
Key: []byte("key1"),
|
|
Value: []byte("value1"),
|
|
}
|
|
|
|
// Create a test batch
|
|
batch := []*wal.Entry{
|
|
{
|
|
SequenceNumber: 2,
|
|
Type: wal.OpTypePut,
|
|
Key: []byte("key2"),
|
|
Value: []byte("value2"),
|
|
},
|
|
{
|
|
SequenceNumber: 3,
|
|
Type: wal.OpTypePut,
|
|
Key: []byte("key3"),
|
|
Value: []byte("value3"),
|
|
},
|
|
}
|
|
|
|
// Create a timestamp
|
|
nodeID := clock.NodeID{}
|
|
ts := clock.Timestamp{
|
|
Counter: 1,
|
|
Node: nodeID,
|
|
}
|
|
|
|
// Call the hook methods directly
|
|
if err := testHook.OnEntryWritten(entry, ts); err != nil {
|
|
t.Fatalf("OnEntryWritten failed: %v", err)
|
|
}
|
|
|
|
if err := testHook.OnBatchWritten(batch, ts); err != nil {
|
|
t.Fatalf("OnBatchWritten failed: %v", err)
|
|
}
|
|
|
|
// Verify callbacks were called
|
|
if callbackCounts.singleEntries != 1 {
|
|
t.Errorf("Expected 1 single entry callback, got %d", callbackCounts.singleEntries)
|
|
}
|
|
|
|
if callbackCounts.batchEntries != 2 {
|
|
t.Errorf("Expected 2 batch entry callbacks, got %d", callbackCounts.batchEntries)
|
|
}
|
|
}
|
|
|
|
// Test helper: a synchronous mock replication hook for testing
|
|
type syncTestReplicationHook struct {
|
|
onEntryCallback func(*wal.Entry, clock.Timestamp) error
|
|
onBatchCallback func([]*wal.Entry, clock.Timestamp) error
|
|
}
|
|
|
|
func (h *syncTestReplicationHook) OnEntryWritten(entry *wal.Entry, ts clock.Timestamp) error {
|
|
if h.onEntryCallback != nil {
|
|
return h.onEntryCallback(entry, ts)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *syncTestReplicationHook) OnBatchWritten(entries []*wal.Entry, ts clock.Timestamp) error {
|
|
if h.onBatchCallback != nil {
|
|
return h.onBatchCallback(entries, ts)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Test helper: an asynchronous mock replication hook for testing
|
|
type testReplicationHook struct {
|
|
onEntryCallback func(*wal.Entry, clock.Timestamp) error
|
|
onBatchCallback func([]*wal.Entry, clock.Timestamp) error
|
|
}
|
|
|
|
func (h *testReplicationHook) OnEntryWritten(entry *wal.Entry, ts clock.Timestamp) error {
|
|
if h.onEntryCallback != nil {
|
|
return h.onEntryCallback(entry, ts)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *testReplicationHook) OnBatchWritten(entries []*wal.Entry, ts clock.Timestamp) error {
|
|
if h.onBatchCallback != nil {
|
|
return h.onBatchCallback(entries, ts)
|
|
}
|
|
return nil
|
|
} |