Skip to content

FetchFromICloudFlowTest fails intermittently: the fake ICloudService is null when the activity resumes #198

Description

@parawanderer

Two different tests in this class failed spuriously today and passed on re-run, on unrelated PRs:

Both present as icloud_device_container still being GONE after the Eventually window, which reads as a slow screen. It is not.

What the logcat says

W FetchFromICloudActivity: iCloud flow stopped: UNKNOWN - Attempt to invoke interface method
'io.reactivex.rxjava3.core.Observable dev.wander.android.opentagviewer.python.icloud.ICloudService.records(java.util.List)'
on a null object reference

Logged at 15:47:45.079, ~5ms after the activity reached RESUMED and well before the assertion gave up at 15:47:57. The screen was never going to populate — the fake was gone by the time the activity asked for it.

Why it is a race and not a slow screen

AppDependencies is process-global static state. open(fake) calls replaceICloud(() -> fake) and then launches the activity, while @After putTheRealOneBack calls AppDependencies.reset(). Activity teardown is asynchronous, so a previous test's reset can land after the next test has installed its fake, or a lingering activity can be recreated against a reset dependency. Nothing serialises the two.

Consistent with it being intermittent, with it hitting different tests in the class, and with it never reproducing in isolation.

Why it matters beyond the noise

It is indistinguishable, from the outside, from the failure mode rule 12 is about: a screen that renders empty. Today it twice looked like a real regression on a PR that could not have caused it, and the second time cost a full 20-minute re-run to rule out. A suite that cries wolf is a suite people stop reading.

Notes toward a fix

  • AppDependencies has no guard against being read after reset — a null factory could throw with a message naming the test seam rather than an NPE deep in an Rx chain
  • the class launches activities in open() rather than through a rule, so teardown ordering is not enforced
  • ActivityScenario.close() is called in @After, but so is reset(), and their order relative to the activity actually finishing is not guaranteed

Logcats are in the instrumented-logcat artifact of the runs above, per test.

Interactively co-authored by Claude Code and @parawanderer

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions