How do you test concurrent and time-dependent code deterministically? (testing/synctest)
Question 254HardGo 1.22 to 1.25
Tests built on time.Sleep are slow and flaky. testing/synctest became generally available in Go 1.25.
synctest.Test(t, f) runs f in an isolated "bubble":
- Time is virtual:
time.Now,Sleep, timers and context deadlines all use a fake clock. - The fake clock advances instantly when every goroutine in the bubble is durably blocked.
synctest.Wait()blocks until every other goroutine in the bubble is durably blocked, which gives you a deterministic sync point.
func TestContextTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
time.Sleep(5*time.Second - time.Nanosecond) // instant, virtual time
synctest.Wait()
if err := ctx.Err(); err != nil {
t.Fatalf("expired too early: %v", err)
}
time.Sleep(time.Nanosecond)
synctest.Wait()
if !errors.Is(ctx.Err(), context.DeadlineExceeded) {
t.Fatal("expected deadline exceeded")
}
})
}
Other tools:
-racewith-count=N.goleak.VerifyNone(t)to catch leaked goroutines.- Injecting clocks or channels as dependencies.
- Stress testing with
-cpu=1,2,8.
Gotcha: goroutines blocked on real I/O (network sockets) or on a mutex are not "durably blocked" for synctest, so use in-memory fakes such as net.Pipe. A channel created inside the bubble must not be used from outside it; doing so panics. A goroutine blocked on a channel created outside the bubble does not count as durably blocked.
More on Concurrency Patterns & sync
- Q252Design a thread-safe loading cache. Why is naive double-checked locking with a plain flag wrong in Go?
- Q253Channels or mutexes: how do you decide? ("Share memory by communicating")
- Q255What does this print? Can recover in main catch a panic from another goroutine?
- Q256What is false sharing, how does it hurt concurrent Go code, and how do you fix it?
- Q257How is GOMAXPROCS chosen in containers, and what changed in Go 1.25?
- Q258Implement a pub/sub broadcaster where one slow subscriber cannot block the others.