From c54414c5e89d589af3caa5ca1df1eadd0bd9fa6e Mon Sep 17 00:00:00 2001 From: Piotr Piotrowski Date: Fri, 1 Dec 2023 14:13:10 +0100 Subject: [PATCH] Add nats-server wrapper for tests Signed-off-by: Piotr Piotrowski --- Sources/NatsSwift/NatsClient/NatsClient.swift | 2 +- .../Integration/ConnectionTests.swift | 24 +++- .../Integration/EventBusTests.swift | 29 +++-- .../Integration/LoopTests.swift | 13 +- .../Integration/PublishTests.swift | 18 ++- .../Integration/ServerInformationTest.swift | 17 ++- .../Integration/SubscribeTests.swift | 18 ++- Tests/NatsSwiftTests/NatsServer.swift | 122 ++++++++++++++++++ Tests/NatsSwiftTests/TestSettings.swift | 10 -- 9 files changed, 208 insertions(+), 45 deletions(-) create mode 100644 Tests/NatsSwiftTests/NatsServer.swift delete mode 100644 Tests/NatsSwiftTests/TestSettings.swift diff --git a/Sources/NatsSwift/NatsClient/NatsClient.swift b/Sources/NatsSwift/NatsClient/NatsClient.swift index 9afca2a..79825c6 100755 --- a/Sources/NatsSwift/NatsClient/NatsClient.swift +++ b/Sources/NatsSwift/NatsClient/NatsClient.swift @@ -21,7 +21,7 @@ public enum NatsEvent: String { case error = "error" case dropped = "dropped" case reconnecting = "reconnecting" - case informed = "informed" + case informed = "informed" static let all = [ connected, disconnected, response, error, dropped, reconnecting ] } diff --git a/Tests/NatsSwiftTests/Integration/ConnectionTests.swift b/Tests/NatsSwiftTests/Integration/ConnectionTests.swift index 740a653..64e6cec 100644 --- a/Tests/NatsSwiftTests/Integration/ConnectionTests.swift +++ b/Tests/NatsSwiftTests/Integration/ConnectionTests.swift @@ -14,19 +14,29 @@ class ConnectionTests: XCTestCase { ("testClientServerSetWhenConnected", testClientServerSetWhenConnected), ("testClientBadConnection", testClientBadConnection) ] + + var natsServer = NatsServer() + + override func tearDown() { + super.tearDown() + natsServer.stop() + } func testClientConnection() { + natsServer.start() - let client = NatsClient(TestSettings.natsUrl) + let client = NatsClient(natsServer.clientURL) try? client.connect() + XCTAssertTrue(client.state == .connected, "Client did not connect") } func testClientServerSetWhenConnected() { + natsServer.start() - let client = NatsClient(TestSettings.natsUrl) + let client = NatsClient(natsServer.clientURL) try? client.connect() guard let _ = client.server else { XCTFail("Client did not connect to server correctly"); return } @@ -34,6 +44,7 @@ class ConnectionTests: XCTestCase { } func testClientBadConnection() { + natsServer.start() let client = NatsClient("notnats.net") @@ -43,8 +54,9 @@ class ConnectionTests: XCTestCase { } func testClientConnectionLogging() { + natsServer.start() - let client = NatsClient(TestSettings.natsUrl) + let client = NatsClient(natsServer.clientURL) client.config.loglevel = .trace try? client.connect() XCTAssertTrue(client.state == .connected, "Client did not connect") @@ -52,7 +64,8 @@ class ConnectionTests: XCTestCase { } func testClientConnectDisconnect() { - let client = NatsClient(TestSettings.natsUrl) + natsServer.start() + let client = NatsClient(natsServer.clientURL) client.config.loglevel = .trace try? client.connect() @@ -77,7 +90,8 @@ class ConnectionTests: XCTestCase { } func testClientReconnectWhenAlreadyConnected() { - let client = NatsClient(TestSettings.natsUrl) + natsServer.start() + let client = NatsClient(natsServer.clientURL) client.config.loglevel = .trace try? client.connect() diff --git a/Tests/NatsSwiftTests/Integration/EventBusTests.swift b/Tests/NatsSwiftTests/Integration/EventBusTests.swift index b8baa29..7a21e55 100644 --- a/Tests/NatsSwiftTests/Integration/EventBusTests.swift +++ b/Tests/NatsSwiftTests/Integration/EventBusTests.swift @@ -8,10 +8,17 @@ import XCTest class EventBusTests: XCTestCase { - func testClientConnectedEvent() { + var natsServer = NatsServer() - let client = NatsClient(TestSettings.natsUrl) + override func tearDown() { + super.tearDown() + natsServer.stop() + } + func testClientConnectedEvent() { + natsServer.start() + let client = NatsClient(natsServer.clientURL) + var isConnected = false client.on(.connected) { _ in isConnected = true @@ -26,8 +33,9 @@ class EventBusTests: XCTestCase { } func testClientDisconnectedEvent() { + natsServer.start() + let client = NatsClient(natsServer.clientURL) - let client = NatsClient(TestSettings.natsUrl) try? client.connect() var isConnected = true @@ -42,8 +50,9 @@ class EventBusTests: XCTestCase { } func testClientEventOff() { + natsServer.start() + let client = NatsClient(natsServer.clientURL) - let client = NatsClient(TestSettings.natsUrl) try? client.connect() var isConnected = true @@ -60,9 +69,9 @@ class EventBusTests: XCTestCase { } func testClientEventMultiple() { - - let client = NatsClient(TestSettings.natsUrl) - + natsServer.start() + let client = NatsClient(natsServer.clientURL) + var counter = 0 client.on([.connected, .disconnected]) { _ in counter += 1 @@ -75,9 +84,9 @@ class EventBusTests: XCTestCase { } func testClientEventAutoOff() { - - let client = NatsClient(TestSettings.natsUrl) - + natsServer.start() + let client = NatsClient(natsServer.clientURL) + var counter = 0 client.on([.connected, .disconnected], autoOff: true) { _ in counter += 1 diff --git a/Tests/NatsSwiftTests/Integration/LoopTests.swift b/Tests/NatsSwiftTests/Integration/LoopTests.swift index fbdd05d..48518e0 100644 --- a/Tests/NatsSwiftTests/Integration/LoopTests.swift +++ b/Tests/NatsSwiftTests/Integration/LoopTests.swift @@ -7,12 +7,19 @@ import XCTest @testable import NatsSwift class LoopTests: XCTestCase { + var natsServer = NatsServer() - func testReadSubscriptionInLoop() { + override func tearDown() { + super.tearDown() + natsServer.stop() + } - let clientPublish = NatsClient(TestSettings.natsUrl) - let clientSubscribe = NatsClient(TestSettings.natsUrl) + func testReadSubscriptionInLoop() { + natsServer.start() + let clientPublish = NatsClient(natsServer.clientURL) + let clientSubscribe = NatsClient(natsServer.clientURL) + try? clientPublish.connect() try? clientSubscribe.connect() diff --git a/Tests/NatsSwiftTests/Integration/PublishTests.swift b/Tests/NatsSwiftTests/Integration/PublishTests.swift index 75960cb..01e2be5 100644 --- a/Tests/NatsSwiftTests/Integration/PublishTests.swift +++ b/Tests/NatsSwiftTests/Integration/PublishTests.swift @@ -8,11 +8,17 @@ import XCTest @testable import NatsSwift class PublishTests: XCTestCase { + var natsServer = NatsServer() - func testClientPublish() { - - let client = NatsClient(TestSettings.natsUrl) + override func tearDown() { + super.tearDown() + natsServer.stop() + } + func testClientPublish() { + natsServer.start() + let client = NatsClient(natsServer.clientURL) + try? client.connect() var responses = [String]() @@ -35,9 +41,9 @@ class PublishTests: XCTestCase { } func testClientPublishSync() { - - let client = NatsClient(TestSettings.natsUrl) - + natsServer.start() + let client = NatsClient(natsServer.clientURL) + try? client.connect() var responses = [String]() diff --git a/Tests/NatsSwiftTests/Integration/ServerInformationTest.swift b/Tests/NatsSwiftTests/Integration/ServerInformationTest.swift index e115ad4..c07dfc7 100644 --- a/Tests/NatsSwiftTests/Integration/ServerInformationTest.swift +++ b/Tests/NatsSwiftTests/Integration/ServerInformationTest.swift @@ -8,10 +8,17 @@ import XCTest @testable import NatsSwift class ServerInformationTest: XCTestCase { + var natsServer = NatsServer() + + override func tearDown() { + super.tearDown() + natsServer.stop() + } func testServerInformation() { + natsServer.start() + let client = NatsClient(natsServer.clientURL) - let client = NatsClient(TestSettings.natsUrl) XCTAssertNil(client.serverInformation) try? client.connect() @@ -22,11 +29,13 @@ class ServerInformationTest: XCTestCase { } func testServerInformationPropertiesSet() { - let client = NatsClient(TestSettings.natsUrl) + natsServer.start() + let client = NatsClient(natsServer.clientURL) + try? client.connect() XCTAssertEqual(client.serverInformation?.host, "0.0.0.0") - XCTAssertEqual(client.serverInformation?.port, 4222) - + XCTAssertEqual(client.serverInformation?.port, natsServer.port!) + XCTAssert(client.serverInformation?.serverName.count ?? 0 > 0) XCTAssert(client.serverInformation?.serverId.count ?? 0 > 0) XCTAssert(client.serverInformation?.version.count ?? 0 >= 5) diff --git a/Tests/NatsSwiftTests/Integration/SubscribeTests.swift b/Tests/NatsSwiftTests/Integration/SubscribeTests.swift index 589b14c..1498bf5 100644 --- a/Tests/NatsSwiftTests/Integration/SubscribeTests.swift +++ b/Tests/NatsSwiftTests/Integration/SubscribeTests.swift @@ -7,11 +7,17 @@ import XCTest @testable import NatsSwift class SubscribeTests: XCTestCase { + var natsServer = NatsServer() - func testClientSubscription() { - - let client = NatsClient(TestSettings.natsUrl) + override func tearDown() { + super.tearDown() + natsServer.stop() + } + func testClientSubscription() { + natsServer.start() + let client = NatsClient(natsServer.clientURL) + try? client.connect() var subscribed = false @@ -29,9 +35,9 @@ class SubscribeTests: XCTestCase { } func testClientSubscriptionSync() { - - let client = NatsClient(TestSettings.natsUrl) - + natsServer.start() + let client = NatsClient(natsServer.clientURL) + try? client.connect() let handler: (NatsMessage) -> Void = { message in diff --git a/Tests/NatsSwiftTests/NatsServer.swift b/Tests/NatsSwiftTests/NatsServer.swift new file mode 100644 index 0000000..7e82861 --- /dev/null +++ b/Tests/NatsSwiftTests/NatsServer.swift @@ -0,0 +1,122 @@ +// +// NatsServer.swift +// NatsSwift +// + +import Foundation +import XCTest + +class NatsServer { + var port: UInt? { return natsServerPort } + var clientURL: String { + let scheme = tlsEnabled ? "tls://" : "nats://" + if let natsServerPort { + return "\(scheme)localhost:\(natsServerPort)" + } else { + return "" + } + } + + private var process: Process? + private var natsServerPort: UInt? + private var tlsEnabled = false + + // TODO: When implementing JetStream, creating and deleting store dir should be handled in start/stop methods + func start(port: Int = -1, cfg: String? = nil, file: StaticString = #file, line: UInt = #line) { + XCTAssertNil(self.process, "nats-server is already running on port \(port)", file: file, line: line) + let process = Process() + let pipe = Pipe() + + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = ["nats-server", "-p", "\(port)"] + if let cfg { + process.arguments?.append(contentsOf: ["-c", cfg]) + } + process.standardError = pipe + process.standardOutput = pipe + + let outputHandle = pipe.fileHandleForReading + let semaphore = DispatchSemaphore(value: 0) + var lineCount = 0 + let maxLines = 100 + var serverPort: UInt? + var serverError: String? + + outputHandle.readabilityHandler = { fileHandle in + let data = fileHandle.availableData + if let line = String(data: data, encoding: .utf8) { + lineCount += 1 + + serverError = self.extracErrorMessage(from: line) + serverPort = self.extractPort(from: line) + if !self.tlsEnabled && self.isTLS(from: line) { + self.tlsEnabled = true + } + if serverPort != nil || serverError != nil || lineCount >= maxLines { + serverError = serverError + semaphore.signal() + outputHandle.readabilityHandler = nil + } + } + } + + XCTAssertNoThrow(try process.run(), "error starting nats-server on port \(port)", file: file, line: line) + + let result = semaphore.wait(timeout: .now() + .seconds(10)) + + XCTAssertFalse(result == .timedOut, "timeout waiting for server to be ready", file: file, line: line) + XCTAssertNil(serverError, "error starting nats-server: \(serverError!)", file: file, line: line) + + self.process = process + self.natsServerPort = serverPort + } + + func stop(file: StaticString = #file, line: UInt = #line) { + XCTAssertNotNil(self.process, "nats-server is not running", file: file, line: line) + + self.process?.terminate() + process?.waitUntilExit() + process = nil + natsServerPort = port + tlsEnabled = false + } + + private func extractPort(from string: String) -> UInt? { + let pattern = "Listening for client connections on [^:]+:(\\d+)" + + let regex = try! NSRegularExpression(pattern: pattern) + let nsrange = NSRange(string.startIndex.. String? { + if logLine.contains("nats-server: No such file or directory") { + return "nats-server not found - make sure nats-server can be found in PATH" + } + guard let range = logLine.range(of: "[FTL]") else { + return nil + } + + let messageStartIndex = range.upperBound + let message = logLine[messageStartIndex...] + + return String(message).trimmingCharacters(in: .whitespaces) + } + + private func isTLS(from logLine: String) -> Bool { + return logLine.contains("TLS required for client connections") + } + + deinit{ + stop() + } +} diff --git a/Tests/NatsSwiftTests/TestSettings.swift b/Tests/NatsSwiftTests/TestSettings.swift deleted file mode 100644 index f1a75ac..0000000 --- a/Tests/NatsSwiftTests/TestSettings.swift +++ /dev/null @@ -1,10 +0,0 @@ -// -// TestSettings.swift -// NatsSwiftTest -// - - -struct TestSettings { - static let natsUrl = "nats://localhost:4222" - static let natsSslUrl = "tls://localhost:4222" -}