Skip to content

Commit 8bde835

Browse files
authored
Ensure we can run tests in parallel (#191)
* Ensure we can run tests in parallel * Remove global TLSConfiguration * Run TLS tests serially
1 parent bca855c commit 8bde835

File tree

2 files changed

+207
-143
lines changed

2 files changed

+207
-143
lines changed

Tests/MQTTNIOTests/MQTTConnectionTests.swift

Lines changed: 155 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import NIOTransportServices
2727
import NIOSSL
2828
#endif
2929

30-
@Suite("MQTTConnection Tests", .serialized)
30+
@Suite("MQTTConnection Tests")
3131
struct MQTTConnectionTests {
3232
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
3333

@@ -97,65 +97,147 @@ struct MQTTConnectionTests {
9797
}
9898
}
9999

100-
@Test("Connect with TLS")
101-
func tlsConnect() async throws {
102-
try await MQTTConnection.withConnection(
103-
address: .hostname(Self.hostname, port: 8883),
104-
configuration: .init(
105-
useSSL: true,
106-
tlsConfiguration: Self.getTLSConfiguration(),
107-
sniServerName: "soto.codes"
108-
),
109-
identifier: "tlsConnect",
110-
eventLoop: Self.eventLoopGroupSingleton.any(),
111-
logger: self.logger
112-
) { connection in
113-
try await connection.ping()
100+
@Suite(.serialized)
101+
struct TLS {
102+
static let hostname = ProcessInfo.processInfo.environment["MOSQUITTO_SERVER"] ?? "localhost"
103+
104+
static let rootPath = #filePath
105+
.split(separator: "/", omittingEmptySubsequences: false)
106+
.dropLast(3)
107+
.joined(separator: "/")
108+
109+
static var eventLoopGroupSingleton: EventLoopGroup {
110+
#if os(Linux)
111+
MultiThreadedEventLoopGroup.singleton
112+
#else
113+
// Return TS Eventloop for non-Linux builds, as we use TS TLS
114+
NIOTSEventLoopGroup.singleton
115+
#endif
114116
}
115-
}
116117

117-
@Test("Connect with WebSocket and TLS")
118-
func webSocketAndTLSConnect() async throws {
119-
try await MQTTConnection.withConnection(
120-
address: .hostname(Self.hostname, port: 8081),
121-
configuration: .init(
122-
timeout: .seconds(5),
123-
useSSL: true,
124-
tlsConfiguration: Self.getTLSConfiguration(),
125-
sniServerName: "soto.codes",
126-
webSocketConfiguration: .init()
127-
),
128-
identifier: "webSocketAndTLSConnect",
129-
eventLoop: Self.eventLoopGroupSingleton.any(),
130-
logger: self.logger
131-
) { connection in
132-
try await connection.ping()
118+
let logger: Logger = {
119+
var logger = Logger(label: "MQTTNIOTests")
120+
logger.logLevel = .trace
121+
return logger
122+
}()
123+
124+
@Test("Connect with TLS")
125+
func tlsConnect() async throws {
126+
try await MQTTConnection.withConnection(
127+
address: .hostname(Self.hostname, port: 8883),
128+
configuration: .init(
129+
useSSL: true,
130+
tlsConfiguration: self.getTLSConfiguration(),
131+
sniServerName: "soto.codes"
132+
),
133+
identifier: "tlsConnect",
134+
eventLoop: Self.eventLoopGroupSingleton.any(),
135+
logger: self.logger
136+
) { connection in
137+
try await connection.ping()
138+
}
133139
}
134-
}
135140

136-
#if canImport(Network)
137-
@Test("Connect with TLS from P12")
138-
func tlsConnectFromP12() async throws {
139-
try await MQTTConnection.withConnection(
140-
address: .hostname(Self.hostname, port: 8883),
141-
configuration: .init(
142-
useSSL: true,
143-
tlsConfiguration: .ts(
144-
.init(
145-
trustRoots: .der(Self.rootPath + "/mosquitto/certs/ca.der"),
146-
clientIdentity: .p12(filename: Self.rootPath + "/mosquitto/certs/client.p12", password: "MQTTNIOClientCertPassword")
147-
)
141+
@Test("Connect with WebSocket and TLS")
142+
func webSocketAndTLSConnect() async throws {
143+
try await MQTTConnection.withConnection(
144+
address: .hostname(Self.hostname, port: 8081),
145+
configuration: .init(
146+
timeout: .seconds(5),
147+
useSSL: true,
148+
tlsConfiguration: self.getTLSConfiguration(),
149+
sniServerName: "soto.codes",
150+
webSocketConfiguration: .init()
148151
),
149-
sniServerName: "soto.codes"
150-
),
151-
identifier: "tlsConnectFromP12",
152-
eventLoop: Self.eventLoopGroupSingleton.any(),
153-
logger: self.logger
154-
) { connection in
155-
try await connection.ping()
152+
identifier: "webSocketAndTLSConnect",
153+
eventLoop: Self.eventLoopGroupSingleton.any(),
154+
logger: self.logger
155+
) { connection in
156+
try await connection.ping()
157+
}
158+
}
159+
160+
#if canImport(Network)
161+
@Test("Connect with TLS from P12")
162+
func tlsConnectFromP12() async throws {
163+
try await MQTTConnection.withConnection(
164+
address: .hostname(Self.hostname, port: 8883),
165+
configuration: .init(
166+
useSSL: true,
167+
tlsConfiguration: .ts(
168+
.init(
169+
trustRoots: .der(Self.rootPath + "/mosquitto/certs/ca.der"),
170+
clientIdentity: .p12(filename: Self.rootPath + "/mosquitto/certs/client.p12", password: "MQTTNIOClientCertPassword")
171+
)
172+
),
173+
sniServerName: "soto.codes"
174+
),
175+
identifier: "tlsConnectFromP12",
176+
eventLoop: Self.eventLoopGroupSingleton.any(),
177+
logger: self.logger
178+
) { connection in
179+
try await connection.ping()
180+
}
181+
}
182+
#endif
183+
184+
var _tlsConfiguration: MQTTConnectionConfiguration.TLSConfigurationType {
185+
get throws {
186+
#if os(Linux)
187+
let rootCertificate = try NIOSSLCertificate.fromPEMFile(Self.rootPath + "/mosquitto/certs/ca.pem")
188+
let certificate = try NIOSSLCertificate.fromPEMFile(Self.rootPath + "/mosquitto/certs/client.pem")
189+
let privateKey = try NIOSSLPrivateKey(file: Self.rootPath + "/mosquitto/certs/client.key", format: .pem)
190+
var tlsConfiguration = TLSConfiguration.makeClientConfiguration()
191+
tlsConfiguration.trustRoots = .certificates(rootCertificate)
192+
tlsConfiguration.certificateChain = certificate.map { .certificate($0) }
193+
tlsConfiguration.privateKey = .privateKey(privateKey)
194+
return .niossl(tlsConfiguration)
195+
#else
196+
let caData = try Data(contentsOf: URL(fileURLWithPath: Self.rootPath + "/mosquitto/certs/ca.der"))
197+
let trustRootCertificates = SecCertificateCreateWithData(nil, caData as CFData).map { [$0] }
198+
let p12Data = try Data(contentsOf: URL(fileURLWithPath: Self.rootPath + "/mosquitto/certs/client.p12"))
199+
let options: [String: String] = [kSecImportExportPassphrase as String: "MQTTNIOClientCertPassword"]
200+
var rawItems: CFArray?
201+
guard SecPKCS12Import(p12Data as CFData, options as CFDictionary, &rawItems) == errSecSuccess else {
202+
throw MQTTError.wrongTLSConfig
203+
}
204+
let items = rawItems! as! [[String: Any]]
205+
let firstItem = items[0]
206+
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
207+
let tlsConfiguration = TSTLSConfiguration(
208+
trustRoots: trustRootCertificates,
209+
clientIdentity: identity
210+
)
211+
return .ts(tlsConfiguration)
212+
#endif
213+
}
214+
}
215+
216+
func getTLSConfiguration(
217+
withTrustRoots: Bool = true,
218+
withClientKey: Bool = true
219+
) throws -> MQTTConnectionConfiguration.TLSConfigurationType {
220+
switch try self._tlsConfiguration {
221+
#if os(macOS) || os(Linux)
222+
case .niossl(let config):
223+
var tlsConfig = TLSConfiguration.makeClientConfiguration()
224+
tlsConfig.trustRoots = withTrustRoots ? (config.trustRoots ?? .default) : .default
225+
tlsConfig.certificateChain = withClientKey ? config.certificateChain : []
226+
tlsConfig.privateKey = withClientKey ? config.privateKey : nil
227+
return .niossl(tlsConfig)
228+
#endif
229+
#if canImport(Network)
230+
case .ts(let config):
231+
return .ts(
232+
TSTLSConfiguration(
233+
trustRoots: withTrustRoots ? config.trustRoots : nil,
234+
clientIdentity: withClientKey ? config.clientIdentity : nil
235+
)
236+
)
237+
#endif
238+
}
156239
}
157240
}
158-
#endif
159241

160242
@Test("Connect with Unix Domain Socket")
161243
func unixDomainSocketConnect() async throws {
@@ -458,7 +540,7 @@ struct MQTTConnectionTests {
458540
try await withThrowingTaskGroup { group in
459541
group.addTask {
460542
try await confirmation("multiLevelWildcard", expectedCount: 2) { receivedMessage in
461-
try await connection.subscribe(to: [.init(topicFilter: "home/kitchen/#", qos: .atLeastOnce)]) { subscription in
543+
try await connection.subscribe(to: [.init(topicFilter: "multiLevel/home/kitchen/#", qos: .atLeastOnce)]) { subscription in
462544
var count = 0
463545
for try await message in subscription {
464546
var buffer = message.payload
@@ -474,9 +556,13 @@ struct MQTTConnectionTests {
474556

475557
group.addTask {
476558
try await Task.sleep(for: .seconds(1))
477-
try await connection.publish(to: "home/kitchen/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
478-
try await connection.publish(to: "home/livingroom/temperature", payload: ByteBuffer(string: "error"), qos: .atLeastOnce)
479-
try await connection.publish(to: "home/kitchen/humidity", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
559+
try await connection.publish(to: "multiLevel/home/kitchen/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
560+
try await connection.publish(
561+
to: "multiLevel/home/livingroom/temperature",
562+
payload: ByteBuffer(string: "error"),
563+
qos: .atLeastOnce
564+
)
565+
try await connection.publish(to: "multiLevel/home/kitchen/humidity", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
480566
}
481567

482568
try await group.waitForAll()
@@ -494,7 +580,8 @@ struct MQTTConnectionTests {
494580
try await withThrowingTaskGroup { group in
495581
group.addTask {
496582
try await confirmation("singleLevelWildcard", expectedCount: 2) { receivedMessage in
497-
try await connection.subscribe(to: [.init(topicFilter: "home/+/temperature", qos: .atLeastOnce)]) { subscription in
583+
try await connection.subscribe(to: [.init(topicFilter: "singleLevel/home/+/temperature", qos: .atLeastOnce)]) {
584+
subscription in
498585
var count = 0
499586
for try await message in subscription {
500587
var buffer = message.payload
@@ -510,9 +597,13 @@ struct MQTTConnectionTests {
510597

511598
group.addTask {
512599
try await Task.sleep(for: .seconds(1))
513-
try await connection.publish(to: "home/livingroom/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
514-
try await connection.publish(to: "home/garden/humidity", payload: ByteBuffer(string: "error"), qos: .atLeastOnce)
515-
try await connection.publish(to: "home/kitchen/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
600+
try await connection.publish(
601+
to: "singleLevel/home/livingroom/temperature",
602+
payload: ByteBuffer(string: "test"),
603+
qos: .atLeastOnce
604+
)
605+
try await connection.publish(to: "singleLevel/home/garden/humidity", payload: ByteBuffer(string: "error"), qos: .atLeastOnce)
606+
try await connection.publish(to: "singleLevel/home/kitchen/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
516607
}
517608

518609
try await group.waitForAll()
@@ -533,8 +624,8 @@ struct MQTTConnectionTests {
533624
group.addTask {
534625
try await confirmation("overlappingSubscriptions", expectedCount: 2) { receivedMessage in
535626
try await connection.subscribe(to: [
536-
.init(topicFilter: "home/+/temperature", qos: .atLeastOnce),
537-
.init(topicFilter: "home/kitchen/#", qos: .atLeastOnce),
627+
.init(topicFilter: "overlapping/home/+/temperature", qos: .atLeastOnce),
628+
.init(topicFilter: "overlapping/home/kitchen/#", qos: .atLeastOnce),
538629
]) { subscription in
539630
var count = 0
540631
for try await message in subscription {
@@ -551,7 +642,7 @@ struct MQTTConnectionTests {
551642

552643
group.addTask {
553644
try await Task.sleep(for: .seconds(1))
554-
try await connection.publish(to: "home/kitchen/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
645+
try await connection.publish(to: "overlapping/home/kitchen/temperature", payload: ByteBuffer(string: "test"), qos: .atLeastOnce)
555646
}
556647

557648
try await group.waitForAll()
@@ -578,63 +669,6 @@ struct MQTTConnectionTests {
578669
NIOTSEventLoopGroup.singleton
579670
#endif
580671
}
581-
582-
static var _tlsConfiguration: MQTTConnectionConfiguration.TLSConfigurationType {
583-
get throws {
584-
#if os(Linux)
585-
let rootCertificate = try NIOSSLCertificate.fromPEMFile(Self.rootPath + "/mosquitto/certs/ca.pem")
586-
let certificate = try NIOSSLCertificate.fromPEMFile(Self.rootPath + "/mosquitto/certs/client.pem")
587-
let privateKey = try NIOSSLPrivateKey(file: Self.rootPath + "/mosquitto/certs/client.key", format: .pem)
588-
var tlsConfiguration = TLSConfiguration.makeClientConfiguration()
589-
tlsConfiguration.trustRoots = .certificates(rootCertificate)
590-
tlsConfiguration.certificateChain = certificate.map { .certificate($0) }
591-
tlsConfiguration.privateKey = .privateKey(privateKey)
592-
return .niossl(tlsConfiguration)
593-
#else
594-
let caData = try Data(contentsOf: URL(fileURLWithPath: Self.rootPath + "/mosquitto/certs/ca.der"))
595-
let trustRootCertificates = SecCertificateCreateWithData(nil, caData as CFData).map { [$0] }
596-
let p12Data = try Data(contentsOf: URL(fileURLWithPath: Self.rootPath + "/mosquitto/certs/client.p12"))
597-
let options: [String: String] = [kSecImportExportPassphrase as String: "MQTTNIOClientCertPassword"]
598-
var rawItems: CFArray?
599-
guard SecPKCS12Import(p12Data as CFData, options as CFDictionary, &rawItems) == errSecSuccess else {
600-
throw MQTTError.wrongTLSConfig
601-
}
602-
let items = rawItems! as! [[String: Any]]
603-
let firstItem = items[0]
604-
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
605-
let tlsConfiguration = TSTLSConfiguration(
606-
trustRoots: trustRootCertificates,
607-
clientIdentity: identity
608-
)
609-
return .ts(tlsConfiguration)
610-
#endif
611-
}
612-
}
613-
614-
static func getTLSConfiguration(
615-
withTrustRoots: Bool = true,
616-
withClientKey: Bool = true
617-
) throws -> MQTTConnectionConfiguration.TLSConfigurationType {
618-
switch try Self._tlsConfiguration {
619-
#if os(macOS) || os(Linux)
620-
case .niossl(let config):
621-
var tlsConfig = TLSConfiguration.makeClientConfiguration()
622-
tlsConfig.trustRoots = withTrustRoots ? (config.trustRoots ?? .default) : .default
623-
tlsConfig.certificateChain = withClientKey ? config.certificateChain : []
624-
tlsConfig.privateKey = withClientKey ? config.privateKey : nil
625-
return .niossl(tlsConfig)
626-
#endif
627-
#if canImport(Network)
628-
case .ts(let config):
629-
return .ts(
630-
TSTLSConfiguration(
631-
trustRoots: withTrustRoots ? config.trustRoots : nil,
632-
clientIdentity: withClientKey ? config.clientIdentity : nil
633-
)
634-
)
635-
#endif
636-
}
637-
}
638672
}
639673

640674
extension MQTTError: Equatable {

0 commit comments

Comments
 (0)