Skip to content

Commit 2bacb97

Browse files
authored
Address flakiness of testSSLHandshakeErrorPropagation (#335)
Motivation: Flaky tests are bad. This test is flaky because the server closes the connection immediately upon channelActive. In practice this can mean that the handshake never even gets a chance to start: by the time the SSLHandler ends up in the pipeline the connection is already dead. Heck, by the time we attempt to complete the connection the connection might be dead. Modifications: - Change the shutdown to be on first read. - Remove the disabled autoRead. - Change the expected NIOTS failure mode to connectTimeout, which is how this manifests in NIOTS. Result: Test is no longer flaky.
1 parent bbebce3 commit 2bacb97

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

Diff for: Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ extension HTTPClientTests {
126126
("testNoBytesSentOverBodyLimit", testNoBytesSentOverBodyLimit),
127127
("testDoubleError", testDoubleError),
128128
("testSSLHandshakeErrorPropagation", testSSLHandshakeErrorPropagation),
129+
("testSSLHandshakeErrorPropagationDelayedClose", testSSLHandshakeErrorPropagationDelayedClose),
129130
("testWeCloseConnectionsWhenConnectionCloseSetByServer", testWeCloseConnectionsWhenConnectionCloseSetByServer),
130131
]
131132
}

Diff for: Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+61-6
Original file line numberDiff line numberDiff line change
@@ -2680,13 +2680,12 @@ class HTTPClientTests: XCTestCase {
26802680
class CloseHandler: ChannelInboundHandler {
26812681
typealias InboundIn = Any
26822682

2683-
func channelActive(context: ChannelHandlerContext) {
2683+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
26842684
context.close(promise: nil)
26852685
}
26862686
}
26872687

26882688
let server = try ServerBootstrap(group: self.serverGroup)
2689-
.childChannelOption(ChannelOptions.autoRead, value: false)
26902689
.childChannelInitializer { channel in
26912690
channel.pipeline.addHandler(CloseHandler())
26922691
}
@@ -2697,17 +2696,73 @@ class HTTPClientTests: XCTestCase {
26972696
XCTAssertNoThrow(try server.close().wait())
26982697
}
26992698

2700-
let request = try Request(url: "https://localhost:\(server.localAddress!.port!)", method: .GET)
2701-
let task = self.defaultClient.execute(request: request, delegate: TestHTTPDelegate())
2699+
// We set the connect timeout down very low here because on NIOTS this manifests as a connect
2700+
// timeout.
2701+
let config = HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(connect: .milliseconds(100), read: nil))
2702+
let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config)
2703+
defer {
2704+
XCTAssertNoThrow(try client.syncShutdown())
2705+
}
2706+
2707+
let request = try Request(url: "https://127.0.0.1:\(server.localAddress!.port!)", method: .GET)
2708+
let task = client.execute(request: request, delegate: TestHTTPDelegate())
2709+
2710+
XCTAssertThrowsError(try task.wait()) { error in
2711+
#if os(Linux)
2712+
XCTAssertEqual(error as? NIOSSLError, NIOSSLError.uncleanShutdown)
2713+
#else
2714+
if isTestingNIOTS() {
2715+
XCTAssertEqual(error as? ChannelError, .connectTimeout(.milliseconds(100)))
2716+
} else {
2717+
XCTAssertEqual((error as? IOError).map { $0.errnoCode }, ECONNRESET)
2718+
}
2719+
#endif
2720+
}
2721+
}
2722+
2723+
func testSSLHandshakeErrorPropagationDelayedClose() throws {
2724+
// This is as the test above, but the close handler delays its close action by a few hundred ms.
2725+
// This will tend to catch the pipeline at different weird stages, and flush out different bugs.
2726+
class CloseHandler: ChannelInboundHandler {
2727+
typealias InboundIn = Any
2728+
2729+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
2730+
context.eventLoop.scheduleTask(in: .milliseconds(100)) {
2731+
context.close(promise: nil)
2732+
}
2733+
}
2734+
}
2735+
2736+
let server = try ServerBootstrap(group: self.serverGroup)
2737+
.childChannelInitializer { channel in
2738+
channel.pipeline.addHandler(CloseHandler())
2739+
}
2740+
.bind(host: "127.0.0.1", port: 0)
2741+
.wait()
2742+
2743+
defer {
2744+
XCTAssertNoThrow(try server.close().wait())
2745+
}
2746+
2747+
// We set the connect timeout down very low here because on NIOTS this manifests as a connect
2748+
// timeout.
2749+
let config = HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(connect: .milliseconds(200), read: nil))
2750+
let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: config)
2751+
defer {
2752+
XCTAssertNoThrow(try client.syncShutdown())
2753+
}
2754+
2755+
let request = try Request(url: "https://127.0.0.1:\(server.localAddress!.port!)", method: .GET)
2756+
let task = client.execute(request: request, delegate: TestHTTPDelegate())
27022757

27032758
XCTAssertThrowsError(try task.wait()) { error in
27042759
#if os(Linux)
27052760
XCTAssertEqual(error as? NIOSSLError, NIOSSLError.uncleanShutdown)
27062761
#else
27072762
if isTestingNIOTS() {
2708-
XCTAssertEqual((error as? AsyncHTTPClient.HTTPClient.NWTLSError).map { $0.status }, errSSLClosedNoNotify)
2763+
XCTAssertEqual(error as? ChannelError, .connectTimeout(.milliseconds(200)))
27092764
} else {
2710-
XCTAssertEqual((error as? IOError).map { $0.errnoCode }, 54)
2765+
XCTAssertEqual((error as? IOError).map { $0.errnoCode }, ECONNRESET)
27112766
}
27122767
#endif
27132768
}

0 commit comments

Comments
 (0)