Add config structs with sensible defaults for tunable parameters: - JetStreamConfig for stream retention (1 year) and replica count (1) - HashRingConfig for virtual nodes per physical node (150) - ShardConfig for shard count (1024) and replication factor (1) Each component gets a new WithConfig constructor that accepts custom configuration, while the original constructors continue to work with defaults. Zero values in configs fall back to defaults for backward compatibility. Closes #38 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
126 lines
3.5 KiB
Go
126 lines
3.5 KiB
Go
package cluster
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestDefaultHashRingConfig(t *testing.T) {
|
|
config := DefaultHashRingConfig()
|
|
|
|
if config.VirtualNodes != DefaultVirtualNodes {
|
|
t.Errorf("expected VirtualNodes=%d, got %d", DefaultVirtualNodes, config.VirtualNodes)
|
|
}
|
|
}
|
|
|
|
func TestDefaultShardConfig(t *testing.T) {
|
|
config := DefaultShardConfig()
|
|
|
|
if config.ShardCount != DefaultNumShards {
|
|
t.Errorf("expected ShardCount=%d, got %d", DefaultNumShards, config.ShardCount)
|
|
}
|
|
if config.ReplicationFactor != 1 {
|
|
t.Errorf("expected ReplicationFactor=1, got %d", config.ReplicationFactor)
|
|
}
|
|
}
|
|
|
|
func TestNewConsistentHashRingWithConfig(t *testing.T) {
|
|
t.Run("custom virtual nodes", func(t *testing.T) {
|
|
config := HashRingConfig{VirtualNodes: 50}
|
|
ring := NewConsistentHashRingWithConfig(config)
|
|
|
|
ring.AddNode("test-node")
|
|
|
|
if len(ring.sortedHashes) != 50 {
|
|
t.Errorf("expected 50 virtual nodes, got %d", len(ring.sortedHashes))
|
|
}
|
|
if ring.GetVirtualNodes() != 50 {
|
|
t.Errorf("expected GetVirtualNodes()=50, got %d", ring.GetVirtualNodes())
|
|
}
|
|
})
|
|
|
|
t.Run("zero value uses default", func(t *testing.T) {
|
|
config := HashRingConfig{VirtualNodes: 0}
|
|
ring := NewConsistentHashRingWithConfig(config)
|
|
|
|
ring.AddNode("test-node")
|
|
|
|
if len(ring.sortedHashes) != DefaultVirtualNodes {
|
|
t.Errorf("expected %d virtual nodes, got %d", DefaultVirtualNodes, len(ring.sortedHashes))
|
|
}
|
|
})
|
|
|
|
t.Run("default constructor uses default config", func(t *testing.T) {
|
|
ring := NewConsistentHashRing()
|
|
|
|
ring.AddNode("test-node")
|
|
|
|
if len(ring.sortedHashes) != DefaultVirtualNodes {
|
|
t.Errorf("expected %d virtual nodes, got %d", DefaultVirtualNodes, len(ring.sortedHashes))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewShardManagerWithConfig(t *testing.T) {
|
|
t.Run("custom shard count", func(t *testing.T) {
|
|
config := ShardConfig{ShardCount: 256, ReplicationFactor: 2}
|
|
sm := NewShardManagerWithConfig(config)
|
|
|
|
if sm.GetShardCount() != 256 {
|
|
t.Errorf("expected shard count 256, got %d", sm.GetShardCount())
|
|
}
|
|
if sm.GetReplicationFactor() != 2 {
|
|
t.Errorf("expected replication factor 2, got %d", sm.GetReplicationFactor())
|
|
}
|
|
})
|
|
|
|
t.Run("zero values use defaults", func(t *testing.T) {
|
|
config := ShardConfig{ShardCount: 0, ReplicationFactor: 0}
|
|
sm := NewShardManagerWithConfig(config)
|
|
|
|
if sm.GetShardCount() != DefaultNumShards {
|
|
t.Errorf("expected shard count %d, got %d", DefaultNumShards, sm.GetShardCount())
|
|
}
|
|
if sm.GetReplicationFactor() != 1 {
|
|
t.Errorf("expected replication factor 1, got %d", sm.GetReplicationFactor())
|
|
}
|
|
})
|
|
|
|
t.Run("legacy constructor still works", func(t *testing.T) {
|
|
sm := NewShardManager(512, 3)
|
|
|
|
if sm.GetShardCount() != 512 {
|
|
t.Errorf("expected shard count 512, got %d", sm.GetShardCount())
|
|
}
|
|
if sm.GetReplicationFactor() != 3 {
|
|
t.Errorf("expected replication factor 3, got %d", sm.GetReplicationFactor())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestShardManagerGetShard_DifferentShardCounts(t *testing.T) {
|
|
testCases := []struct {
|
|
shardCount int
|
|
}{
|
|
{shardCount: 16},
|
|
{shardCount: 64},
|
|
{shardCount: 256},
|
|
{shardCount: 1024},
|
|
{shardCount: 4096},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run("shardCount="+string(rune(tc.shardCount)), func(t *testing.T) {
|
|
sm := NewShardManagerWithConfig(ShardConfig{ShardCount: tc.shardCount})
|
|
|
|
// Verify all actor IDs map to valid shard range
|
|
for i := 0; i < 1000; i++ {
|
|
actorID := "actor-" + string(rune(i))
|
|
shard := sm.GetShard(actorID)
|
|
if shard < 0 || shard >= tc.shardCount {
|
|
t.Errorf("shard %d out of range [0, %d)", shard, tc.shardCount)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|