Harden iOS-sim CI: destination fallback + Noise reconnect test determinism (#1483)

Two CI-robustness fixes for the iOS-simulator job seen on main:

1. The destination picker no longer dies on runner images with placeholder/unavailable simulator lists (macos-26-arm64 20260720.0258): it filters error/unavailable rows, falls back to xcrun simctl's available-device list, and as a last resort creates a simulator from the newest installed iOS runtime's own supported iPhone device types. Each path logs which route was taken.

2. Deflakes NoiseEncryptionServiceTests' quarantine-restore test: the injected 20ms responder timeout also armed during the SETUP handshake, so a loaded runner tore down the half-open responder session before the scenario began (production quarantine/restore verified correct — the failure signature was a fresh msg2 to a session-less peer). Timeout raised to 1s and the fixed 100ms sleep replaced with a bounded waitUntil on the restore condition. No assertion weakened; 10/10 sequential + 3/3 under full-core CPU load.
This commit is contained in:
jack
2026-07-26 16:13:04 +02:00
committed by GitHub
parent a1711bd399
commit 934b2cd2d3
2 changed files with 69 additions and 9 deletions
@@ -1235,9 +1235,17 @@ struct NoiseEncryptionServiceTests {
@Test("Lost reconnect completion restores the quarantined transport")
func timedOutReconnectRestoresQuarantinedTransport() async throws {
let alice = NoiseEncryptionService(keychain: MockKeychain())
// The injected responder timeout also arms during the ordinary setup
// handshake below (bob is its responder), where the only work between
// message 1 and message 3 is two consecutive synchronous statements.
// It must be generous enough that a preempted runner cannot let the
// timeout fire mid-setup and tear down the half-open responder at
// 20ms a loaded 2-core CI runner did exactly that, so message 3 was
// answered as a fresh initiation (96-byte message 2) and nothing was
// ever quarantined.
let bob = NoiseEncryptionService(
keychain: MockKeychain(),
ordinaryResponderHandshakeTimeout: 0.02
ordinaryResponderHandshakeTimeout: 1.0
)
let mallory = NoiseEncryptionService(keychain: MockKeychain())
let alicePeerID = PeerID(publicKey: alice.getStaticPublicKeyData())
@@ -1253,9 +1261,17 @@ struct NoiseEncryptionServiceTests {
)
#expect(!bob.hasEstablishedSession(with: alicePeerID))
try? await Task.sleep(nanoseconds: 100_000_000)
#expect(bob.hasEstablishedSession(with: alicePeerID))
// Poll instead of sleeping a fixed interval: the responder timeout
// fires on bob's manager queue at the quarantine deadline, and a
// starved runner can delay that work item well past the deadline.
let restored = await TestHelpers.waitUntil(
{ bob.hasEstablishedSession(with: alicePeerID) },
timeout: TestConstants.longTimeout
)
try #require(
restored,
"Responder timeout should restore the quarantined transport"
)
let oldTransport = try alice.encrypt(
Data("timeout rollback".utf8),
for: bobPeerID