Files
seaweedfs/sw-block/engine/replication/runtime/pending_test.go
T
pingqiuandClaude Opus 4.6 b304b8e212 refactor: make bounded recovery command addressing replica-scoped
Replace the remaining volume-scoped recovery command and pending slot
with replica-scoped addressing on the bounded core-present path. This
preserves the current single-replica catch-up and rebuilding behavior
while removing the structural blocker for later multi-replica startup
ownership.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:05:36 -07:00

171 lines
4.4 KiB
Go

package runtime
import "testing"
func TestPendingCoordinator_TakeCatchUp_MatchSucceeds(t *testing.T) {
pc := NewPendingCoordinator(nil)
pc.Store("vol1", &PendingExecution{
VolumeID: "vol1",
CatchUpTarget: 100,
})
pe := pc.TakeCatchUp("vol1", 100)
if pe == nil {
t.Fatal("matching take should succeed")
}
if pe.CatchUpTarget != 100 {
t.Fatalf("target=%d", pe.CatchUpTarget)
}
// Second take should return nil (already consumed).
if pc.TakeCatchUp("vol1", 100) != nil {
t.Fatal("second take should return nil")
}
}
func TestPendingCoordinator_TakeCatchUp_MismatchCancels(t *testing.T) {
var cancelledReason string
pc := NewPendingCoordinator(func(pe *PendingExecution, reason string) {
cancelledReason = reason
})
pc.Store("vol1", &PendingExecution{
VolumeID: "vol1",
CatchUpTarget: 100,
})
pe := pc.TakeCatchUp("vol1", 999) // wrong target
if pe != nil {
t.Fatal("mismatched take should return nil")
}
if cancelledReason != "start_catchup_target_mismatch" {
t.Fatalf("cancel reason=%q", cancelledReason)
}
// Pending should be consumed (removed even on mismatch).
if pc.Has("vol1") {
t.Fatal("pending should be removed after mismatch")
}
}
func TestPendingCoordinator_TakeRebuild_MatchSucceeds(t *testing.T) {
pc := NewPendingCoordinator(nil)
pc.Store("vol1", &PendingExecution{
VolumeID: "vol1",
RebuildTargetLSN: 50,
})
pe := pc.TakeRebuild("vol1", 50)
if pe == nil {
t.Fatal("matching rebuild take should succeed")
}
}
func TestPendingCoordinator_TakeRebuild_MismatchCancels(t *testing.T) {
var cancelledReason string
pc := NewPendingCoordinator(func(pe *PendingExecution, reason string) {
cancelledReason = reason
})
pc.Store("vol1", &PendingExecution{
VolumeID: "vol1",
RebuildTargetLSN: 50,
})
pe := pc.TakeRebuild("vol1", 999)
if pe != nil {
t.Fatal("mismatched rebuild take should return nil")
}
if cancelledReason != "start_rebuild_target_mismatch" {
t.Fatalf("cancel reason=%q", cancelledReason)
}
}
func TestPendingCoordinator_Cancel_ExplicitRemoval(t *testing.T) {
var cancelledReason string
pc := NewPendingCoordinator(func(pe *PendingExecution, reason string) {
cancelledReason = reason
})
pc.Store("vol1", &PendingExecution{VolumeID: "vol1"})
pc.Cancel("vol1", "superseded")
if cancelledReason != "superseded" {
t.Fatalf("cancel reason=%q", cancelledReason)
}
if pc.Has("vol1") {
t.Fatal("should be removed after cancel")
}
}
func TestPendingCoordinator_Cancel_NoopWhenEmpty(t *testing.T) {
cancelled := false
pc := NewPendingCoordinator(func(pe *PendingExecution, reason string) {
cancelled = true
})
pc.Cancel("vol1", "noop")
if cancelled {
t.Fatal("cancel on empty should not invoke callback")
}
}
func TestPendingCoordinator_Has_And_Peek(t *testing.T) {
pc := NewPendingCoordinator(nil)
if pc.Has("vol1") {
t.Fatal("empty coordinator should not have vol1")
}
if pc.Peek("vol1") != nil {
t.Fatal("peek on empty should return nil")
}
pc.Store("vol1", &PendingExecution{VolumeID: "vol1", CatchUpTarget: 42})
if !pc.Has("vol1") {
t.Fatal("should have vol1 after store")
}
pe := pc.Peek("vol1")
if pe == nil || pe.CatchUpTarget != 42 {
t.Fatal("peek should return stored execution")
}
// Peek does not consume.
if !pc.Has("vol1") {
t.Fatal("peek should not remove")
}
}
func TestPendingCoordinator_StoreReplaces(t *testing.T) {
pc := NewPendingCoordinator(nil)
pc.Store("vol1", &PendingExecution{VolumeID: "vol1", CatchUpTarget: 10})
pc.Store("vol1", &PendingExecution{VolumeID: "vol1", CatchUpTarget: 20})
pe := pc.TakeCatchUp("vol1", 20)
if pe == nil {
t.Fatal("replaced store should be latest")
}
}
func TestPendingCoordinator_ReplicaScopedKeysDoNotCollide(t *testing.T) {
pc := NewPendingCoordinator(nil)
pc.Store("vol1/r1", &PendingExecution{VolumeID: "vol1", ReplicaID: "vol1/r1", CatchUpTarget: 10})
pc.Store("vol1/r2", &PendingExecution{VolumeID: "vol1", ReplicaID: "vol1/r2", CatchUpTarget: 20})
pe1 := pc.TakeCatchUp("vol1/r1", 10)
if pe1 == nil || pe1.ReplicaID != "vol1/r1" {
t.Fatalf("pe1=%+v", pe1)
}
pe2 := pc.TakeCatchUp("vol1/r2", 20)
if pe2 == nil || pe2.ReplicaID != "vol1/r2" {
t.Fatalf("pe2=%+v", pe2)
}
}
func TestPendingCoordinator_TakeFromEmpty_ReturnsNil(t *testing.T) {
pc := NewPendingCoordinator(nil)
if pc.TakeCatchUp("vol1", 100) != nil {
t.Fatal("take from empty should return nil")
}
if pc.TakeRebuild("vol1", 100) != nil {
t.Fatal("take rebuild from empty should return nil")
}
}