package activator import ( "context" "errors" "testing" "time" ) func TestFenceInitialAdmissionTimeoutHasNoFence(t *testing.T) { t.Parallel() a := &Service{} a.mu.Lock() ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) proof, err := a.FenceAndStop(ctx, "timeout.example.com", 1) cancel() if proof != nil || !errors.Is(err, ErrFenceNotInstalled) { a.mu.Unlock() t.Fatalf("uninstalled fence reported as installed: proof=%v err=%v", proof != nil, err) } if a.fences["timeout.example.com"] != 0 { a.mu.Unlock() t.Fatal("fence installed while mutex held") } a.mu.Unlock() if !a.wakeAllowed("timeout.example.com") { t.Fatal("unexpected admission state") } } func TestScheduledTokenSlowHostDoesNotBlockSiblingFence(t *testing.T) { a := &Service{} entered, release := make(chan struct{}), make(chan struct{}) done := make(chan error, 1) go func() { _, e := a.PrepareScheduledToken("slow.example.com", func() (string, error) { close(entered); <-release; return "token", nil }) done <- e }() <-entered defer func() { close(release); <-done }() ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() proof, err := a.FenceAndStop(ctx, "sibling.example.com", 1) if proof != nil || !errors.Is(err, ErrStopUnproven) { t.Fatalf("sibling fence blocked by unrelated IO: %v", err) } callback := make(chan error, 1) go func() { _, e := a.PrepareScheduledToken("fast.example.com", func() (string, error) { return "fast", nil }) callback <- e }() select { case e := <-callback: if e != nil { t.Fatal(e) } case <-time.After(time.Second): t.Fatal("sibling token callback blocked") } // A separate goroutine detects the former global mutex hold without hanging the test. result := make(chan bool, 1) go func() { result <- a.wakeAllowed("third.example.com") }() select { case allowed := <-result: if !allowed { t.Fatal("unrelated admission refused") } case <-time.After(time.Second): t.Fatal("unrelated admission blocked") } }