Skip to content

Commit 4fc60bc

Browse files
tmmampagent
andauthored
test(zero-swift): show the test server's side when an app lifecycle test times out (#115)
The lifecycle tests' flake on the iOS Simulator (4 of about 60 macOS runs) shows only the client's log: it sent the WebSocket upgrade and never logged the socket opening, then timed out after 10 s, as the test did. The local server's view, whether it accepted the connection and the upgrade finished, wasn't recorded, so which side stalls is unknown. The server now writes its listener and connection events to the same log the failure prints. Amp-Thread-ID: https://ampcode.com/threads/T-c513a037-a257-45e0-b9a9-15c15edfe859 Co-authored-by: Amp <amp@ampcode.com>
1 parent db501aa commit 4fc60bc

2 files changed

Lines changed: 35 additions & 15 deletions

File tree

‎swift/Tests/AppleTests/AppLifecycleTests.swift‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,23 @@
3030
private var peers: [WebSocketServer.Peer] = []
3131
private var task: Task<Void, Never>?
3232

33-
init() async throws {
34-
server = try WebSocketServer()
33+
/// Its events go to `log`, with the client's, so a test that times out shows what the server saw.
34+
init(_ log: RecordingLogSink) async throws {
35+
server = try WebSocketServer(trace: {
36+
log.log(level: .debug, message: "server: \($0)", context: [:])
37+
})
3538
port = try await server.start()
36-
task = Task { @MainActor [server] in
39+
task = Task { @MainActor [server, log] in
3740
for await event in server.events {
3841
switch event {
3942
case .connected(let peer, _):
4043
self.connected += 1
4144
self.peers.append(peer)
45+
log.log(
46+
level: .debug,
47+
message:
48+
"server: \(peer.connection.endpoint) open, acknowledging: \(self.acknowledge)",
49+
context: [:])
4250
if self.acknowledge { peer.send(.text(#"["connected",{"wsid":"w"}]"#)) }
4351
case .message(let peer, .text(let text)):
4452
self.messages.append(text)
@@ -177,7 +185,7 @@
177185
}
178186

179187
@Test func hiddenDisconnectsAndShownReconnects() async throws {
180-
let server = try await Server()
188+
let server = try await Server(log)
181189
defer { server.stop() }
182190
let zero = try makeZero(server)
183191
defer { zero.close() }
@@ -193,7 +201,7 @@
193201

194202
/// Hidden while the socket is open but not yet acknowledged: the client ends up disconnected with no socket open, and connects when shown. If the attempt timed out first (`timedOutWhileStopped`), the client closed the socket and, hidden, waits to be shown, still connecting, as zero-client does.
195203
@Test func hiddenWhileConnecting() async throws {
196-
let server = try await Server()
204+
let server = try await Server(log)
197205
defer { server.stop() }
198206
server.acknowledge = false
199207
let zero = try makeZero(server)
@@ -218,7 +226,7 @@
218226

219227
/// Many transitions in a row, without waiting between them: the client ends up in the state of the last one, with at most one socket open.
220228
@Test func rapidTransitionsSettleOnTheLast() async throws {
221-
let server = try await Server()
229+
let server = try await Server(log)
222230
defer { server.stop() }
223231
let zero = try makeZero(server)
224232
defer { zero.close() }
@@ -250,7 +258,7 @@
250258

251259
/// Transitions after `close()` do nothing: no socket is opened and the client stays closed.
252260
@Test func transitionsAfterCloseDoNothing() async throws {
253-
let server = try await Server()
261+
let server = try await Server(log)
254262
defer { server.stop() }
255263
let zero = try makeZero(server)
256264
try await eventually("connected") { zero.connection.state == .connected }
@@ -268,7 +276,7 @@
268276

269277
/// A `Zero` released without `close()`: its observers go with it, and later transitions don't reach it.
270278
@Test func releasedZeroStopsObserving() async throws {
271-
let server = try await Server()
279+
let server = try await Server(log)
272280
defer { server.stop() }
273281
weak var weakZero: Zero<Void>?
274282
do {
@@ -288,7 +296,7 @@
288296
#if canImport(UIKit)
289297
/// In the background, the client waits for the server to confirm the pending mutations before it disconnects, up to 10 seconds; back in the foreground before that, it stays connected.
290298
@Test func backgroundWaitsForPendingMutationsUntilTheForeground() async throws {
291-
let server = try await Server()
299+
let server = try await Server(log)
292300
defer { server.stop() }
293301
let zero = try makeZero(server)
294302
defer { zero.close() }
@@ -305,7 +313,7 @@
305313

306314
/// The server never confirms: the client disconnects after the 10 seconds.
307315
@Test func backgroundGivesUpWaitingAfterTenSeconds() async throws {
308-
let server = try await Server()
316+
let server = try await Server(log)
309317
defer { server.stop() }
310318
let zero = try makeZero(server)
311319
defer { zero.close() }

‎swift/Tests/AppleTests/WebSocketServer.swift‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,11 @@
6464
let events: AsyncStream<Event>
6565
private let continuation: AsyncStream<Event>.Continuation
6666
private let protocols = Locked<[String]>([])
67+
/// Reports the listener's and the connections' state changes, for a test to show when it fails.
68+
private let trace: (@Sendable (String) -> Void)?
6769

68-
init() throws {
70+
init(trace: (@Sendable (String) -> Void)? = nil) throws {
71+
self.trace = trace
6972
(events, continuation) = AsyncStream<Event>.makeStream()
7073
let parameters = NWParameters.tcp
7174
let options = NWProtocolWebSocket.Options()
@@ -86,15 +89,18 @@
8689
let continuation = self.continuation
8790
let protocols = self.protocols
8891
let queue = self.queue
92+
let trace = self.trace
8993
listener.newConnectionHandler = { connection in
9094
let peer = Peer(connection)
95+
trace?("accepted \(connection.endpoint)")
9196
connection.stateUpdateHandler = { state in
97+
trace?("\(connection.endpoint) \(state)")
9298
if case .ready = state {
9399
continuation.yield(.connected(peer, protocols: protocols.withLock { $0 }))
94100
}
95101
}
96102
connection.start(queue: queue)
97-
Self.receive(peer, continuation)
103+
Self.receive(peer, continuation, trace)
98104
}
99105
return try await withCheckedThrowingContinuation {
100106
(c: CheckedContinuation<UInt16, any Error>) in
@@ -106,6 +112,7 @@
106112
}
107113
}
108114
listener.stateUpdateHandler = { [listener] state in
115+
trace?("listener \(state)")
109116
switch state {
110117
case .ready: if first() { c.resume(returning: listener.port?.rawValue ?? 0) }
111118
case .failed(let error): if first() { c.resume(throwing: error) }
@@ -121,9 +128,13 @@
121128
continuation.finish()
122129
}
123130

124-
private static func receive(_ peer: Peer, _ events: AsyncStream<Event>.Continuation) {
131+
private static func receive(
132+
_ peer: Peer, _ events: AsyncStream<Event>.Continuation,
133+
_ trace: (@Sendable (String) -> Void)?
134+
) {
125135
peer.connection.receiveMessage { data, context, _, error in
126-
if error != nil {
136+
if let error {
137+
trace?("\(peer.connection.endpoint) receive failed: \(error)")
127138
events.yield(.closed(peer, code: 1006))
128139
return
129140
}
@@ -142,12 +153,13 @@
142153
case .applicationCode(let c)?, .privateCode(let c)?: code = Int(c)
143154
default: break
144155
}
156+
trace?("\(peer.connection.endpoint) close frame \(code)")
145157
events.yield(.closed(peer, code: code))
146158
return
147159
default:
148160
break
149161
}
150-
receive(peer, events)
162+
receive(peer, events, trace)
151163
}
152164
}
153165
}

0 commit comments

Comments
 (0)