test: migrate timer-based tests to testing/synctest#4453
Draft
kakkoyun wants to merge 3 commits intoDataDog:mainfrom
Draft
test: migrate timer-based tests to testing/synctest#4453kakkoyun wants to merge 3 commits intoDataDog:mainfrom
kakkoyun wants to merge 3 commits intoDataDog:mainfrom
Conversation
2d61b7b to
cd2d72b
Compare
Extend the internal/synctest wrapper with a Wait() re-export and migrate tests that used raw time.Sleep as a proxy for timer expiry. - internal/synctest: add Wait() re-export - internal/log: wrap rate-limiter expiry test in synctest bubble - ddtrace/tracer/span_test: replace duration-measurement sleeps with synctest bubbles; simplify polling in TestSpanFinishWithError - ddtrace/tracer/stats_test: replace concentrator flush sleep with synctest.Wait() - ddtrace/tracer/writer_test: replace concurrent flush sleep with synctest.Wait() Inside a synctest bubble time.Sleep is instant (fake clock), so these tests go from ~seconds of wall time to effectively zero. Signed-off-by: Kemal Akkoyun <kemal.akkoyun@datadoghq.com>
Replace assert.Eventually polling loops, time-variable overrides, and runtime.Gosched() workarounds with deterministic synctest bubbles. - ddtrace/tracer/abandonedspans_test: wrap all subtests in synctest.Test, compute span start times relative to the fake clock's time.Now(), and remove the setTestTime() helper that overrode the package-level now var. - internal/telemetry/client_test: wrap TestClientFlush in synctest.Test; add a 1 ns sleep in the heartbeat case to advance the fake clock past the rate-limiter interval; remove runtime.Gosched() calls (no-op inside bubbles); switch from t.Cleanup to defer for c.Close() so the ticker goroutine stops before the bubble exits. - ddtrace/tracer/tracer_test: migrate TestTracerAtomicFlush and TestWorker; replace a time.After+time.Sleep polling loop with synctest.Wait() followed by a direct assertion. Signed-off-by: Kemal Akkoyun <kemal.akkoyun@datadoghq.com>
On Windows, var now points to GetSystemTimePreciseAsFileTime() via a direct Windows syscall. This syscall bypasses testing/synctest's fake clock, which only intercepts time.Now(). As a result, synctest-wrapped tests fail on Windows: span durations read the real wall clock (a few µs) instead of the fake 2 ms advance, and span-age strings diverge between the test and the code under test. Add an init() in time_windows_test.go that overrides now and nowTime to call time.Now() for the test binary. Production code keeps its high-precision syscall; tests gain fake-clock compatibility without any change to production behavior. Signed-off-by: Kemal Akkoyun <kemal.akkoyun@datadoghq.com>
c3260ff to
0c80f99
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Migrates timer-dependent and polling-loop tests to use Go 1.25's
testing/synctestfake-clockbubbles, replacing real wall-clock sleeps and
assert.Eventuallypolling with deterministic, instant-in-fake-time equivalents.
The migration is structured in four phases:
Phase 0 — extend
internal/synctestwrapper with aWait()re-export.
Phase 1 — pure timer tests (1 sleep replaced per test):
internal/log/log_test.go— rate-limiter expiryddtrace/tracer/span_test.go— duration measurement + pollingddtrace/tracer/stats_test.go— concentrator flushddtrace/tracer/writer_test.go— concurrent flushPhase 2 —
assert.Eventually+ injectable-time patterns:ddtrace/tracer/abandonedspans_test.go— replace 10assert.Eventuallycalls anddefer setTestTime()()overrides; spanstart times are now relative to
time.Now()inside each bubble.internal/telemetry/client_test.go— wrapTestClientFlushtable-driven loop in
synctest.Test; fix heartbeat case with a 1 nssleep; remove
runtime.Gosched()(no-op inside bubbles); usedefer c.Close()instead oft.Cleanupso the ticker goroutine stops beforethe bubble exits.
Phase 3 — polling loops:
ddtrace/tracer/tracer_test.go: migrateTestTracerAtomicFlushandTestWorker; replace atime.After(2s*timeMultiplicator)+time.Sleep(10ms)polling loop withsynctest.Wait()+ directassertion.
Phase 4 — cleanup: remove
setTestTime(), which overrode thepackage-level
nowvar for deterministic span-age strings. Thesynctest fake clock makes this override redundant.
Motivation
Timer-based tests that use
time.Sleeporassert.Eventuallyareinherently racy and slow. They either rely on wall-clock timing (fragile
in CI, slow on loaded machines) or use inflated timeouts that make the
suite unnecessarily slow.
testing/synctestprovides a fake clock wheretime.Sleep(1 * time.Second)is instant and deterministic, eliminatingthe root cause rather than working around it with time multipliers.
Reviewer's Checklist
make lintlocally.make testlocally.make generatelocally.make fix-moduleslocally.