Skip to content

Test suite flakiness: 22 issues across race conditions, timing, and resource management #1963

Description

@mkysel

Summary

Systematic analysis of all 135 _test.go files identified 22 distinct flakiness patterns that can cause intermittent CI failures, test hangs, and fatal panics.

Breakdown: 9 CRITICAL · 8 HIGH · 5 MEDIUM


CRITICAL — Likely Causing Failures Today

1. Concurrent map access without mutex — originator_stream_test.go

Lines: pkg/sync/originator_stream_test.go:162, 197, 282, 341, 378, 433, 504

lastSequenceIds maps are written by goroutines and read in require.Eventually polling loops without synchronization. Go's runtime will panic:

fatal error: concurrent map read and map write

Fix: Wrap with sync.RWMutex or use sync.Map.


2. Channel reads without timeout — can hang forever

  • pkg/registry/notifier_test.go:62-76for v := range ch blocks forever if channel never closes
  • pkg/indexer/common/log_handler_test.go:99-107 — unbuffered done channel; goroutine orphaned on timeout, may panic on double-close
  • pkg/sync/originator_stream_test.go — direct <-ch reads without select/timeout

Fix: Always use select with case <-time.After(...) or test-scoped context deadline.


3. Package-level state mutation without synchronization

  • pkg/tracing/tracing_test.go:13-19apmEnabled (package-level bool) set/restored via t.Cleanup() with no mutex. Parallel tests race on this value.
  • pkg/api/message/subscribe_test.go:33-50allRows (package-level slice) re-assigned in setupTest(). Parallel tests overwrite each other's data.

Fix: Protect with sync.Mutex or restructure so each test owns its own data.


4. Ultra-tight require.Eventually timeouts (500ms for DB ops)

  • pkg/indexer/storer/groupMessage_test.go — 50ms tick / 500ms timeout
  • pkg/indexer/storer/identityUpdate_test.go — 50ms tick / 500ms timeout

A single slow query or GC pause under CI load causes failure.

Fix: Increase to 5s+ timeout.


5. Goroutines spawned without lifecycle management

pkg/sync/originator_stream_test.go:158-160, 193-195, 276, 333, 376, 431, 500, 572, 634

go dbStorerInstance.Start() called without WaitGroup, done channel, or t.Cleanup. Tests exit before goroutine finishes → resource leaks, stale goroutines writing to closed DB connections.

Fix: Use t.Cleanup() to cancel context and wait for goroutine completion.


6. Unfired barrier channel — goroutine leak

pkg/blockchain/noncemanager/manager_test.go:264-300

Barrier channel created but never closed. All 50 goroutines block forever on <-barrier.

Fix: Add close(barrier) to release goroutines.


HIGH — Frequent Intermittent Failures

7. time.Sleep as synchronization (~50+ instances)

Most problematic:

File Sleep Waiting For
pkg/payerreport/store_test.go:185 1ms DB write
pkg/payerreport/workers/settlement_test.go:516 1ms Async op
pkg/blockchain/noncemanager/redis/manager_test.go:52,182 1ms Redis ops
pkg/blockchain/noncemanager/manager_test.go:303 1ms Concurrent nonce detection
pkg/db/sequences_test.go:58,78 10ms DB locking
pkg/db/payer_test.go:68 5ms Concurrent payer creation
pkg/tracing/tracing_test.go:82,107 100ms 50ms TTL expiry (only 2x margin)
pkg/blockchain/oracle/oracle_test.go:101 300ms 250ms stale threshold (50ms margin)

Fix: Replace with require.Eventually or event-driven synchronization.


8. Sleeps coupled to internal polling constants

File Pattern
pkg/api/message/subscribe_topics_test.go:94 Sleep(SubscribeWorkerPollTime + 100ms)
pkg/api/metadata/cursor_test.go:99 Sleep(SubscribeWorkerPollTime + 100ms)
pkg/api/message/subscribe_test.go:620 Sleep(4 * SubscribeWorkerPollTime)

Fix: Use require.Eventually instead of coupling to implementation internals.


9. reflect.DeepEqual on proto messages

pkg/server/server_test.go:178-180, 200-202 — proto structs have unexported internal fields that cause spurious inequality. Note: pkg/api/message/publish_test.go:84 correctly uses proto.Equal().

Fix: Replace with proto.Equal().


10. Unsynchronized counter in flaky query helper

pkg/db/subscription_test.go:115-125numQueries++ in a closure without synchronization creates a data race when called concurrently by the polling mechanism.

Fix: Use atomic.Int32.


11. Random sleep in test — non-reproducible

pkg/blockchain/oracle/oracle_test.go:84time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond). Failures cannot be reproduced.

