swift-nio/Tests/NIOPosixTests/BootstrapTest.swift

703 lines
31 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIOCore
import NIOEmbedded
@testable import NIOPosix
import NIOConcurrencyHelpers
import XCTest
class BootstrapTest: XCTestCase {
var group: MultiThreadedEventLoopGroup!
var groupBag: [MultiThreadedEventLoopGroup]? = nil // protected by `self.lock`
let lock = NIOLock()
override func setUp() {
XCTAssertNil(self.group)
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
self.lock.withLock {
XCTAssertNil(self.groupBag)
self.groupBag = []
}
}
override func tearDown() {
XCTAssertNoThrow(try self.lock.withLock {
guard let groupBag = self.groupBag else {
XCTFail()
return
}
XCTAssertNoThrow(try groupBag.forEach {
XCTAssertNoThrow(try $0.syncShutdownGracefully())
})
self.groupBag = nil
XCTAssertNotNil(self.group)
})
XCTAssertNoThrow(try self.group?.syncShutdownGracefully())
self.group = nil
}
func freshEventLoop() -> EventLoop {
let group: MultiThreadedEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
self.lock.withLock {
self.groupBag!.append(group)
}
return group.next()
}
func testBootstrapsCallInitializersOnCorrectEventLoop() throws {
for numThreads in [1 /* everything on one event loop */,
2 /* some stuff has shared event loops */,
5 /* everything on a different event loop */] {
let group = MultiThreadedEventLoopGroup(numberOfThreads: numThreads)
defer {
XCTAssertNoThrow(try group.syncShutdownGracefully())
}
let childChannelDone = group.next().makePromise(of: Void.self)
let serverChannelDone = group.next().makePromise(of: Void.self)
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
.childChannelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
childChannelDone.succeed(())
return channel.eventLoop.makeSucceededFuture(())
}
.serverChannelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
serverChannelDone.succeed(())
return channel.eventLoop.makeSucceededFuture(())
}
.bind(host: "localhost", port: 0)
.wait())
defer {
XCTAssertNoThrow(try serverChannel.close().wait())
}
let client = try assertNoThrowWithValue(ClientBootstrap(group: group)
.channelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
return channel.eventLoop.makeSucceededFuture(())
}
.connect(to: serverChannel.localAddress!)
.wait(), message: "resolver debug info: \(try! resolverDebugInformation(eventLoop: group.next(),host: "localhost", previouslyReceivedResult: serverChannel.localAddress!))")
defer {
XCTAssertNoThrow(try client.syncCloseAcceptingAlreadyClosed())
}
XCTAssertNoThrow(try childChannelDone.futureResult.wait())
XCTAssertNoThrow(try serverChannelDone.futureResult.wait())
}
}
func testTCPBootstrapsTolerateFuturesFromDifferentEventLoopsReturnedInInitializers() throws {
let childChannelDone = self.freshEventLoop().makePromise(of: Void.self)
let serverChannelDone = self.freshEventLoop().makePromise(of: Void.self)
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: self.freshEventLoop())
.childChannelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
defer {
childChannelDone.succeed(())
}
return self.freshEventLoop().makeSucceededFuture(())
}
.serverChannelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
defer {
serverChannelDone.succeed(())
}
return self.freshEventLoop().makeSucceededFuture(())
}
.bind(host: "127.0.0.1", port: 0)
.wait())
defer {
XCTAssertNoThrow(try serverChannel.close().wait())
}
let client = try assertNoThrowWithValue(ClientBootstrap(group: self.freshEventLoop())
.channelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
return self.freshEventLoop().makeSucceededFuture(())
}
.connect(to: serverChannel.localAddress!)
.wait())
defer {
XCTAssertNoThrow(try client.syncCloseAcceptingAlreadyClosed())
}
XCTAssertNoThrow(try childChannelDone.futureResult.wait())
XCTAssertNoThrow(try serverChannelDone.futureResult.wait())
}
func testUDPBootstrapToleratesFuturesFromDifferentEventLoopsReturnedInInitializers() throws {
XCTAssertNoThrow(try DatagramBootstrap(group: self.freshEventLoop())
.channelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
return self.freshEventLoop().makeSucceededFuture(())
}
.bind(host: "127.0.0.1", port: 0)
.wait()
.close()
.wait())
}
func testPreConnectedClientSocketToleratesFuturesFromDifferentEventLoopsReturnedInInitializers() throws {
var socketFDs: [CInt] = [-1, -1]
XCTAssertNoThrow(try Posix.socketpair(domain: .local,
type: .stream,
protocolSubtype: .default,
socketVector: &socketFDs))
defer {
// 0 is closed together with the Channel below.
XCTAssertNoThrow(try NIOBSDSocket.close(socket: socketFDs[1]))
}
XCTAssertNoThrow(try ClientBootstrap(group: self.freshEventLoop())
.channelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
return self.freshEventLoop().makeSucceededFuture(())
}
.withConnectedSocket(socketFDs[0])
.wait()
.close()
.wait())
}
func testPreConnectedServerSocketToleratesFuturesFromDifferentEventLoopsReturnedInInitializers() throws {
let socket =
try NIOBSDSocket.socket(domain: .inet, type: .stream, protocolSubtype: .default)
let serverAddress = try assertNoThrowWithValue(SocketAddress.makeAddressResolvingHost("127.0.0.1", port: 0))
try serverAddress.withSockAddr { address, len in
try NIOBSDSocket.bind(socket: socket, address: address,
address_len: socklen_t(len))
}
let childChannelDone = self.freshEventLoop().next().makePromise(of: Void.self)
let serverChannelDone = self.freshEventLoop().next().makePromise(of: Void.self)
let serverChannel = try assertNoThrowWithValue(try ServerBootstrap(group: self.freshEventLoop())
.childChannelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
defer {
childChannelDone.succeed(())
}
return self.freshEventLoop().makeSucceededFuture(())
}
.serverChannelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
defer {
serverChannelDone.succeed(())
}
return self.freshEventLoop().makeSucceededFuture(())
}
.withBoundSocket(socket)
.wait())
let client = try assertNoThrowWithValue(ClientBootstrap(group: self.freshEventLoop())
.channelInitializer { channel in
XCTAssert(channel.eventLoop.inEventLoop)
return self.freshEventLoop().makeSucceededFuture(())
}
.connect(to: serverChannel.localAddress!)
.wait())
defer {
XCTAssertNoThrow(try client.syncCloseAcceptingAlreadyClosed())
}
XCTAssertNoThrow(try childChannelDone.futureResult.wait())
XCTAssertNoThrow(try serverChannelDone.futureResult.wait())
}
func testTCPClientBootstrapAllowsConformanceCorrectly() throws {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try group.syncShutdownGracefully())
}
func restrictBootstrapType(clientBootstrap: NIOClientTCPBootstrap) throws {
let serverAcceptedChannelPromise = group.next().makePromise(of: Channel.self)
let serverChannel = try assertNoThrowWithValue(ServerBootstrap(group: group)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
serverAcceptedChannelPromise.succeed(channel)
return channel.eventLoop.makeSucceededFuture(())
}.bind(host: "127.0.0.1", port: 0).wait())
let clientChannel = try assertNoThrowWithValue(clientBootstrap
.channelInitializer({ (channel: Channel) in channel.eventLoop.makeSucceededFuture(()) })
.connect(host: "127.0.0.1", port: serverChannel.localAddress!.port!).wait())
var buffer = clientChannel.allocator.buffer(capacity: 1)
buffer.writeString("a")
try clientChannel.writeAndFlush(NIOAny(buffer)).wait()
let serverAcceptedChannel = try serverAcceptedChannelPromise.futureResult.wait()
// Start shutting stuff down.
XCTAssertNoThrow(try clientChannel.close().wait())
// Wait for the close promises. These fire last.
XCTAssertNoThrow(try EventLoopFuture.andAllSucceed([clientChannel.closeFuture,
serverAcceptedChannel.closeFuture],
on: group.next()).wait())
}
let bootstrap = NIOClientTCPBootstrap(ClientBootstrap(group: group), tls: NIOInsecureNoTLS())
try restrictBootstrapType(clientBootstrap: bootstrap)
}
func testServerBootstrapBindTimeout() throws {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try group.syncShutdownGracefully())
}
// Set a bindTimeout and call bind. Setting a bind timeout is currently unsupported
// by ServerBootstrap. We therefore expect the bind timeout to be ignored and bind to
// succeed, even with a minimal bind timeout.
let bootstrap = ServerBootstrap(group: group)
.bindTimeout(.nanoseconds(0))
let channel = try assertNoThrowWithValue(bootstrap.bind(host: "127.0.0.1", port: 0).wait())
XCTAssertNoThrow(try channel.close().wait())
}
func testServerBootstrapSetsChannelOptionsBeforeChannelInitializer() {
var channel: Channel? = nil
XCTAssertNoThrow(channel = try ServerBootstrap(group: self.group)
.serverChannelOption(ChannelOptions.autoRead, value: false)
.serverChannelInitializer { channel in
channel.getOption(ChannelOptions.autoRead).whenComplete { result in
func workaround() {
XCTAssertNoThrow(XCTAssertFalse(try result.get()))
}
workaround()
}
return channel.pipeline.addHandler(MakeSureAutoReadIsOffInChannelInitializer())
}
.bind(to: .init(ipAddress: "127.0.0.1", port: 0))
.wait())
XCTAssertNotNil(channel)
XCTAssertNoThrow(try channel?.close().wait())
}
func testClientBootstrapSetsChannelOptionsBeforeChannelInitializer() {
XCTAssertNoThrow(try withTCPServerChannel(group: self.group) { server in
var channel: Channel? = nil
XCTAssertNoThrow(channel = try ClientBootstrap(group: self.group)
.channelOption(ChannelOptions.autoRead, value: false)
.channelInitializer { channel in
channel.getOption(ChannelOptions.autoRead).whenComplete { result in
func workaround() {
XCTAssertNoThrow(XCTAssertFalse(try result.get()))
}
workaround()
}
return channel.pipeline.addHandler(MakeSureAutoReadIsOffInChannelInitializer())
}
.connect(to: server.localAddress!)
.wait())
XCTAssertNotNil(channel)
XCTAssertNoThrow(try channel?.close().wait())
})
}
func testPreConnectedSocketSetsChannelOptionsBeforeChannelInitializer() {
XCTAssertNoThrow(try withTCPServerChannel(group: self.group) { server in
var maybeSocket: Socket? = nil
XCTAssertNoThrow(maybeSocket = try Socket(protocolFamily: .inet, type: .stream))
XCTAssertNoThrow(XCTAssertEqual(true, try maybeSocket?.connect(to: server.localAddress!)))
var maybeFD: CInt? = nil
XCTAssertNoThrow(maybeFD = try maybeSocket?.takeDescriptorOwnership())
guard let fd = maybeFD else {
XCTFail("could not get a socket fd")
return
}
var channel: Channel? = nil
XCTAssertNoThrow(channel = try ClientBootstrap(group: self.group)
.channelOption(ChannelOptions.autoRead, value: false)
.channelInitializer { channel in
channel.getOption(ChannelOptions.autoRead).whenComplete { result in
func workaround() {
XCTAssertNoThrow(XCTAssertFalse(try result.get()))
}
workaround()
}
return channel.pipeline.addHandler(MakeSureAutoReadIsOffInChannelInitializer())
}
.withConnectedSocket(fd)
.wait())
XCTAssertNotNil(channel)
XCTAssertNoThrow(try channel?.close().wait())
})
}
func testDatagramBootstrapSetsChannelOptionsBeforeChannelInitializer() {
var channel: Channel? = nil
XCTAssertNoThrow(channel = try DatagramBootstrap(group: self.group)
.channelOption(ChannelOptions.autoRead, value: false)
.channelInitializer { channel in
channel.getOption(ChannelOptions.autoRead).whenComplete { result in
func workaround() {
XCTAssertNoThrow(XCTAssertFalse(try result.get()))
}
workaround()
}
return channel.pipeline.addHandler(MakeSureAutoReadIsOffInChannelInitializer())
}
.bind(to: .init(ipAddress: "127.0.0.1", port: 0))
.wait())
XCTAssertNotNil(channel)
XCTAssertNoThrow(try channel?.close().wait())
}
func testPipeBootstrapSetsChannelOptionsBeforeChannelInitializer() {
XCTAssertNoThrow(try withPipe { inPipe, outPipe in
var maybeInFD: CInt? = nil
var maybeOutFD: CInt? = nil
XCTAssertNoThrow(maybeInFD = try inPipe.takeDescriptorOwnership())
XCTAssertNoThrow(maybeOutFD = try outPipe.takeDescriptorOwnership())
guard let inFD = maybeInFD, let outFD = maybeOutFD else {
XCTFail("couldn't get pipe fds")
return [inPipe, outPipe]
}
var channel: Channel? = nil
XCTAssertNoThrow(channel = try NIOPipeBootstrap(group: self.group)
.channelOption(ChannelOptions.autoRead, value: false)
.channelInitializer { channel in
channel.getOption(ChannelOptions.autoRead).whenComplete { result in
func workaround() {
XCTAssertNoThrow(XCTAssertFalse(try result.get()))
}
workaround()
}
return channel.pipeline.addHandler(MakeSureAutoReadIsOffInChannelInitializer())
}
.withPipes(inputDescriptor: inFD, outputDescriptor: outFD)
.wait())
XCTAssertNotNil(channel)
XCTAssertNoThrow(try channel?.close().wait())
return []
})
}
func testPipeBootstrapInEventLoop() {
let testGrp = DispatchGroup()
testGrp.enter()
let eventLoop = self.group.next()
eventLoop.execute {
do {
let pipe = Pipe()
let readHandle = NIOFileHandle(descriptor: pipe.fileHandleForReading.fileDescriptor)
let writeHandle = NIOFileHandle(descriptor: pipe.fileHandleForWriting.fileDescriptor)
_ = NIOPipeBootstrap(group: self.group)
.withPipes(inputDescriptor: try readHandle.takeDescriptorOwnership(), outputDescriptor: try writeHandle.takeDescriptorOwnership())
.flatMap({ channel in
channel.close()
}).always({ _ in
testGrp.leave()
})
} catch {
XCTFail("Failed to bootstrap pipechannel in eventloop: \(error)")
testGrp.leave()
}
}
testGrp.wait()
}
func testServerBootstrapAddsAcceptHandlerAfterServerChannelInitialiser() {
// It's unclear if this is the right solution, see https://github.com/apple/swift-nio/issues/1392
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try group.syncShutdownGracefully())
}
struct FoundHandlerThatWasNotSupposedToBeThereError: Error {}
var maybeServer: Channel? = nil
XCTAssertNoThrow(maybeServer = try ServerBootstrap(group: group)
.serverChannelInitializer { channel in
// Here, we test that we can't find the AcceptHandler
return channel.pipeline.context(name: "AcceptHandler").flatMap { context -> EventLoopFuture<Void> in
XCTFail("unexpectedly found \(context)")
return channel.eventLoop.makeFailedFuture(FoundHandlerThatWasNotSupposedToBeThereError())
}.flatMapError { error -> EventLoopFuture<Void> in
XCTAssertEqual(.notFound, error as? ChannelPipelineError)
if case .some(.notFound) = error as? ChannelPipelineError {
return channel.eventLoop.makeSucceededFuture(())
}
return channel.eventLoop.makeFailedFuture(error)
}
}
.bind(host: "127.0.0.1", port: 0)
.wait())
guard let server = maybeServer else {
XCTFail("couldn't bootstrap server")
return
}
// But now, it should be there.
XCTAssertNoThrow(_ = try server.pipeline.context(name: "AcceptHandler").wait())
XCTAssertNoThrow(try server.close().wait())
}
func testClientBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNotNil(ClientBootstrap(validatingGroup: elg))
XCTAssertNotNil(ClientBootstrap(validatingGroup: el))
}
func testClientBootstrapRejectsNotWorkingELGsCorrectly() {
let elg = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNil(ClientBootstrap(validatingGroup: elg))
XCTAssertNil(ClientBootstrap(validatingGroup: el))
}
func testServerBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNotNil(ServerBootstrap(validatingGroup: elg))
XCTAssertNotNil(ServerBootstrap(validatingGroup: el))
XCTAssertNotNil(ServerBootstrap(validatingGroup: elg, childGroup: elg))
XCTAssertNotNil(ServerBootstrap(validatingGroup: el, childGroup: el))
}
func testServerBootstrapRejectsNotWorkingELGsCorrectly() {
let correctELG = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try correctELG.syncShutdownGracefully())
}
let wrongELG = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try wrongELG.syncShutdownGracefully())
}
let wrongEL = wrongELG.next()
let correctEL = correctELG.next()
// both wrong
XCTAssertNil(ServerBootstrap(validatingGroup: wrongELG))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongEL))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongELG, childGroup: wrongELG))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongEL, childGroup: wrongEL))
// group correct, child group wrong
XCTAssertNil(ServerBootstrap(validatingGroup: correctELG, childGroup: wrongELG))
XCTAssertNil(ServerBootstrap(validatingGroup: correctEL, childGroup: wrongEL))
// group wrong, child group correct
XCTAssertNil(ServerBootstrap(validatingGroup: wrongELG, childGroup: correctELG))
XCTAssertNil(ServerBootstrap(validatingGroup: wrongEL, childGroup: correctEL))
}
func testDatagramBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNotNil(DatagramBootstrap(validatingGroup: elg))
XCTAssertNotNil(DatagramBootstrap(validatingGroup: el))
}
func testDatagramBootstrapRejectsNotWorkingELGsCorrectly() {
let elg = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNil(DatagramBootstrap(validatingGroup: elg))
XCTAssertNil(DatagramBootstrap(validatingGroup: el))
}
func testNIOPipeBootstrapValidatesWorkingELGsCorrectly() {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNotNil(NIOPipeBootstrap(validatingGroup: elg))
XCTAssertNotNil(NIOPipeBootstrap(validatingGroup: el))
}
func testNIOPipeBootstrapRejectsNotWorkingELGsCorrectly() {
let elg = EmbeddedEventLoop()
defer {
XCTAssertNoThrow(try elg.syncShutdownGracefully())
}
let el = elg.next()
XCTAssertNil(NIOPipeBootstrap(validatingGroup: elg))
XCTAssertNil(NIOPipeBootstrap(validatingGroup: el))
}
func testConvenienceOptionsAreEquivalentUniversalClient() throws {
func setAndGetOption<Option>(option: Option, _ applyOptions : (NIOClientTCPBootstrap) -> NIOClientTCPBootstrap) throws
-> Option.Value where Option : ChannelOption {
var optionRead : EventLoopFuture<Option.Value>?
XCTAssertNoThrow(try withTCPServerChannel(group: self.group) { server in
var channel: Channel? = nil
XCTAssertNoThrow(channel = try applyOptions(NIOClientTCPBootstrap(
ClientBootstrap(group: self.group), tls: NIOInsecureNoTLS()))
.channelInitializer { channel in optionRead = channel.getOption(option)
return channel.eventLoop.makeSucceededFuture(())
}
.connect(to: server.localAddress!)
.wait())
XCTAssertNotNil(optionRead)
XCTAssertNotNil(channel)
XCTAssertNoThrow(try channel?.close().wait())
})
return try optionRead!.wait()
}
func checkOptionEquivalence<Option>(longOption: Option, setValue: Option.Value,
shortOption: ChannelOptions.TCPConvenienceOption) throws
where Option : ChannelOption, Option.Value : Equatable {
let longSetValue = try setAndGetOption(option: longOption) { bs in
bs.channelOption(longOption, value: setValue)
}
let shortSetValue = try setAndGetOption(option: longOption) { bs in
bs.channelConvenienceOptions([shortOption])
}
let unsetValue = try setAndGetOption(option: longOption) { $0 }
XCTAssertEqual(longSetValue, shortSetValue)
XCTAssertNotEqual(longSetValue, unsetValue)
}
try checkOptionEquivalence(longOption: ChannelOptions.socketOption(.so_reuseaddr),
setValue: 1,
shortOption: .allowLocalEndpointReuse)
try checkOptionEquivalence(longOption: ChannelOptions.allowRemoteHalfClosure,
setValue: true,
shortOption: .allowRemoteHalfClosure)
try checkOptionEquivalence(longOption: ChannelOptions.autoRead,
setValue: false,
shortOption: .disableAutoRead)
}
func testClientBindWorksOnSocketsBoundToEitherIPv4OrIPv6Only() {
for isIPv4 in [true, false] {
guard System.supportsIPv6 || isIPv4 else {
continue // need to skip IPv6 tests if we don't support it.
}
let localIP = isIPv4 ? "127.0.0.1" : "::1"
guard let serverLocalAddressChoice = try? SocketAddress(ipAddress: localIP, port: 0),
let clientLocalAddressWholeInterface = try? SocketAddress(ipAddress: localIP, port: 0),
let server1 = (try? ServerBootstrap(group: self.group)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.serverChannelOption(ChannelOptions.maxMessagesPerRead, value: 1)
.bind(to: serverLocalAddressChoice)
.wait()),
let server2 = (try? ServerBootstrap(group: self.group)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.serverChannelOption(ChannelOptions.maxMessagesPerRead, value: 1)
.bind(to: serverLocalAddressChoice)
.wait()),
let server1LocalAddress = server1.localAddress,
let server2LocalAddress = server2.localAddress else {
XCTFail("can't boot servers even")
return
}
defer {
XCTAssertNoThrow(try server1.close().wait())
XCTAssertNoThrow(try server2.close().wait())
}
// Try 1: Directly connect to 127.0.0.1, this won't do Happy Eyeballs.
XCTAssertNoThrow(try ClientBootstrap(group: self.group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.bind(to: clientLocalAddressWholeInterface)
.connect(to: server1LocalAddress)
.wait()
.close()
.wait())
var maybeChannel1: Channel? = nil
// Try 2: Connect to "localhost", this will do Happy Eyeballs.
XCTAssertNoThrow(maybeChannel1 = try ClientBootstrap(group: self.group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.bind(to: clientLocalAddressWholeInterface)
.connect(host: "localhost", port: server1LocalAddress.port!)
.wait())
guard let myChannel1 = maybeChannel1, let myChannel1Address = myChannel1.localAddress else {
XCTFail("can't connect channel 1")
return
}
XCTAssertEqual(localIP, maybeChannel1?.localAddress?.ipAddress)
// Try 3: Bind the client to the same address/port as in try 2 but to server 2.
XCTAssertNoThrow(try ClientBootstrap(group: self.group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.connectTimeout(.hours(2))
.bind(to: myChannel1Address)
.connect(to: server2LocalAddress)
.map { channel -> Channel in
XCTAssertEqual(myChannel1Address, channel.localAddress)
return channel
}
.wait()
.close()
.wait())
}
}
}
private final class WriteStringOnChannelActive: ChannelInboundHandler {
typealias InboundIn = Never
typealias OutboundOut = ByteBuffer
let string: String
init(_ string: String) {
self.string = string
}
func channelActive(context: ChannelHandlerContext) {
var buffer = context.channel.allocator.buffer(capacity: self.string.utf8.count)
buffer.writeString(string)
context.writeAndFlush(self.wrapOutboundOut(buffer), promise: nil)
}
}
private final class MakeSureAutoReadIsOffInChannelInitializer: ChannelInboundHandler {
typealias InboundIn = Channel
func channelActive(context: ChannelHandlerContext) {
context.channel.getOption(ChannelOptions.autoRead).whenComplete { result in
func workaround() {
XCTAssertNoThrow(XCTAssertFalse(try result.get()))
}
workaround()
}
}
}