Both the dependency declaration and its catalog entry named
EscapedCoroutineErrors.kt as the sole reason kotlinx-coroutines-test is on the
test classpath. That file is gone, and nothing in the gate -- not ktlint, not
detekt, not lint -- fails on prose naming a deleted file, so this would have
survived as a reference a reader could only resolve through git history.
The dependency itself stays, and for a reason worth restating where it is
declared: `runTest` is what registers the collector callback, so the one test
that deliberately lets an error escape is the scope that receives it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`drainEscapedCoroutineErrors()` cleared the collector at rule-construction time
with `runCatching { runTest {} }`, and discarding what it found was the whole
mechanism: it could not tell the one known deposit from an escaped error nobody
had asserted on. That traded a loud, misleading failure for a silent one, which
was acceptable only while exactly one depositor existed and the seam to remove it
did not.
The seam exists now, so the depositor is gone: the OOM is consumed by the test
that raises it. Every Compose class takes the v2 `createComposeRule()` directly,
and a future escaped error fails a test again instead of disappearing.
The two findings the drain's KDoc carried that outlive it: the v2 rule and the
non-v2 `StateRestorationTester` do interoperate -- the note now sits at the two
declarations that pair them -- and a drain could never have been a `@Before`
(the rule's `runTest` wraps it) or a `@BeforeClass` (Robolectric runs that
outside the sandbox classloader, where the collector is a different object).
Full JVM suite run twice in a row with the drain deleted: 373 tests, 0 failures
both times.
Closes#66
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`onInputPicked` hops to a hard-coded `Dispatchers.IO` inside a `launch` with no
exception handler -- deliberate, because a real OutOfMemoryError should reach the
thread's default handler and take the process down. On the JVM there is no such
handler: kotlinx-coroutines-test installs a process-wide collector, once per
classloader and never removed, which keeps the error and rethrows it at whichever
`runTest` starts next. Every Compose rule is a `runTest`, so the OOM raised by
`ConversionViewModelProbeFailureTest` failed some *other* Compose class, and which
one moved between runs of identical, green code.
Naming the dispatcher gives the throw somewhere to land. With the pick inline
inside a `runTest`, the collector's callback belongs to the test that caused the
error, so it is handed over and consumed rather than stored for a stranger.
Both hops of a pick rather than only the probe, which is where this differs from
the seam issue #66 sketched: leaving the metadata query on a real IO thread makes
the coroutine resume on a main looper Robolectric leaves paused, and that bounce
is exactly the asynchrony that made delivery unpredictable.
That buys the assertion the test could not make before -- the real OutOfMemoryError
instance, not an inference from a card that never filled in, which is also what a
probe returning null looks like. Reverting the hop to `Dispatchers.IO` turns it
red: "expected java.lang.OutOfMemoryError to be thrown, but nothing was thrown".
Refs #66
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>