Fix: Use fixed sleep or event-driven sync.


12. Unseeded rand in test inputs

  • pkg/api/message/subscribe_test.go:391rand.Intn(high - low)
  • pkg/db/gateway_envelope_batch_test.go:59,152rand.Intn(10) + 1 for batch size

Different behavior every run; failures non-reproducible.

Fix: Use fixed seed or deterministic inputs.


13. Port reuse race

pkg/db/pgx_test.go:150-176 — listener opened on :0, port captured, listener closed, then port reused. Between close and rebind another process can claim the port.

Fix: Pass the listener directly instead of close-and-rebind.


14. gRPC listener leak

pkg/api/payer/publish_test.go:264-283net.Listen + grpc.NewServer without t.Cleanup(s.GracefulStop). Listener leaks if test fails before goroutine starts.

Fix: Add t.Cleanup(func() { s.GracefulStop() }).


MEDIUM — Occasional Failures

15. context.Background() instead of test-scoped context

Widespread (~20+ files), especially pkg/prune/prune_test.go (13 occurrences). Test timeouts don't propagate; hung operations hang forever.

Fix: Use t.Context() (Go 1.24+).


16. Missing t.Cleanup for servers/workers

  • pkg/blockchain/rpcMultiClient_test.go — HTTP test servers
  • pkg/sync/syncworker_test.go — workers
  • pkg/api/server_test.go — server shutdown

17. Database result ordering assumptions

  • pkg/prune/prune_test.go:194,240 — SELECT without ORDER BY
  • pkg/db/gateway_envelope_batch_test.go:362 — payer_id query without ordering
  • pkg/api/message/subscribe_test.go — notification order assumed deterministic

Fix: Add ORDER BY or sort results before comparison.


18. Tight time.After deadlines in select blocks

File Timeout What It Guards
pkg/indexer/common/log_handler_test.go:105,166 1s Async DB work
pkg/api/message/publish_worker_test.go:408 2s Worker exit

Fix: Use 10s+ for I/O-bound operations in CI.


19. Hardcoded Redis address shared across all tests

pkg/testutils/redis/redis.go:14localhost:6379 DB 15. All Redis tests compete for the same instance.

Fix: Use testcontainers or verify availability before tests.


20. Concurrent Anvil container startup race

pkg/testutils/anvil/anvil_test.go:26-49 — 10 concurrent containers with 5s timeout and no backoff.


21. Connection pool exhaustion under parallel load

pkg/testutils/store.go:70-74 — default pool (25 conns) shared across parallel tests.


22. Transaction lock race in prune tests

pkg/prune/prune_test.go:203-235 — test holds lock and runs executor with no guaranteed timeout behavior.


Top 10 Flakiest Files

Rank File Issues
1 pkg/sync/originator_stream_test.go Concurrent map access, orphaned goroutines, no lifecycle mgmt
2 pkg/api/message/subscribe_test.go Shared global state, ordering assumptions, unseeded rand
3 pkg/tracing/tracing_test.go Package-level state race, tight TTL margins
4 pkg/indexer/common/log_handler_test.go 1s timeout, unbuffered done channel, goroutine leak
5 pkg/registry/notifier_test.go Unbounded channel read, 100 unsynced goroutines
6 pkg/server/server_test.go reflect.DeepEqual on protos, lifecycle races
7 pkg/blockchain/noncemanager/manager_test.go Unfired barrier, 1ms sleeps
8 pkg/blockchain/oracle/oracle_test.go Random sleep, 50ms timing margin
9 pkg/db/subscription_test.go Unsynchronized counter, context lifecycle
10 pkg/db/gateway_envelope_batch_test.go Unseeded rand, missing ORDER BY

Suggested Fix Priority

Immediate (prevents panics and hangs):

  1. Mutex on lastSequenceIds map in originator_stream_test.go
  2. Timeouts on all bare channel reads
  3. close(barrier) in nonce manager test
  4. Mutex on apmEnabled in tracing tests
  5. atomic.Int32 for numQueries in subscription_test.go
  6. proto.Equal instead of reflect.DeepEqual in server_test.go

Short-term (reduces CI flakiness):
7. Replace all 1ms sleeps with require.Eventually
8. Increase 500ms Eventually timeouts to 5s+
9. t.Cleanup for all goroutines/servers started in tests
10. Fix port reuse race in pgx_test.go

Medium-term (improves reliability):
11. Migrate context.Background()t.Context() across test suite
12. Replace proto require.Equal with protocmp.Transform()
13. Remove sleep coupling to SubscribeWorkerPollTime
14. Sort results before slice comparisons in query tests
15. Fixed seed or deterministic inputs for rand in tests

🤖 Generated with Claude Code

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

Status
In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions