Skip to content

Commit 70dc3e6

Browse files
committed
ci: unit test failure handling
[skip ci]
1 parent 4289539 commit 70dc3e6

3 files changed

Lines changed: 47 additions & 18 deletions

File tree

TestRunnerTests/Embassy/TCPSocket.swift

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,27 @@ public final class TCPSocket {
2525
}
2626

2727
/// Whether to ignore SIGPIPE signal or not
28+
///
29+
/// NOTE (NativeScript test harness): the get/set deliberately do NOT
30+
/// `assert()` on getsockopt/setsockopt failure. This server runs inside the
31+
/// XCUITest runner, where any trap aborts the entire run ("Executed 0
32+
/// tests"). Some accepted client sockets are already half-closed by the time
33+
/// we configure them — notably the runtime's synchronous HTTP module loader
34+
/// (NSURLConnection), whose sockets fail getpeername()/setsockopt() with
35+
/// EINVAL/ENOTCONN. Treat SO_NOSIGPIPE as best-effort: a later send() on a
36+
/// dead socket simply throws and is handled by Transport, instead of the
37+
/// whole runner crashing here (assertionFailure in this setter was the
38+
/// observed CI failure once getPeerName() was made tolerant).
2839
var ignoreSigPipe: Bool {
2940
get {
3041
#if os(Linux)
3142
return false
3243
#else
3344
var value: Int32 = 0
3445
var size = socklen_t(MemoryLayout<Int32>.size)
35-
assert(
36-
getsockopt(fileDescriptor, SOL_SOCKET, SO_NOSIGPIPE, &value, &size) >= 0,
37-
"Failed to get SO_NOSIGPIPE, errno=\(errno), message=\(lastErrorDescription())"
38-
)
46+
guard getsockopt(fileDescriptor, SOL_SOCKET, SO_NOSIGPIPE, &value, &size) >= 0 else {
47+
return false
48+
}
3949
return value == 1
4050
#endif
4151
}
@@ -47,15 +57,12 @@ public final class TCPSocket {
4757
return
4858
#else
4959
var value: Int32 = newValue ? 1 : 0
50-
assert(
51-
setsockopt(
52-
fileDescriptor,
53-
SOL_SOCKET,
54-
SO_NOSIGPIPE,
55-
&value,
56-
socklen_t(MemoryLayout<Int32>.size)
57-
) >= 0,
58-
"Failed to set SO_NOSIGPIPE, errno=\(errno), message=\(lastErrorDescription())"
60+
_ = setsockopt(
61+
fileDescriptor,
62+
SOL_SOCKET,
63+
SO_NOSIGPIPE,
64+
&value,
65+
socklen_t(MemoryLayout<Int32>.size)
5966
)
6067
#endif
6168
}

TestRunnerTests/Embassy/Transport.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,17 @@ public final class Transport {
134134
// (usually means that this function was called by resumeReading)
135135
return
136136
}
137-
fatalError("Failed to read, errno=\(errno), message=\(lastErrorDescription())")
137+
// NativeScript test harness: do NOT fatalError. This server runs in
138+
// the XCUITest runner, where a trap aborts the whole run ("Executed
139+
// 0 tests"). A half-closed client socket (e.g. the runtime's
140+
// NSURLConnection module loader) makes recv() fail with
141+
// ECONNRESET/ENOTCONN/EINVAL — tear the connection down like a peer
142+
// close instead of crashing the runner.
143+
closedByPeer()
144+
return
138145
} catch {
139-
fatalError("Failed to read")
146+
closedByPeer()
147+
return
140148
}
141149
guard data.count > 0 else {
142150
closedByPeer()
@@ -186,7 +194,7 @@ public final class Transport {
186194
socket.close()
187195
}
188196
}
189-
} catch let OSError.ioError(number, message) {
197+
} catch let OSError.ioError(number, _) {
190198
switch number {
191199
case EAGAIN:
192200
break
@@ -200,10 +208,14 @@ public final class Transport {
200208
closedByPeer()
201209

202210
default:
203-
fatalError("Failed to send, errno=\(number), message=\(message)")
211+
// NativeScript test harness: do NOT fatalError (it would abort
212+
// the XCUITest runner -> "Executed 0 tests"). Any other send()
213+
// failure on a half-dead client socket just means the peer is
214+
// gone; tear the connection down instead of crashing.
215+
closedByPeer()
204216
}
205217
} catch {
206-
fatalError("Failed to send")
218+
closedByPeer()
207219
}
208220
}
209221
}

TestRunnerTests/QUARANTINED_TESTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ server can't serve these requests.
6767
filter once the server is fixed):
6868
- `DefaultHTTPServer.handleNewConnection` tolerates `getPeerName()` failure and
6969
serves the connection with a placeholder peer (instead of crashing/dropping).
70+
- `TCPSocket.ignoreSigPipe` get/set no longer `assert()` on
71+
getsockopt/setsockopt failure — a half-dead loader socket made
72+
`setsockopt(SO_NOSIGPIPE)` fail with EINVAL, and the assert trapped (live in
73+
the Debug runner build) → `_assertionFailure` aborted the whole run
74+
("Executed 0 tests"). Now best-effort.
75+
- `Transport.handleRead`/`handleWrite` tear the connection down
76+
(`closedByPeer()`) on any unexpected recv/send error instead of `fatalError`,
77+
for the same reason: those sockets reach `Transport` once `getPeerName`/
78+
`ignoreSigPipe` stopped trapping, and the next event-loop read/write on a
79+
dead socket would otherwise crash the runner.
7080
- `/esm/timeout.mjs` responds via a non-blocking `loop.call(withDelay:)` instead
7181
of `Thread.sleep` (which wedged the single-threaded server).
7282
- The server also serves the `/ns/m/…` hot-data aliases and `/ns/core` bridge

0 commit comments

Comments
 (0)