From f26bcf669babd0ac10c3b1401933586f0bb8a39a Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Sun, 8 Nov 2026 01:27:24 +0100 Subject: [PATCH] test(collect): stop the single-flight test failing on slow runners The test raced 8 goroutines against a 2 ms sleep. On a slow macOS race runner a call could start after the first run had finished, a legitimate second run, and the test failed with 6 collects instead of 3. The fake collector now parks on a gate that the test closes only after the other 7 calls have returned, so exactly one run is open while they try. --- app_test.go | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/app_test.go b/app_test.go index 4affbc5..35e99bb 100644 --- a/app_test.go +++ b/app_test.go @@ -390,7 +390,9 @@ type fakeCollector struct { inFlight atomic.Int32 maxSeen atomic.Int32 - hold time.Duration + // gate, when set, parks every collect until it is closed, so a test can keep a + // run open for as long as it needs instead of guessing with a sleep. + gate chan struct{} } func newFakeCollector(supported, fail []string) *fakeCollector { @@ -423,8 +425,8 @@ func (f *fakeCollector) Collect(_ context.Context, slug string, _ map[string]str break } } - if f.hold > 0 { - time.Sleep(f.hold) + if f.gate != nil { + <-f.gate } f.inFlight.Add(-1) @@ -584,20 +586,42 @@ func TestCollectAllCollectsCredentialOnlyServicesAndDedups(t *testing.T) { // (Within that one run the services now collect CONCURRENTLY, bounded by // collectConcurrency, so max-concurrent-collects may exceed 1 — that is expected and is // a separate property, asserted only to be within the bound.) +// +// The run that wins is parked on the fake's gate until every other call has +// returned. A sleep used to stand in for that, and on a slow runner a call could +// start after the first run had finished, which is a legitimate second run and +// failed the test with 6 collects. func TestCollectAllSingleFlight(t *testing.T) { fake := newFakeCollector([]string{"svc-a", "svc-b", "svc-c"}, nil) - fake.hold = 2 * time.Millisecond // hold each collect so overlapping runs would be observable + fake.gate = make(chan struct{}) app, _, cancel := newSchedulerTestApp(t, fake, "svc-a", "svc-b", "svc-c") defer cancel() + const callers = 8 + returned := make(chan struct{}, callers) var wg sync.WaitGroup - for i := 0; i < 8; i++ { + for i := 0; i < callers; i++ { wg.Add(1) go func() { defer wg.Done() app.collectAll(context.Background()) + returned <- struct{}{} }() } + + // A call can only return while the gate is shut by bailing on the guard, so + // callers-1 returns means exactly one run holds the guard and is parked. + deadline := time.After(10 * time.Second) + for got := 0; got < callers-1; got++ { + select { + case <-returned: + case <-deadline: + close(fake.gate) + wg.Wait() + t.Fatalf("single-flight violated: only %d of %d calls bailed while one run was in progress", got, callers-1) + } + } + close(fake.gate) wg.Wait() // Single-flight: exactly one collectAll body ran, so the 3 services were collected