Deflake AppArchitectureTests: raise the local waitUntil to settleTimeout (#1653)

* Deflake AppArchitectureTests: raise the local waitUntil to settleTimeout

The file-local waitUntil defaulted to 3s — below the documented
minimumSettleTimeout floor — and the recent-chats geo-dedup wait
missed it on a loaded runner right after merge (the Combine hop
through receive(on: .main) was starved). Every caller waits for a
condition to become true, so green runs return immediately and never
pay the 30s deadline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix the real geo-dedup race: re-assert tracker state on each poll

The 30s deadline didn't help — the recent-chats geo-dedup wait wasn't
slow, it was permanently broken under parallel swift-test load: the
view model's own channel binding delivers its initial .mesh selection
asynchronously and resets the active participant geohash when it lands
(GeohashSubscriptionManager → setActiveParticipantGeohash(nil)),
wiping the test's setup so the dedup could never happen. Reproduced
locally with `swift test --parallel`; standalone runs never hit it.

The wait now re-asserts setActiveGeohash + recordParticipant on every
poll (both idempotent) — the same self-healing pattern the geohash
timeline test already uses for singleton interference. Interference
heals on the next poll; a genuine dedup failure still times out.
6× full parallel runs green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack 2026-08-10 09:12:50 +02:00 committed by GitHub
parent 5b592c8bae
commit dba67c1466
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -69,7 +69,11 @@ private func makeArchitectureMessage(
@MainActor
private func waitUntil(
timeoutNanoseconds: UInt64 = 3_000_000_000,
// Settle deadline, not a latency budget (see TestConstants.settleTimeout):
// the old 3s default flaked on CI the moment a Combine hop through
// receive(on: .main) was starved. Every caller waits for a condition to
// become true, so passing runs return immediately.
timeoutNanoseconds: UInt64 = UInt64(TestConstants.settleTimeout * 1_000_000_000),
pollNanoseconds: UInt64 = 20_000_000,
_ condition: @escaping @MainActor () -> Bool
) async {
@ -580,11 +584,19 @@ struct AppArchitectureTests {
// While that person is visible in the geohash roster, the chat row
// collapses same absent-from-rosters contract as mesh.
viewModel.participantTracker.setActiveGeohash("u4pruy")
viewModel.participantTracker.recordParticipant(pubkeyHex: pubkeyHex, geohash: "u4pruy")
//
// Re-assert the tracker state on every poll: the view model's own
// channel binding delivers its initial .mesh selection asynchronously
// and resets the active participant geohash when it lands
// (GeohashSubscriptionManager.setActiveParticipantGeohash(nil)) on
// a loaded parallel runner that reset arrives AFTER this setup and
// the dedup can never happen. Both calls are idempotent, so the
// interference heals on the next poll while a genuine dedup failure
// still times out.
await waitUntil {
!peerListModel.recentChatRows.contains { $0.peerID == geoDMPeer }
viewModel.participantTracker.setActiveGeohash("u4pruy")
viewModel.participantTracker.recordParticipant(pubkeyHex: pubkeyHex, geohash: "u4pruy")
return !peerListModel.recentChatRows.contains { $0.peerID == geoDMPeer }
}
#expect(!peerListModel.recentChatRows.contains { $0.peerID == geoDMPeer })
#expect(peerListModel.recentChatRows.map(\.peerID) == [offlinePeerID])