Skip to content

Commit e77be1b

Browse files
authored
chore: Fix flaky tests with deterministic runs (#4377)
1 parent 1b2229c commit e77be1b

1 file changed

Lines changed: 33 additions & 28 deletions

File tree

github/github_test.go

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,6 +2839,18 @@ func TestDo_rateLimit_sleepUntilClientResetLimit(t *testing.T) {
28392839
}
28402840

28412841
// Ensure sleep is aborted when the context is cancelled.
2842+
//
2843+
// The test verifies that when a request receives a 403 rate-limit response,
2844+
// the client begins sleeping until the reset time, and that canceling the
2845+
// context during (or just before) that sleep aborts it and returns
2846+
// context.Canceled.
2847+
//
2848+
// Determinism: The handler signals via requestReceived once it has run,
2849+
// guaranteeing exactly one request before cancellation. A tiny goroutine
2850+
// waits for that signal and then calls cancel, so the context is cancelled
2851+
// after the handler returns but independently of wall-clock timing. There
2852+
// are no time.After timeouts or time.Sleep delays: the test either completes
2853+
// in microseconds or hangs (caught by the test framework's global timeout).
28422854
func TestDo_rateLimit_abortSleepContextCancelled(t *testing.T) {
28432855
t.Parallel()
28442856
client, mux, _ := setup(t)
@@ -2870,42 +2882,31 @@ func TestDo_rateLimit_abortSleepContextCancelled(t *testing.T) {
28702882

28712883
req, _ := client.NewRequest(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), "GET", ".", nil)
28722884

2873-
errCh := make(chan error, 1)
2885+
// Cancel the context as soon as the handler has processed the request.
2886+
// The handler always runs and returns before the client begins the
2887+
// rate-limit sleep (the HTTP transport is synchronous), so cancel fires
2888+
// either just before or during the sleep — both paths are handled
2889+
// identically by sleepUntilResetWithBuffer's select.
28742890
go func() {
2875-
_, err := client.Do(req, nil)
2876-
errCh <- err
2891+
<-requestReceived
2892+
cancel()
28772893
}()
28782894

2879-
// Wait until the handler has processed the request, ensuring exactly one
2880-
// request was made before we cancel the context to abort the rate-limit
2881-
// sleep. This decouples the request-count assertion from the timing of
2882-
// the context cancellation, eliminating flakiness on slow runners.
2883-
select {
2884-
case <-requestReceived:
2885-
case <-time.After(10 * time.Second):
2886-
t.Fatal("Timed out waiting for request to be received.")
2887-
}
2895+
_, err := client.Do(req, nil)
28882896

28892897
if got, want := int(requestCount.Load()), 1; got != want {
28902898
t.Errorf("Expected 1 request, got %v", got)
28912899
}
2892-
2893-
// Now cancel the context while the client is sleeping waiting for the
2894-
// rate limit to reset. This should abort the sleep and return the
2895-
// context error.
2896-
cancel()
2897-
2898-
select {
2899-
case err := <-errCh:
2900-
if !errors.Is(err, context.Canceled) {
2901-
t.Errorf("Expected context cancelled error, got: %v", err)
2902-
}
2903-
case <-time.After(10 * time.Second):
2904-
t.Fatal("Timed out waiting for Do to return after context cancellation.")
2900+
if !errors.Is(err, context.Canceled) {
2901+
t.Errorf("Expected context cancelled error, got: %v", err)
29052902
}
29062903
}
29072904

29082905
// Ensure sleep is aborted when the context is cancelled on initial request.
2906+
//
2907+
// Determinism: The context is pre-cancelled before Do is called, so
2908+
// checkRateLimitBeforeDo → sleepUntilResetWithBuffer sees ctx.Done() already
2909+
// closed and returns immediately. No wall-clock timeout is involved.
29092910
func TestDo_rateLimit_abortSleepContextCancelledClientLimit(t *testing.T) {
29102911
t.Parallel()
29112912
client, mux, _ := setup(t)
@@ -2924,8 +2925,12 @@ func TestDo_rateLimit_abortSleepContextCancelledClientLimit(t *testing.T) {
29242925
w.WriteHeader(http.StatusOK)
29252926
fmt.Fprintln(w, `{}`)
29262927
})
2927-
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond)
2928-
defer cancel()
2928+
// Pre-cancel the context: checkRateLimitBeforeDo will call
2929+
// sleepUntilResetWithBuffer which immediately returns ctx.Err() because
2930+
// ctx.Done() is already closed. This is fully deterministic — no timing
2931+
// race between a short timeout and goroutine scheduling.
2932+
ctx, cancel := context.WithCancel(t.Context())
2933+
cancel()
29292934
req, _ := client.NewRequest(context.WithValue(ctx, SleepUntilPrimaryRateLimitResetWhenRateLimited, true), "GET", ".", nil)
29302935
_, err := client.Do(req, nil)
29312936
var rateLimitError *RateLimitError
@@ -2936,7 +2941,7 @@ func TestDo_rateLimit_abortSleepContextCancelledClientLimit(t *testing.T) {
29362941
t.Errorf("Expected request to be prevented because context cancellation, got: %v.", got)
29372942
}
29382943
if got, want := int(requestCount.Load()), 0; got != want {
2939-
t.Errorf("Expected 1 requests, got %v", got)
2944+
t.Errorf("Expected 0 requests, got %v", got)
29402945
}
29412946
}
29422947

0 commit comments

Comments
 (0)