package schedule import ( "context" "errors" "testing" "time" ) // A nil gate is main's runner and converger: every host enters, and a drain // has nothing to wait for. func TestNilHostGateIsInert(t *testing.T) { var g *HostGate if !g.tryEnter("a") { t.Fatal("a nil gate refused a host") } g.leave("a") if err := g.Drain(context.Background(), "a"); err != nil { t.Fatalf("a nil gate's drain = %v", err) } } // The drain waits for the host's in-flight work, refuses new work while it // waits, and leaves the host open once it returns. Other hosts are never // held up. func TestHostGateDrainWaitsForInFlightWork(t *testing.T) { g := &HostGate{} if !g.tryEnter("a") { t.Fatal("first entry refused") } done := make(chan error, 1) go func() { done <- g.Drain(context.Background(), "a") }() select { case err := <-done: t.Fatalf("the drain returned (%v) while work on the host was in flight", err) case <-time.After(50 * time.Millisecond): } // A drain is waiting: new work on the host is refused, other hosts enter. deadline := time.Now().Add(time.Second) for g.tryEnter("a") { g.leave("a") if time.Now().After(deadline) { t.Fatal("work entered the host while a drain was waiting") } time.Sleep(time.Millisecond) } if !g.tryEnter("b") { t.Fatal("a drain on one host refused another host") } g.leave("b") g.leave("a") if err := <-done; err != nil { t.Fatalf("drain = %v", err) } if !g.tryEnter("a") { t.Fatal("the host stayed closed after the drain") } g.leave("a") } // A drain whose context ends returns the context's error; the host's work // is unaffected. func TestHostGateDrainHonoursItsContext(t *testing.T) { g := &HostGate{} if !g.tryEnter("a") { t.Fatal("entry refused") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond) defer cancel() if err := g.Drain(ctx, "a"); !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("drain = %v, want the deadline", err) } g.leave("a") }