package erasure_coding import ( "sync" "testing" "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" ) func TestShardsInfo_SetAndGet(t *testing.T) { si := NewShardsInfo() // Test setting shards si.Set(ShardInfo{Id: 0, Size: 1000}) si.Set(ShardInfo{Id: 5, Size: 2000}) si.Set(ShardInfo{Id: 13, Size: 3000}) // Verify Has if !si.Has(0) { t.Error("Expected shard 0 to exist") } if !si.Has(5) { t.Error("Expected shard 5 to exist") } if !si.Has(13) { t.Error("Expected shard 13 to exist") } if si.Has(1) { t.Error("Expected shard 1 to not exist") } // Verify Size if got := si.Size(0); got != 1000 { t.Errorf("Expected size 1000, got %d", got) } if got := si.Size(5); got != 2000 { t.Errorf("Expected size 2000, got %d", got) } if got := si.Size(13); got != 3000 { t.Errorf("Expected size 3000, got %d", got) } // Verify Count if got := si.Count(); got != 3 { t.Errorf("Expected count 3, got %d", got) } // Verify Bitmap expectedBitmap := uint32((1 << 0) | (1 << 5) | (1 << 13)) if got := si.Bitmap(); got != expectedBitmap { t.Errorf("Expected bitmap %b, got %b", expectedBitmap, got) } } func TestShardsInfo_SortedOrder(t *testing.T) { si := NewShardsInfo() // Add shards in non-sequential order si.Set(ShardInfo{Id: 10, Size: 1000}) si.Set(ShardInfo{Id: 2, Size: 2000}) si.Set(ShardInfo{Id: 7, Size: 3000}) si.Set(ShardInfo{Id: 0, Size: 4000}) // Verify Ids returns sorted order ids := si.Ids() expected := []ShardId{0, 2, 7, 10} if len(ids) != len(expected) { t.Fatalf("Expected %d ids, got %d", len(expected), len(ids)) } for i, id := range ids { if id != expected[i] { t.Errorf("Expected id[%d]=%d, got %d", i, expected[i], id) } } } func TestShardsInfo_Delete(t *testing.T) { si := NewShardsInfo() si.Set(ShardInfo{Id: 0, Size: 1000}) si.Set(ShardInfo{Id: 5, Size: 2000}) si.Set(ShardInfo{Id: 10, Size: 3000}) // Delete middle shard si.Delete(5) if si.Has(5) { t.Error("Expected shard 5 to be deleted") } if !si.Has(0) || !si.Has(10) { t.Error("Expected other shards to remain") } if got := si.Count(); got != 2 { t.Errorf("Expected count 2, got %d", got) } // Verify slice is still sorted ids := si.Ids() if len(ids) != 2 || ids[0] != 0 || ids[1] != 10 { t.Errorf("Expected ids [0, 10], got %v", ids) } } func TestShardsInfo_Update(t *testing.T) { si := NewShardsInfo() si.Set(ShardInfo{Id: 5, Size: 1000}) // Update existing shard si.Set(ShardInfo{Id: 5, Size: 2000}) if got := si.Size(5); got != 2000 { t.Errorf("Expected updated size 2000, got %d", got) } if got := si.Count(); got != 1 { t.Errorf("Expected count to remain 1, got %d", got) } } func TestShardsInfo_TotalSize(t *testing.T) { si := NewShardsInfo() si.Set(ShardInfo{Id: 0, Size: 1000}) si.Set(ShardInfo{Id: 5, Size: 2000}) si.Set(ShardInfo{Id: 10, Size: 3000}) expected := ShardSize(6000) if got := si.TotalSize(); got != expected { t.Errorf("Expected total size %d, got %d", expected, got) } } func TestShardsInfo_Sizes(t *testing.T) { si := NewShardsInfo() si.Set(ShardInfo{Id: 2, Size: 100}) si.Set(ShardInfo{Id: 5, Size: 200}) si.Set(ShardInfo{Id: 8, Size: 300}) sizes := si.Sizes() expected := []ShardSize{100, 200, 300} if len(sizes) != len(expected) { t.Fatalf("Expected %d sizes, got %d", len(expected), len(sizes)) } for i, size := range sizes { if size != expected[i] { t.Errorf("Expected size[%d]=%d, got %d", i, expected[i], size) } } } func TestShardsInfo_Copy(t *testing.T) { si := NewShardsInfo() si.Set(ShardInfo{Id: 0, Size: 1000}) si.Set(ShardInfo{Id: 5, Size: 2000}) siCopy := si.Copy() // Verify copy has same data if !siCopy.Has(0) || !siCopy.Has(5) { t.Error("Copy should have same shards") } if siCopy.Size(0) != 1000 || siCopy.Size(5) != 2000 { t.Error("Copy should have same sizes") } // Modify original si.Set(ShardInfo{Id: 10, Size: 3000}) // Verify copy is independent if siCopy.Has(10) { t.Error("Copy should be independent of original") } } func TestShardsInfo_AddSubtract(t *testing.T) { si1 := NewShardsInfo() si1.Set(ShardInfo{Id: 0, Size: 1000}) si1.Set(ShardInfo{Id: 2, Size: 2000}) si2 := NewShardsInfo() si2.Set(ShardInfo{Id: 2, Size: 9999}) // Different size si2.Set(ShardInfo{Id: 5, Size: 3000}) // Test Add si1.Add(si2) if !si1.Has(0) || !si1.Has(2) || !si1.Has(5) { t.Error("Add should merge shards") } if si1.Size(2) != 9999 { t.Error("Add should update existing shard size") } // Test Subtract si1.Subtract(si2) if si1.Has(2) || si1.Has(5) { t.Error("Subtract should remove shards") } if !si1.Has(0) { t.Error("Subtract should keep non-matching shards") } } func TestShardsInfo_PlusMinus(t *testing.T) { si1 := NewShardsInfo() si1.Set(ShardInfo{Id: 0, Size: 1000}) si1.Set(ShardInfo{Id: 2, Size: 2000}) si2 := NewShardsInfo() si2.Set(ShardInfo{Id: 2, Size: 2000}) si2.Set(ShardInfo{Id: 5, Size: 3000}) // Test Plus result := si1.Plus(si2) if !result.Has(0) || !result.Has(2) || !result.Has(5) { t.Error("Plus should merge into new instance") } if si1.Has(5) { t.Error("Plus should not modify original") } // Test Minus result = si1.Minus(si2) if !result.Has(0) || result.Has(2) { t.Error("Minus should subtract into new instance") } if !si1.Has(2) { t.Error("Minus should not modify original") } } func TestShardsInfo_DeleteParityShards(t *testing.T) { si := NewShardsInfo() // Add data shards (0-9) for i := 0; i < DataShardsCount; i++ { si.Set(ShardInfo{Id: ShardId(i), Size: ShardSize((i + 1) * 1000)}) } // Add parity shards (10-13) for i := DataShardsCount; i < TotalShardsCount; i++ { si.Set(ShardInfo{Id: ShardId(i), Size: ShardSize((i + 1) * 1000)}) } si.DeleteParityShards() // Verify only data shards remain for i := 0; i < DataShardsCount; i++ { if !si.Has(ShardId(i)) { t.Errorf("Expected data shard %d to remain", i) } } for i := DataShardsCount; i < TotalShardsCount; i++ { if si.Has(ShardId(i)) { t.Errorf("Expected parity shard %d to be deleted", i) } } } func TestShardsInfo_FromVolumeEcShardInformationMessage(t *testing.T) { tests := []struct { name string msg *master_pb.VolumeEcShardInformationMessage wantBits uint32 wantSizes []int64 }{ { name: "nil message", msg: nil, wantBits: 0, wantSizes: []int64{}, }, { name: "single shard", msg: &master_pb.VolumeEcShardInformationMessage{ EcIndexBits: 1 << 5, ShardSizes: []int64{12345}, }, wantBits: 1 << 5, wantSizes: []int64{12345}, }, { name: "multiple shards", msg: &master_pb.VolumeEcShardInformationMessage{ EcIndexBits: (1 << 0) | (1 << 3) | (1 << 7), ShardSizes: []int64{1000, 2000, 3000}, }, wantBits: (1 << 0) | (1 << 3) | (1 << 7), wantSizes: []int64{1000, 2000, 3000}, }, { name: "missing sizes", msg: &master_pb.VolumeEcShardInformationMessage{ EcIndexBits: (1 << 0) | (1 << 3), ShardSizes: []int64{1000}, }, wantBits: (1 << 0) | (1 << 3), wantSizes: []int64{1000, 0}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { si := ShardsInfoFromVolumeEcShardInformationMessage(tt.msg) if got := si.Bitmap(); got != tt.wantBits { t.Errorf("Bitmap() = %b, want %b", got, tt.wantBits) } if got := si.SizesInt64(); len(got) != len(tt.wantSizes) { t.Errorf("SizesInt64() length = %d, want %d", len(got), len(tt.wantSizes)) } else { for i, size := range got { if size != tt.wantSizes[i] { t.Errorf("SizesInt64()[%d] = %d, want %d", i, size, tt.wantSizes[i]) } } } }) } } func TestShardsInfo_String(t *testing.T) { si := NewShardsInfo() si.Set(ShardInfo{Id: 0, Size: 1024}) si.Set(ShardInfo{Id: 5, Size: 2048}) str := si.String() if str == "" { t.Error("String() should not be empty") } // Basic validation - should contain shard IDs if len(str) < 3 { t.Errorf("String() too short: %s", str) } } func BenchmarkShardsInfo_Set(b *testing.B) { si := NewShardsInfo() b.ResetTimer() for i := 0; i < b.N; i++ { si.Set(ShardInfo{Id: ShardId(i % TotalShardsCount), Size: ShardSize(i * 1000)}) } } func BenchmarkShardsInfo_Has(b *testing.B) { si := NewShardsInfo() for i := 0; i < TotalShardsCount; i++ { si.Set(ShardInfo{Id: ShardId(i), Size: ShardSize(i * 1000)}) } b.ResetTimer() for i := 0; i < b.N; i++ { si.Has(ShardId(i % TotalShardsCount)) } } func BenchmarkShardsInfo_Size(b *testing.B) { si := NewShardsInfo() for i := 0; i < TotalShardsCount; i++ { si.Set(ShardInfo{Id: ShardId(i), Size: ShardSize(i * 1000)}) } b.ResetTimer() for i := 0; i < b.N; i++ { si.Size(ShardId(i % TotalShardsCount)) } } func TestShardsInfo_ConcurrentAccess(t *testing.T) { si := NewShardsInfo() var wg sync.WaitGroup wg.Add(3) // Goroutine 1: Continuously Set/Delete shards go func() { defer wg.Done() for i := 0; i < 1000; i++ { si.Set(ShardInfo{Id: ShardId(i % TotalShardsCount), Size: 100}) if i%10 == 0 { si.Delete(ShardId((i / 10) % TotalShardsCount)) } } }() // Goroutine 2: Continuously read Info (Sizes, Bitmap, Count) go func() { defer wg.Done() for i := 0; i < 1000; i++ { si.Sizes() si.Bitmap() si.Count() si.TotalSize() } }() // Goroutine 3: Continuously Add/Subtract from another ShardsInfo go func() { defer wg.Done() other := NewShardsInfo() other.Set(ShardInfo{Id: 1, Size: 100}) other.Set(ShardInfo{Id: 2, Size: 200}) for i := 0; i < 1000; i++ { si.Add(other) si.Subtract(other) } }() wg.Wait() }