Skip to content

test: fix flaky fdv2_rapidStateChangesCoalesceIntoOneRebuild#365

Open
abelonogov-ld wants to merge 2 commits into
mainfrom
fix/fdv2-coalesce-test-notify-race
Open

test: fix flaky fdv2_rapidStateChangesCoalesceIntoOneRebuild#365
abelonogov-ld wants to merge 2 commits into
mainfrom
fix/fdv2-coalesce-test-notify-race

Conversation

@abelonogov-ld
Copy link
Copy Markdown
Contributor

@abelonogov-ld abelonogov-ld commented Jun 3, 2026

Summary

ConnectivityManagerTest.fdv2_rapidStateChangesCoalesceIntoOneRebuild intermittently failed CI (e.g. the Build and Test run on main) with:

java.lang.AssertionError: timed out waiting for stopping of data source
    at ConnectivityManagerTest.verifyDataSourceWasStopped(ConnectivityManagerTest.java:916)

Two latent issues combined to make it flaky:

1. Strict-mock call ordering. After resetAll(), the test records setOffline(any).anyTimes() then setInBackground(any).anyTimes() on a MockType.STRICT eventProcessor mock. Each updateEventProcessor() call repeats the (setOffline, setInBackground) pair, but a strict mock enforces call order — so the second connectivity change's setOffline is rejected as an unexpected call and throws on the listener thread, aborting it before setNetworkAvailable() runs. The captured system-err showed exactly this:

Unexpected method call EventProcessor.setOffline(false):
  EventProcessor.setInBackground(<any>): expected: at least 0, actual: 1

anyTimes() only relaxes the count, not the order. Fixed by disabling order checking for this phase with checkOrder(eventProcessor, false).

2. Notification reordering. MockPlatformState.setAndNotifyConnectivityChangeListeners spawns a new thread per call, so three rapid changes (false, true, false) can be delivered out of order. If true (the baseline) lands last, the debouncer's A→B→C→A dedup suppresses the rebuild and no data source is ever stopped. Added setAndNotifyConnectivityChangeListenersInSequence(...) which delivers a burst deterministically on a single thread, ending on false, while preserving the "rapid changes" intent.

The racing version passed locally only by luck; both issues are real races independent of CI load.

Test plan

  • fdv2_rapidStateChangesCoalesceIntoOneRebuild passes across repeated --rerun-tasks runs (6/6)
  • Full ConnectivityManagerTest class passes across repeated runs (3/3)
  • Full :launchdarkly-android-client-sdk:testDebugUnitTest suite passes (BUILD SUCCESSFUL)
  • No new lint errors; the new MockPlatformState helper is additive (other call sites unchanged)

Made with Cursor


Note

Low Risk
Test and shared test-helper changes only; production SDK behavior is unchanged.

Overview
Stabilizes fdv2_rapidStateChangesCoalesceIntoOneRebuild by fixing two test-only races that caused intermittent CI timeouts waiting for a data source stop.

In ConnectivityManagerTest, after resetAll() the phase that expects repeated EventProcessor updates now calls checkOrder(eventProcessor, false) so EasyMock strict ordering no longer rejects interleaved setOffline / setInBackground pairs when multiple connectivity updates run in parallel.

In MockPlatformState, adds setAndNotifyConnectivityChangeListenersInSequence(boolean...) to fire a burst of connectivity notifications in order on one thread. The debounce test switches from three separate setAndNotifyConnectivityChangeListeners calls to this helper so the sequence ends on false deterministically instead of sometimes finishing on true and suppressing the offline rebuild.

Reviewed by Cursor Bugbot for commit 5e9e9ad. Bugbot is set up for automated code reviews on this repo. Configure here.

This ConnectivityManager test intermittently failed CI with "timed out
waiting for stopping of data source" (ConnectivityManagerTest.java:916).
Two latent issues combined to make it flaky:

1. Strict-mock call ordering. After resetAll(), the test records
   setOffline(any).anyTimes() then setInBackground(any).anyTimes() on a
   STRICT eventProcessor mock. Each updateEventProcessor() call repeats the
   (setOffline, setInBackground) pair, but a strict mock enforces call order,
   so the second change's setOffline is rejected as "unexpected" and throws
   on the listener thread — aborting it before setNetworkAvailable() runs.
   Fixed by disabling order checking for this phase via checkOrder(false);
   anyTimes() only relaxes the count, not the order.

2. Notification reordering. setAndNotifyConnectivityChangeListeners spawns a
   new thread per call, so three rapid changes (false, true, false) could be
   delivered out of order. If true (the baseline) is applied last, the
   debouncer's A->B->C->A dedup suppresses the rebuild and no data source is
   ever stopped. Added MockPlatformState.setAndNotifyConnectivityChangeListenersInSequence
   to deliver a burst deterministically on one thread, ending on false.

Verified deterministic across repeated runs of the test, the full
ConnectivityManagerTest class, and the full unit test suite.

Co-authored-by: Cursor <cursoragent@cursor.com>
@abelonogov-ld abelonogov-ld requested a review from a team as a code owner June 3, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant