test(subgroups): stop a ceremony-queue test throwing into another test's name
Some checks failed
Material Design conformance / budgets (push) Has been cancelled
Material Design conformance / tests (push) Has been cancelled

`./gradlew :composeApp:jvmTest` failed `NavigationRoutingTest > an account already on
the device does not stay on the loading screen` with `UncaughtExceptionsBeforeTest:
There were uncaught exceptions before the test started`. **"Before the test started" is
the tell.** `runTest` reports exceptions that reached the default handler before it
began, so the name on the failure is whichever `runTest` ran next -- not the one that
threw. `NavigationRoutingTest` passes alone and the subgroups work never touched it.

The thrower is `CeremonyQueueRoutingJvmTest`, and the chain is five links with no
branch in it:

1. `awaiting()` builds a `ChatMessageListViewModel` on the bare
   `NO_OP_NOSTR_REPOSITORY` and calls `initiate()`.
2. `initiate()`'s second line is `scheduleSynchronization()`.
3. That launches on `Dispatchers.IO` and asks `MemberProfileSync` who cannot be named.
4. The fixture's `room()` gives every `LocalParticipant` a `profile = null`, so the
   answer is never empty and the next line always runs.
5. `queueNegentropySynchronizeRequest` is one of the **forty-one**
   `TODO("Not yet implemented")` bodies on that no-op, out of forty-five members. It
   throws in a coroutine nobody awaits, four times a run -- once per test in the class.

Fixed the way this file already fixes it for `DkgRepository`: an `object : X by NO_OP_X`
with the one member overridden. The comment above it carries the chain, because the next
person to see `UncaughtExceptionsBeforeTest` will be reading the name of the wrong test.

**Verified under the condition that reproduced it.** It is order-dependent, so a
filtered run is not evidence -- the culprit passes alone, and the pair passed too. The
full suite under `--rerun-tasks` failed reproducibly before and is 982 of 982 after.

**Not fixed here: the landmine itself.** Sixty `TODO()` bodies sit across four no-op
repositories -- 41 of 45 on `NostrRepository`, 12 of 37 on `ChatRepository`, 6 of 7 on
`MarmotRepository`, 1 of 1 on `SearchRepository` -- and seventeen jvmTest files build
view models on the Nostr one alone, so this will recur; the reporter had already hit it
through `getProfileWithPublicKey`. It is not a sweep, though: "no-op" means something
different for each member, the return types vary from `Unit` to `Flow<Int>`, and a
blanket empty body could make a test pass vacuously where it currently fails honestly.
Sixty judgement calls about shared code do not belong in a commit about one flake, so it
is flagged for its own change.

982 jvm tests, 0 failed, under `--rerun-tasks`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso Ngako
2026-10-02 21:22:29 +02:00
parent 1a6034dadb
commit f46a7c627d

View File

@@ -13,6 +13,7 @@ import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.delay
import press.mantra.compose.database.model.ChatRoom
import press.mantra.compose.database.model.DkgSession
import press.mantra.compose.database.model.NegentropySynchronizeRequest
import press.mantra.compose.database.model.Participant
import press.mantra.compose.database.model.intermdiate.LocalChatRoom
import press.mantra.compose.database.model.intermdiate.LocalParticipant
@@ -151,7 +152,23 @@ class CeremonyQueueRoutingJvmTest {
val viewModel = ChatMessageListViewModel(
initialChatMessageListUIState = ChatMessageListUIState.Loading,
localChatRoom = room(),
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY,
// Not the bare no-op. `initiate` calls `scheduleSynchronization` on
// its second line, which launches on `Dispatchers.IO` and -- because
// `room()` gives every participant a null profile, so there is always
// somebody it cannot name -- reaches
// `queueNegentropySynchronizeRequest`. That member is one of the
// forty-one `TODO("Not yet implemented")` bodies on
// NO_OP_NOSTR_REPOSITORY, so it throws in a coroutine nobody awaits.
//
// Nothing catches it, so it reaches the default handler, and the next
// `runTest` in the same JVM reports it as `UncaughtExceptionsBeforeTest`
// -- against its own name. That is how this test failed
// `NavigationRoutingTest` four times a run while passing itself.
nostrRepository = object : NostrRepository by NostrRepository.NO_OP_NOSTR_REPOSITORY {
override suspend fun queueNegentropySynchronizeRequest(
negentropySynchronizeRequests: List<NegentropySynchronizeRequest>
) = Unit
},
chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY,
frostSigningRepository = FrostSigningRepository.NO_OP_FROST_SIGNING_REPOSITORY,
dkgRepository = object : DkgRepository by DkgRepository.NO_OP_DKG_REPOSITORY {