diff --git a/Sources/Vapor/Application.swift b/Sources/Vapor/Application.swift index 36e51da519..2ca96ce6cd 100644 --- a/Sources/Vapor/Application.swift +++ b/Sources/Vapor/Application.swift @@ -155,7 +155,7 @@ public final class Application: Sendable { self.servers.use(.http) self.clients.initialize() self.clients.use(.http) - self.commands.use(self.servers.command, as: "serve", isDefault: true) + self.asyncCommands.use(self.servers.command, as: "serve", isDefault: true) self.asyncCommands.use(RoutesCommand(), as: "routes") } diff --git a/Sources/Vapor/Commands/ServeCommand.swift b/Sources/Vapor/Commands/ServeCommand.swift index 2947bfe635..ea787ef14e 100644 --- a/Sources/Vapor/Commands/ServeCommand.swift +++ b/Sources/Vapor/Commands/ServeCommand.swift @@ -7,7 +7,7 @@ import NIOConcurrencyHelpers /// $ swift run Run serve /// Server starting on http://localhost:8080 /// -public final class ServeCommand: Command, Sendable { +public final class ServeCommand: AsyncCommand, Sendable { public struct Signature: CommandSignature, Sendable { @Option(name: "hostname", short: "H", help: "Set the hostname the server will run on.") var hostname: String? @@ -30,10 +30,10 @@ public final class ServeCommand: Command, Sendable { case incompatibleFlags } - /// See `Command`. + // See `AsyncCommand`. public let signature = Signature() - /// See `Command`. + // See `AsyncCommand`. public var help: String { return "Begins serving the app over HTTP." } @@ -53,23 +53,23 @@ public final class ServeCommand: Command, Sendable { self.box = .init(box) } - /// See `Command`. - public func run(using context: CommandContext, signature: Signature) throws { + // See `AsyncCommand`. + public func run(using context: CommandContext, signature: Signature) async throws { switch (signature.hostname, signature.port, signature.bind, signature.socketPath) { case (.none, .none, .none, .none): // use defaults - try context.application.server.start(address: nil) + try await context.application.server.start(address: nil) case (.none, .none, .none, .some(let socketPath)): // unix socket - try context.application.server.start(address: .unixDomainSocket(path: socketPath)) + try await context.application.server.start(address: .unixDomainSocket(path: socketPath)) case (.none, .none, .some(let address), .none): // bind ("hostname:port") let hostname = address.split(separator: ":").first.flatMap(String.init) let port = address.split(separator: ":").last.flatMap(String.init).flatMap(Int.init) - try context.application.server.start(address: .hostname(hostname, port: port)) + try await context.application.server.start(address: .hostname(hostname, port: port)) case (let hostname, let port, .none, .none): // hostname / port - try context.application.server.start(address: .hostname(hostname, port: port)) + try await context.application.server.start(address: .hostname(hostname, port: port)) default: throw Error.incompatibleFlags } diff --git a/Sources/Vapor/HTTP/Server/HTTPServer.swift b/Sources/Vapor/HTTP/Server/HTTPServer.swift index 1025f9ab56..97808f82c7 100644 --- a/Sources/Vapor/HTTP/Server/HTTPServer.swift +++ b/Sources/Vapor/HTTP/Server/HTTPServer.swift @@ -382,17 +382,8 @@ public final class HTTPServer: Server, Sendable { configuration.address = address! } - /// Print starting message. - let scheme = configuration.tlsConfiguration == nil ? "http" : "https" - let addressDescription: String - switch configuration.address { - case .hostname(let hostname, let port): - addressDescription = "\(scheme)://\(hostname ?? configuration.hostname):\(port ?? configuration.port)" - case .unixDomainSocket(let socketPath): - addressDescription = "\(scheme)+unix: \(socketPath)" - } - - self.configuration.logger.notice("Server starting on \(addressDescription)") + /// Log starting message for debugging before attempting to start the server. + configuration.logger.debug("Server starting on \(configuration.addressDescription)") /// Start the actual `HTTPServer`. let serverConnection = try await HTTPServerConnection.start( @@ -407,6 +398,19 @@ public final class HTTPServer: Server, Sendable { precondition($0 == nil, "You can't start the server connection twice") $0 = serverConnection } + + /// Overwrite configuration with actual address, if applicable. + /// They may differ from the provided configuation if port 0 was provided, for example. + if let localAddress = self.localAddress { + if let hostname = localAddress.hostname, let port = localAddress.port { + configuration.address = .hostname(hostname, port: port) + } else if let pathname = localAddress.pathname { + configuration.address = .unixDomainSocket(path: pathname) + } + } + + /// Log started message with the actual configuration. + configuration.logger.notice("Server started on \(configuration.addressDescription)") self.configuration = configuration self.didStart.withLockedValue { $0 = true } diff --git a/Sources/Vapor/Server/Server.swift b/Sources/Vapor/Server/Server.swift index ef4372dba7..ca52287f30 100644 --- a/Sources/Vapor/Server/Server.swift +++ b/Sources/Vapor/Server/Server.swift @@ -7,8 +7,14 @@ public protocol Server: Sendable { /// Start the server with the specified address. /// - Parameters: /// - address: The address to start the server with. + @available(*, noasync, message: "Use the async start() method instead.") func start(address: BindAddress?) throws + /// Start the server with the specified address. + /// - Parameters: + /// - address: The address to start the server with. + func start(address: BindAddress?) async throws + /// Start the server with the specified hostname and port, if provided. If left blank, the server will be started with its default configuration. /// - Deprecated: Please use `start(address: .hostname(hostname, port: port))` instead. /// - Parameters: @@ -17,7 +23,12 @@ public protocol Server: Sendable { @available(*, deprecated, renamed: "start(address:)", message: "Please use `start(address: .hostname(hostname, port: port))` instead") func start(hostname: String?, port: Int?) throws + /// Shut the server down. + @available(*, noasync, message: "Use the async start() method instead.") func shutdown() + + /// Shut the server down. + func shutdown() async } public enum BindAddress: Equatable, Sendable { @@ -54,6 +65,27 @@ extension Server { public func start(hostname: String?, port: Int?) throws { try self.start(address: .hostname(hostname, port: port)) } + + /// A default implementation for those servers that haven't migrated yet + @available(*, deprecated, message: "Implement an async version of this yourself") + public func start(address: BindAddress?) async throws { + try self.syncStart(address: address) + } + + /// A default implementation for those servers that haven't migrated yet + @available(*, deprecated, message: "Implement an async version of this yourself") + public func shutdown() async { + self.syncShutdown() + } + + // Trick the compiler + private func syncStart(address: BindAddress?) throws { + try self.start(address: address) + } + + private func syncShutdown() { + self.shutdown() + } } /// Errors that may be thrown when starting a server diff --git a/Sources/XCTVapor/XCTApplication.swift b/Sources/XCTVapor/XCTApplication.swift index 32103ccf40..13383e203c 100644 --- a/Sources/XCTVapor/XCTApplication.swift +++ b/Sources/XCTVapor/XCTApplication.swift @@ -89,41 +89,47 @@ extension Application { } func performTest(request: XCTHTTPRequest) async throws -> XCTHTTPResponse { - try app.server.start(address: .hostname(self.hostname, port: self.port)) - defer { app.server.shutdown() } - + try await app.server.start(address: .hostname(self.hostname, port: self.port)) let client = HTTPClient(eventLoopGroup: MultiThreadedEventLoopGroup.singleton) - defer { try! client.syncShutdown() } - var path = request.url.path - path = path.hasPrefix("/") ? path : "/\(path)" - - let actualPort: Int - if self.port == 0 { - guard let portAllocated = app.http.server.shared.localAddress?.port else { - throw Abort(.internalServerError, reason: "Failed to get port from local address") + do { + var path = request.url.path + path = path.hasPrefix("/") ? path : "/\(path)" + + let actualPort: Int + + if self.port == 0 { + guard let portAllocated = app.http.server.shared.localAddress?.port else { + throw Abort(.internalServerError, reason: "Failed to get port from local address") + } + actualPort = portAllocated + } else { + actualPort = self.port } - actualPort = portAllocated - } else { - actualPort = self.port - } - - var url = "http://\(self.hostname):\(actualPort)\(path)" - if let query = request.url.query { - url += "?\(query)" + + var url = "http://\(self.hostname):\(actualPort)\(path)" + if let query = request.url.query { + url += "?\(query)" + } + var clientRequest = HTTPClientRequest(url: url) + clientRequest.method = request.method + clientRequest.headers = request.headers + clientRequest.body = .bytes(request.body) + let response = try await client.execute(clientRequest, timeout: .seconds(30)) + // Collect up to 1MB + let responseBody = try await response.body.collect(upTo: 1024 * 1024) + try await client.shutdown() + await app.server.shutdown() + return XCTHTTPResponse( + status: response.status, + headers: response.headers, + body: responseBody + ) + } catch { + try? await client.shutdown() + await app.server.shutdown() + throw error } - var clientRequest = try HTTPClient.Request( - url: url, - method: request.method, - headers: request.headers - ) - clientRequest.body = .byteBuffer(request.body) - let response = try await client.execute(request: clientRequest).get() - return XCTHTTPResponse( - status: response.status, - headers: response.headers, - body: response.body ?? ByteBufferAllocator().buffer(capacity: 0) - ) } } diff --git a/Tests/VaporTests/AsyncAuthTests.swift b/Tests/VaporTests/AsyncAuthTests.swift index c8da07a268..4ff0609274 100644 --- a/Tests/VaporTests/AsyncAuthTests.swift +++ b/Tests/VaporTests/AsyncAuthTests.swift @@ -3,6 +3,17 @@ import Vapor import XCTest final class AsyncAuthenticationTests: XCTestCase { + + var app: Application! + + override func setUp() async throws { + app = try await Application.make(.testing) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testBearerAuthenticator() async throws { struct Test: Authenticatable { static func authenticator() -> AsyncAuthenticator { @@ -21,9 +32,6 @@ final class AsyncAuthenticationTests: XCTestCase { } } - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.routes.grouped([ Test.authenticator(), Test.guardMiddleware() ]).get("test") { req -> String in @@ -63,9 +71,6 @@ final class AsyncAuthenticationTests: XCTestCase { } } - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.routes.grouped([ Test.authenticator(), Test.guardMiddleware() ]).get("test") { req -> String in @@ -103,10 +108,7 @@ final class AsyncAuthenticationTests: XCTestCase { } } } - - let app = try await Application.make(.testing) - defer { app.shutdown() } - + app.routes.grouped([ Test.authenticator(), Test.guardMiddleware() ]).get("test") { req -> String in @@ -142,9 +144,6 @@ final class AsyncAuthenticationTests: XCTestCase { } } - let app = try await Application.make(.testing) - defer { app.shutdown() } - let redirectMiddleware = Test.redirectMiddleware { req -> String in return "/redirect?orig=\(req.url.path)" } @@ -199,9 +198,6 @@ final class AsyncAuthenticationTests: XCTestCase { } } - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.routes.grouped([ app.sessions.middleware, Test.sessionAuthenticator(), diff --git a/Tests/VaporTests/AsyncCacheTests.swift b/Tests/VaporTests/AsyncCacheTests.swift index 4d59ef3f92..b558a76402 100644 --- a/Tests/VaporTests/AsyncCacheTests.swift +++ b/Tests/VaporTests/AsyncCacheTests.swift @@ -4,10 +4,17 @@ import Vapor import NIOCore final class AsyncCacheTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + app = try await Application.make(.testing) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testInMemoryCache() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let value1 = try await app.cache.get("foo", as: String.self) XCTAssertNil(value1) try await app.cache.set("foo", to: "bar") @@ -33,8 +40,6 @@ final class AsyncCacheTests: XCTestCase { } func testCustomCache() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } app.caches.use(.foo) try await app.cache.set("1", to: "2") let value = try await app.cache.get("foo", as: String.self) diff --git a/Tests/VaporTests/AsyncClientTests.swift b/Tests/VaporTests/AsyncClientTests.swift index e309e60b79..827e90b7de 100644 --- a/Tests/VaporTests/AsyncClientTests.swift +++ b/Tests/VaporTests/AsyncClientTests.swift @@ -10,6 +10,7 @@ final class AsyncClientTests: XCTestCase { var remoteAppPort: Int! var remoteApp: Application! + var app: Application! override func setUp() async throws { remoteApp = try await Application.make(.testing) @@ -52,6 +53,8 @@ final class AsyncClientTests: XCTestCase { } self.remoteAppPort = port + + app = try await Application.make(.testing) } override func tearDown() async throws { @@ -59,17 +62,13 @@ final class AsyncClientTests: XCTestCase { } func testClientConfigurationChange() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.client.configuration.redirectConfiguration = .disallow app.get("redirect") { $0.redirect(to: "foo") } - try app.server.start(address: .hostname("localhost", port: 0)) - defer { app.server.shutdown() } + try await app.server.start(address: .hostname("localhost", port: 0)) guard let port = app.http.server.shared.localAddress?.port else { XCTFail("Failed to get port for app") @@ -79,20 +78,18 @@ final class AsyncClientTests: XCTestCase { let res = try await app.client.get("http://localhost:\(port)/redirect") XCTAssertEqual(res.status, .seeOther) + + await app.server.shutdown() } func testClientConfigurationCantBeChangedAfterClientHasBeenUsed() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.client.configuration.redirectConfiguration = .disallow app.get("redirect") { $0.redirect(to: "foo") } - try app.server.start(address: .hostname("localhost", port: 0)) - defer { app.server.shutdown() } + try await app.server.start(address: .hostname("localhost", port: 0)) guard let port = app.http.server.shared.localAddress?.port else { XCTFail("Failed to get port for app") @@ -104,12 +101,11 @@ final class AsyncClientTests: XCTestCase { app.http.client.configuration.redirectConfiguration = .follow(max: 1, allowCycles: false) let res = try await app.client.get("http://localhost:\(port)/redirect") XCTAssertEqual(res.status, .seeOther) + + await app.server.shutdown() } func testClientResponseCodable() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let res = try await app.client.get("http://localhost:\(remoteAppPort!)/json") let encoded = try JSONEncoder().encode(res) @@ -119,8 +115,6 @@ final class AsyncClientTests: XCTestCase { } func testClientBeforeSend() async throws { - let app = try await Application.make() - defer { app.shutdown() } try app.boot() let res = try await app.client.post("http://localhost:\(remoteAppPort!)/anything") { req in @@ -133,9 +127,7 @@ final class AsyncClientTests: XCTestCase { } func testBoilerplateClient() async throws { - let app = try await Application.make(.testing) app.http.server.configuration.port = 0 - defer { app.shutdown() } let remotePort = self.remoteAppPort! app.get("foo") { req async throws -> String in @@ -168,9 +160,6 @@ final class AsyncClientTests: XCTestCase { } func testCustomClient() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.clients.use(.custom) _ = try await app.client.get("https://vapor.codes") @@ -179,8 +168,6 @@ final class AsyncClientTests: XCTestCase { } func testClientLogging() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } let logs = TestLogHandler() app.logger = logs.logger diff --git a/Tests/VaporTests/AsyncCommandsTests.swift b/Tests/VaporTests/AsyncCommandsTests.swift index 5d6c8fb24b..cfdcde4b29 100644 --- a/Tests/VaporTests/AsyncCommandsTests.swift +++ b/Tests/VaporTests/AsyncCommandsTests.swift @@ -2,10 +2,17 @@ import XCTest import Vapor final class AsyncCommandsTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + app = try await Application.make(.testing) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testAsyncCommands() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.asyncCommands.use(FooCommand(), as: "foo") app.environment.arguments = ["vapor", "foo", "bar"] diff --git a/Tests/VaporTests/AsyncFileTests.swift b/Tests/VaporTests/AsyncFileTests.swift index 588ad6f629..c3c99b8694 100644 --- a/Tests/VaporTests/AsyncFileTests.swift +++ b/Tests/VaporTests/AsyncFileTests.swift @@ -7,10 +7,17 @@ import _NIOFileSystem import Crypto final class AsyncFileTests: XCTestCase, @unchecked Sendable { + var app: Application! + + override func setUp() async throws { + app = try await Application.make(.testing) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testStreamFile() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in return try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in do { @@ -29,9 +36,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileConnectionClose() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in return try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) } @@ -46,9 +50,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileNull() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in var tmpPath: String repeat { @@ -74,9 +75,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testAdvancedETagHeaders() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in return try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in do { @@ -96,9 +94,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testSimpleETagHeaders() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in return try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: false) { result in do { @@ -120,9 +115,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeaderTail() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in return try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in do { @@ -151,9 +143,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeaderStart() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in return try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in do { @@ -182,9 +171,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeadersWithin() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in XCTAssertNoThrow(try result.get()) @@ -209,9 +195,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeadersOnlyFirstByte() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req in try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in XCTAssertNoThrow(try result.get()) @@ -232,9 +215,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeadersWithinFail() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in XCTAssertNoThrow(try result.get()) @@ -254,9 +234,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeadersStartFail() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in XCTAssertNoThrow(try result.get()) @@ -276,9 +253,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testStreamFileContentHeadersTailFail() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) { result in XCTAssertNoThrow(try result.get()) @@ -302,9 +276,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { let path = "/tmp/fileio_write.txt" do { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let request = Request(application: app, on: app.eventLoopGroup.next()) try await request.fileio.writeFile(ByteBuffer(string: data), at: path) @@ -319,9 +290,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { // https://github.com/vapor/vapor/security/advisories/GHSA-vj2m-9f5j-mpr5 func testInvalidRangeHeaderDoesNotCrash() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> Response in try await req.fileio.asyncStreamFile(at: #file, advancedETagComparison: true) } @@ -369,9 +337,6 @@ final class AsyncFileTests: XCTestCase, @unchecked Sendable { } func testAsyncFileRead() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let request = Request(application: app, on: app.eventLoopGroup.next()) let path = "/" + #filePath.split(separator: "/").dropLast().joined(separator: "/") + "/Utilities/long-test-file.txt" diff --git a/Tests/VaporTests/AsyncMiddlewareTests.swift b/Tests/VaporTests/AsyncMiddlewareTests.swift index dcbde9870c..830c84b836 100644 --- a/Tests/VaporTests/AsyncMiddlewareTests.swift +++ b/Tests/VaporTests/AsyncMiddlewareTests.swift @@ -3,6 +3,16 @@ import XCTest import Vapor final class AsyncMiddlewareTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + app = try await Application.make(.testing) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + actor OrderStore { var order: [String] = [] @@ -29,9 +39,6 @@ final class AsyncMiddlewareTests: XCTestCase { } func testMiddlewareOrder() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let store = OrderStore() app.grouped( OrderMiddleware("a", store: store), OrderMiddleware("b", store: store), OrderMiddleware("c", store: store) @@ -48,9 +55,6 @@ final class AsyncMiddlewareTests: XCTestCase { } func testPrependingMiddleware() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let store = OrderStore() app.middleware.use(OrderMiddleware("b", store: store)) app.middleware.use(OrderMiddleware("c", store: store)) @@ -70,9 +74,6 @@ final class AsyncMiddlewareTests: XCTestCase { } func testCORSMiddlewareVariedByRequestOrigin() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.grouped( CORSMiddleware(configuration: .init(allowedOrigin: .originBased, allowedMethods: [.GET], allowedHeaders: [.origin])) ).get("order") { req -> String in @@ -89,9 +90,6 @@ final class AsyncMiddlewareTests: XCTestCase { } func testCORSMiddlewareNoVariationByRequestOriginAllowed() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.grouped( CORSMiddleware(configuration: .init(allowedOrigin: .none, allowedMethods: [.GET], allowedHeaders: [])) ).get("order") { req -> String in diff --git a/Tests/VaporTests/AsyncPasswordTests.swift b/Tests/VaporTests/AsyncPasswordTests.swift index e7009765ad..fb98572892 100644 --- a/Tests/VaporTests/AsyncPasswordTests.swift +++ b/Tests/VaporTests/AsyncPasswordTests.swift @@ -3,42 +3,34 @@ import XCTest import Vapor final class AsyncPasswordTests: XCTestCase { - func testAsyncBCryptRequestPassword() async throws { + var app: Application! + + override func setUp() async throws { let test = Environment(name: "testing", arguments: ["vapor"]) - let app = try await Application.make(test) - defer { app.shutdown() } - + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + + func testAsyncBCryptRequestPassword() async throws { try await assertAsyncRequestPasswordVerifies(.bcrypt, on: app) } func testAsyncPlaintextRequestPassword() async throws { - let test = Environment(name: "testing", arguments: ["vapor"]) - let app = try await Application.make(test) - defer { app.shutdown() } - try await assertAsyncRequestPasswordVerifies(.plaintext, on: app) } func testAsyncBCryptApplicationPassword() async throws { - let test = Environment(name: "testing", arguments: ["vapor"]) - let app = try await Application.make(test) - defer { app.shutdown() } - try await assertAsyncApplicationPasswordVerifies(.bcrypt, on: app) } func testAsyncPlaintextApplicationPassword() async throws { - let test = Environment(name: "testing", arguments: ["vapor"]) - let app = try await Application.make(test) - defer { app.shutdown() } - try await assertAsyncApplicationPasswordVerifies(.plaintext, on: app) } func testAsyncUsesProvider() async throws { - let test = Environment(name: "testing", arguments: ["vapor"]) - let app = try await Application.make(test) - defer { app.shutdown() } app.passwords.use(.plaintext) let hash = try await app.password.async( on: app.threadPool, @@ -48,9 +40,6 @@ final class AsyncPasswordTests: XCTestCase { } func testAsyncApplicationDefault() async throws { - let test = Environment(name: "testing", arguments: ["vapor"]) - let app = try await Application.make(test) - defer { app.shutdown() } app.passwords.use(.plaintext) let hash = try await app.password.async.hash("vapor") XCTAssertEqual(hash, "vapor") diff --git a/Tests/VaporTests/AsyncRouteTests.swift b/Tests/VaporTests/AsyncRouteTests.swift index ca9abad7de..da8b0b624f 100644 --- a/Tests/VaporTests/AsyncRouteTests.swift +++ b/Tests/VaporTests/AsyncRouteTests.swift @@ -4,6 +4,16 @@ import Vapor import NIOHTTP1 final class AsyncRouteTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + app = try await Application.make(.testing) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testEnumResponse() async throws { enum IntOrString: AsyncResponseEncodable { case int(Int) @@ -19,9 +29,6 @@ final class AsyncRouteTests: XCTestCase { } } - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.routes.get("foo") { req -> IntOrString in if try req.query.get(String.self, at: "number") == "true" { return .int(42) @@ -44,9 +51,6 @@ final class AsyncRouteTests: XCTestCase { var name: String } - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.post("users") { req async throws -> Response in return try await req.content .decode(User.self) @@ -65,9 +69,6 @@ final class AsyncRouteTests: XCTestCase { } func testWebsocketUpgrade() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let testMarkerHeaderKey = "TestMarker" let testMarkerHeaderValue = "addedInShouldUpgrade" diff --git a/Tests/VaporTests/AsyncSessionTests.swift b/Tests/VaporTests/AsyncSessionTests.swift index c615f0cdda..c754deaca1 100644 --- a/Tests/VaporTests/AsyncSessionTests.swift +++ b/Tests/VaporTests/AsyncSessionTests.swift @@ -4,6 +4,17 @@ import Vapor import NIOHTTP1 final class AsyncSessionTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + let test = Environment(name: "testing", arguments: ["vapor"]) + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testSessionDestroy() async throws { actor MockKeyedCache: AsyncSessionDriver { var ops: [String] = [] @@ -40,9 +51,6 @@ final class AsyncSessionTests: XCTestCase { var cookie: HTTPCookies.Value? - let app = try await Application.make() - defer { app.shutdown() } - let cache = MockKeyedCache() app.sessions.use { _ in cache } let sessions = app.routes.grouped(app.sessions.middleware) @@ -83,9 +91,6 @@ final class AsyncSessionTests: XCTestCase { } func testInvalidCookie() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - // Configure sessions. app.sessions.use(.memory) app.middleware.use(app.sessions.middleware) diff --git a/Tests/VaporTests/AsyncWebSocketTests.swift b/Tests/VaporTests/AsyncWebSocketTests.swift index 37ad405edd..877abe297d 100644 --- a/Tests/VaporTests/AsyncWebSocketTests.swift +++ b/Tests/VaporTests/AsyncWebSocketTests.swift @@ -6,6 +6,17 @@ import NIOCore import NIOPosix final class AsyncWebSocketTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + let test = Environment(name: "testing", arguments: ["vapor"]) + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testWebSocketClient() async throws { let server = try await Application.make(.testing) @@ -17,10 +28,6 @@ final class AsyncWebSocketTests: XCTestCase { server.environment.arguments = ["serve"] try await server.startup() - defer { - server.shutdown() - } - guard let localAddress = server.http.server.shared.localAddress, let port = localAddress.port else { XCTFail("couldn't get port from \(server.http.server.shared.localAddress.debugDescription)") return @@ -42,14 +49,13 @@ final class AsyncWebSocketTests: XCTestCase { let string = try await promise.futureResult.get() XCTAssertEqual(string, "Hello, world!") + + try await server.asyncShutdown() } // https://github.com/vapor/vapor/issues/1997 func testWebSocket404() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.server.configuration.port = 0 app.webSocket("bar") { req, ws in @@ -80,8 +86,6 @@ final class AsyncWebSocketTests: XCTestCase { // https://github.com/vapor/vapor/issues/2009 func testWebSocketServer() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } app.webSocket("foo") { req, ws in ws.send("foo") ws.close(promise: nil) @@ -114,9 +118,6 @@ final class AsyncWebSocketTests: XCTestCase { } func testManualUpgradeToWebSocket() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.server.configuration.port = 0 app.get("foo") { req in diff --git a/Tests/VaporTests/FileTests.swift b/Tests/VaporTests/FileTests.swift index 23c0ad3c5f..43d8dd2a8f 100644 --- a/Tests/VaporTests/FileTests.swift +++ b/Tests/VaporTests/FileTests.swift @@ -6,10 +6,18 @@ import NIOHTTP1 import Crypto final class FileTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + let test = Environment(name: "testing", arguments: ["vapor"]) + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testStreamFile() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -29,9 +37,6 @@ final class FileTests: XCTestCase { @available(*, deprecated) func testLegacyStreamFile() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req in return req.fileio.streamFile(at: #filePath) { result in do { @@ -50,9 +55,6 @@ final class FileTests: XCTestCase { } func testStreamFileConnectionClose() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) } @@ -67,9 +69,6 @@ final class FileTests: XCTestCase { } func testStreamFileNull() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in var tmpPath: String repeat { @@ -91,9 +90,6 @@ final class FileTests: XCTestCase { } func testAdvancedETagHeaders() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -113,9 +109,6 @@ final class FileTests: XCTestCase { } func testSimpleETagHeaders() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: false) { result in do { @@ -137,9 +130,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeaderTail() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -168,9 +158,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeaderStart() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -199,9 +186,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeadersWithin() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -230,9 +214,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeadersOnlyFirstByte() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.get("file-stream") { req in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -257,9 +238,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeadersWithinFail() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -283,9 +261,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeadersStartFail() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -309,9 +284,6 @@ final class FileTests: XCTestCase { } func testStreamFileContentHeadersTailFail() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) { result in do { @@ -335,9 +307,6 @@ final class FileTests: XCTestCase { } func testFileWrite() throws { - let app = Application(.testing) - defer { app.shutdown() } - let request = Request(application: app, on: app.eventLoopGroup.next()) let data = "Hello" @@ -351,9 +320,6 @@ final class FileTests: XCTestCase { } func testPercentDecodedFilePath() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use(FileMiddleware(publicDirectory: "/" + path)) @@ -364,9 +330,6 @@ final class FileTests: XCTestCase { } func testPercentDecodedRelativePath() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use(FileMiddleware(publicDirectory: "/" + path)) @@ -379,9 +342,6 @@ final class FileTests: XCTestCase { } func testDefaultFileRelative() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use(FileMiddleware(publicDirectory: "/" + path, defaultFile: "index.html")) @@ -395,9 +355,6 @@ final class FileTests: XCTestCase { } func testDefaultFileAbsolute() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use(FileMiddleware(publicDirectory: "/" + path, defaultFile: "/Utilities/index.html")) @@ -411,9 +368,6 @@ final class FileTests: XCTestCase { } func testNoDefaultFile() throws { - let app = Application(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use(FileMiddleware(publicDirectory: "/" + path)) @@ -423,9 +377,6 @@ final class FileTests: XCTestCase { } func testRedirect() throws { - let app = Application(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use( FileMiddleware( @@ -443,9 +394,6 @@ final class FileTests: XCTestCase { } func testRedirectWithQueryParams() throws { - let app = Application(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use( FileMiddleware( @@ -468,9 +416,6 @@ final class FileTests: XCTestCase { } func testNoRedirect() throws { - let app = Application(.testing) - defer { app.shutdown() } - let path = #filePath.split(separator: "/").dropLast().joined(separator: "/") app.middleware.use( FileMiddleware( @@ -489,9 +434,6 @@ final class FileTests: XCTestCase { // https://github.com/vapor/vapor/security/advisories/GHSA-vj2m-9f5j-mpr5 func testInvalidRangeHeaderDoesNotCrash() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("file-stream") { req -> EventLoopFuture in return req.fileio.streamFile(at: #file, advancedETagComparison: true) } @@ -539,9 +481,6 @@ final class FileTests: XCTestCase { } func testAsyncFileWrite() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let request = Request(application: app, on: app.eventLoopGroup.next()) let data = "Hello" @@ -555,9 +494,6 @@ final class FileTests: XCTestCase { } func testAsyncFileRead() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let request = Request(application: app, on: app.eventLoopGroup.next()) let path = "/" + #filePath.split(separator: "/").dropLast().joined(separator: "/") + "/Utilities/long-test-file.txt" diff --git a/Tests/VaporTests/MiddlewareTests.swift b/Tests/VaporTests/MiddlewareTests.swift index b98d44359d..f2c057a100 100644 --- a/Tests/VaporTests/MiddlewareTests.swift +++ b/Tests/VaporTests/MiddlewareTests.swift @@ -4,6 +4,17 @@ import Vapor import NIOCore final class MiddlewareTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + let test = Environment(name: "testing", arguments: ["vapor"]) + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + actor OrderStore { var order: [String] = [] @@ -33,9 +44,6 @@ final class MiddlewareTests: XCTestCase { } func testMiddlewareOrder() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let store = OrderStore() app.grouped( OrderMiddleware("a", store: store), OrderMiddleware("b", store: store), OrderMiddleware("c", store: store) @@ -52,9 +60,6 @@ final class MiddlewareTests: XCTestCase { } func testPrependingMiddleware() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - let store = OrderStore() app.middleware.use(OrderMiddleware("b", store: store)) app.middleware.use(OrderMiddleware("c", store: store)) @@ -74,9 +79,6 @@ final class MiddlewareTests: XCTestCase { } func testCORSMiddlewareVariedByRequestOrigin() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.grouped( CORSMiddleware(configuration: .init(allowedOrigin: .originBased, allowedMethods: [.GET], allowedHeaders: [.origin])) ).get("order") { req -> String in @@ -93,9 +95,6 @@ final class MiddlewareTests: XCTestCase { } func testCORSMiddlewareNoVariationByRequestOriginAllowed() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.grouped( CORSMiddleware(configuration: .init(allowedOrigin: .none, allowedMethods: [.GET], allowedHeaders: [])) ).get("order") { req -> String in @@ -116,8 +115,6 @@ final class MiddlewareTests: XCTestCase { XCTAssertNoThrow(fileMiddleware = try FileMiddleware(bundle: .module, publicDirectory: "/"), "FileMiddleware instantiation from Bundle should not fail") - let app = try await Application.make(.testing) - defer { app.shutdown() } app.middleware.use(fileMiddleware) try await app.testable().test(.GET, "/foo.txt") { result async in @@ -131,8 +128,6 @@ final class MiddlewareTests: XCTestCase { XCTAssertNoThrow(fileMiddleware = try FileMiddleware(bundle: .module, publicDirectory: "SubUtilities"), "FileMiddleware instantiation from Bundle should not fail") - let app = try await Application.make(.testing) - defer { app.shutdown() } app.middleware.use(fileMiddleware) try await app.testable().test(.GET, "/index.html") { result async in diff --git a/Tests/VaporTests/PipelineTests.swift b/Tests/VaporTests/PipelineTests.swift index 09b584c1ab..c9e5abfb1b 100644 --- a/Tests/VaporTests/PipelineTests.swift +++ b/Tests/VaporTests/PipelineTests.swift @@ -69,10 +69,6 @@ final class PipelineTests: XCTestCase { } func testAsyncEchoHandlers() async throws { - let app = try await Application.make(.testing) - defer { app.shutdown() } - - app.on(.POST, "echo", body: .stream) { request async throws -> Response in var buffers = [ByteBuffer]() diff --git a/Tests/VaporTests/ServerTests.swift b/Tests/VaporTests/ServerTests.swift index f48234f8a4..1bad7c6ee4 100644 --- a/Tests/VaporTests/ServerTests.swift +++ b/Tests/VaporTests/ServerTests.swift @@ -12,7 +12,18 @@ import NIOHTTP1 import NIOSSL import Atomics -final class ServerTests: XCTestCase { +final class ServerTests: XCTestCase, @unchecked Sendable { + var app: Application! + + override func setUp() async throws { + let test = Environment(name: "testing", arguments: ["vapor"]) + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testPortOverride() throws { let env = Environment( name: "testing", @@ -233,9 +244,7 @@ final class ServerTests: XCTestCase { let payload_2766 = "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" let jsonPayload = ByteBuffer(base64String: payload_2766)! // Payload from #2766 - - let app = Application(.testing) - defer { app.shutdown() } + app.http.server.configuration.port = 0 // Max out at the smaller payload (.size is of compressed data) @@ -269,8 +278,6 @@ final class ServerTests: XCTestCase { } func testConfigureHTTPDecompressionLimit() throws { - let app = Application(.testing) - defer { app.shutdown() } app.http.server.configuration.port = 0 let smallOrigString = "Hello, world!" @@ -319,9 +326,6 @@ final class ServerTests: XCTestCase { /// To regenerate, copy the above and run `% pbpaste | gzip | base64`. To verify, run `% pbpaste | base64 -d | gzip -d` instead. let compressedPayload = ByteBuffer(base64String: "H4sIANRAImYAA6tWSs7PLShKLS5OTVGyUohWyk6tBNJKZYk5palKOgqj/FH+KH+UP8of5RPmx9YCAMfjVAhQBgAA")! - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 @@ -423,9 +427,6 @@ final class ServerTests: XCTestCase { /// To regenerate, copy the above and run `% pbpaste | gzip | base64`. To verify, run `% pbpaste | base64 -d | gzip -d` instead. let compressedPayload = ByteBuffer(base64String: "H4sIANRAImYAA6tWSs7PLShKLS5OTVGyUohWyk6tBNJKZYk5palKOgqj/FH+KH+UP8of5RPmx9YCAMfjVAhQBgAA")! - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 @@ -433,7 +434,7 @@ final class ServerTests: XCTestCase { serverConfig.certificateVerification = .noHostnameVerification app.http.server.configuration.tlsConfiguration = serverConfig - app.http.server.configuration.customCertificateVerifyCallback = { peerCerts, successPromise in + app.http.server.configuration.customCertificateVerifyCallback = { @Sendable peerCerts, successPromise in /// This lies and accepts the above cert, which has actually expired. XCTAssertEqual(peerCerts, [cert]) successPromise.succeed(.certificateVerified) @@ -532,9 +533,6 @@ final class ServerTests: XCTestCase { func testHTTP1ResponseDecompression() async throws { let compressiblePayload = #"{"compressed": ["key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value"]}"# - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 @@ -600,9 +598,6 @@ final class ServerTests: XCTestCase { let compressiblePayload = #"{"compressed": ["key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value", "key": "value"]}"# - let app = try await Application.make(.testing) - defer { app.shutdown() } - app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 @@ -610,7 +605,7 @@ final class ServerTests: XCTestCase { serverConfig.certificateVerification = .noHostnameVerification app.http.server.configuration.tlsConfiguration = serverConfig - app.http.server.configuration.customCertificateVerifyCallback = { peerCerts, successPromise in + app.http.server.configuration.customCertificateVerifyCallback = { @Sendable peerCerts, successPromise in /// This lies and accepts the above cert, which has actually expired. XCTAssertEqual(peerCerts, [cert]) successPromise.succeed(.certificateVerified) @@ -673,10 +668,8 @@ final class ServerTests: XCTestCase { } func testRequestBodyStreamGetsFinalisedEvenIfClientAbandonsConnection() throws { - let app = Application(.testing) app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 - defer { app.shutdown() } let numRequests = ManagedAtomic(0) let writersStarted = DispatchSemaphore(value: 0) @@ -720,9 +713,6 @@ final class ServerTests: XCTestCase { } func testLiveServer() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.routes.get("ping") { req -> String in return "123" } @@ -734,9 +724,6 @@ final class ServerTests: XCTestCase { } func testCustomServer() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.servers.use(.custom) XCTAssertEqual(app.customServer.didStart.withLockedValue({ $0 }), false) XCTAssertEqual(app.customServer.didShutdown.withLockedValue({ $0 }), false) @@ -751,9 +738,6 @@ final class ServerTests: XCTestCase { } func testMultipleChunkBody() throws { - let app = Application(.testing) - defer { app.shutdown() } - let payload = [UInt8].random(count: 1 << 20) app.on(.POST, "payload", body: .collect(maxSize: "1gb")) { req -> HTTPStatus in @@ -773,9 +757,6 @@ final class ServerTests: XCTestCase { } func testCollectedResponseBodyEnd() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.post("drain") { req -> EventLoopFuture in let promise = req.eventLoop.makePromise(of: HTTPStatus.self) req.body.drain { result in @@ -802,9 +783,6 @@ final class ServerTests: XCTestCase { func testMissingBody() throws { struct User: Content { } - let app = Application(.testing) - defer { app.shutdown() } - app.get("user") { req -> User in return try req.content.decode(User.self) } @@ -816,17 +794,11 @@ final class ServerTests: XCTestCase { // https://github.com/vapor/vapor/issues/2245 func testTooLargePort() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.http.server.configuration.port = .max XCTAssertThrowsError(try app.start()) } func testEarlyExitStreamingRequest() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.on(.POST, "upload", body: .stream) { req -> EventLoopFuture in guard req.headers.first(name: "test") != nil else { return req.eventLoop.makeFailedFuture(Abort(.badRequest)) @@ -865,9 +837,6 @@ final class ServerTests: XCTestCase { @available(*, deprecated, message: "To avoid deprecation warnings") func testEchoServer() throws { - let app = Application(.testing, .createNew) - defer { app.shutdown() } - final class Context: Sendable { let server: NIOLockedValueBox<[String]> let client: NIOLockedValueBox<[String]> @@ -912,7 +881,7 @@ final class ServerTests: XCTestCase { body: .stream(length: nil, { stream in // We set the application to have a single event loop so we can use the same // event loop here - let streamBox = NIOLoopBound(stream, eventLoop: app.eventLoopGroup.any()) + let streamBox = NIOLoopBound(stream, eventLoop: self.app.eventLoopGroup.any()) return stream.write(.byteBuffer(.init(string: "foo"))).flatMap { streamBox.value.write(.byteBuffer(.init(string: "bar"))) }.flatMap { @@ -998,35 +967,25 @@ final class ServerTests: XCTestCase { func testStartWithValidSocketFile() throws { let socketPath = "/tmp/\(UUID().uuidString).vapor.socket" - let app = Application(.testing) app.http.server.configuration.address = .unixDomainSocket(path: socketPath) - defer { - app.shutdown() - } app.environment.arguments = ["serve"] XCTAssertNoThrow(try app.start()) } func testStartWithUnsupportedSocketFile() throws { - let app = Application(.testing) app.http.server.configuration.address = .unixDomainSocket(path: "/tmp") - defer { app.shutdown() } XCTAssertThrowsError(try app.start()) } func testStartWithInvalidSocketFilePath() throws { - let app = Application(.testing) app.http.server.configuration.address = .unixDomainSocket(path: "/tmp/nonexistent/vapor.socket") - defer { app.shutdown() } XCTAssertThrowsError(try app.start()) } func testStartWithDefaultHostnameConfiguration() throws { - let app = Application(.testing) app.http.server.configuration.address = .hostname(nil, port: nil) - defer { app.shutdown() } app.environment.arguments = ["serve"] XCTAssertNoThrow(try app.start()) @@ -1096,9 +1055,6 @@ final class ServerTests: XCTestCase { } func testQuiesceKeepAliveConnections() throws { - let app = Application(.testing) - defer { app.shutdown() } - app.get("hello") { req in "world" } @@ -1122,10 +1078,8 @@ final class ServerTests: XCTestCase { } func testRequestBodyStreamGetsFinalisedEvenIfClientDisappears() { - let app = Application(.testing) app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 - defer { app.shutdown() } let serverIsFinalisedPromise = app.eventLoopGroup.any().makePromise(of: Void.self) let allDonePromise = app.eventLoopGroup.any().makePromise(of: Void.self) @@ -1178,10 +1132,8 @@ final class ServerTests: XCTestCase { } func testRequestBodyBackpressureWorks() { - let app = Application(.testing) app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 - defer { app.shutdown() } let numberOfTimesTheServerGotOfferedBytes = ManagedAtomic(0) let bytesTheServerSaw = ManagedAtomic(0) @@ -1275,9 +1227,7 @@ final class ServerTests: XCTestCase { let cert = try NIOSSLCertificate(file: clientCertPath.path, format: .pem) let key = try NIOSSLPrivateKey(file: clientKeyPath.path, format: .pem) - - let app = Application(.testing) - + app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 @@ -1285,7 +1235,7 @@ final class ServerTests: XCTestCase { serverConfig.certificateVerification = .noHostnameVerification app.http.server.configuration.tlsConfiguration = serverConfig - app.http.server.configuration.customCertificateVerifyCallback = { peerCerts, successPromise in + app.http.server.configuration.customCertificateVerifyCallback = { @Sendable peerCerts, successPromise in // This lies and accepts the above cert, which has actually expired. XCTAssertEqual(peerCerts, [cert]) successPromise.succeed(.certificateVerified) @@ -1305,7 +1255,6 @@ final class ServerTests: XCTestCase { "world" } - defer { app.shutdown() } try app.start() XCTAssertNotNil(app.http.server.shared.localAddress) @@ -1333,9 +1282,7 @@ final class ServerTests: XCTestCase { let cert = try NIOSSLCertificate(file: clientCertPath.path, format: .pem) let key = try NIOSSLPrivateKey(file: clientKeyPath.path, format: .pem) - - let app = Application(.testing) - + app.http.server.configuration.hostname = "127.0.0.1" app.http.server.configuration.port = 0 app.http.server.configuration.serverName = "Old" @@ -1354,7 +1301,6 @@ final class ServerTests: XCTestCase { "world" } - defer { app.shutdown() } try app.start() XCTAssertNotNil(app.http.server.shared.localAddress) @@ -1382,7 +1328,7 @@ final class ServerTests: XCTestCase { serverConfig.certificateVerification = .noHostnameVerification app.http.server.configuration.tlsConfiguration = serverConfig - app.http.server.configuration.customCertificateVerifyCallback = { peerCerts, successPromise in + app.http.server.configuration.customCertificateVerifyCallback = { @Sendable peerCerts, successPromise in /// This lies and accepts the above cert, which has actually expired. XCTAssertEqual(peerCerts, [cert]) successPromise.succeed(.certificateVerified) @@ -1410,10 +1356,8 @@ final class ServerTests: XCTestCase { } func testConfigurationHasActualPortAfterStart() throws { - let app = Application(.testing) app.environment.arguments = ["serve"] app.http.server.configuration.port = 0 - defer { app.shutdown() } try app.start() XCTAssertNotEqual(app.http.server.configuration.port, 0) @@ -1461,17 +1405,21 @@ final class CustomServer: Server, Sendable { self.didShutdown = .init(false) } - func start(hostname: String?, port: Int?) throws { - try self.start(address: .hostname(hostname, port: port)) + func start(address: BindAddress?) throws { + self.didStart.withLockedValue { $0 = true } } - func start(address: BindAddress?) throws { + func start(address: BindAddress?) async throws { self.didStart.withLockedValue { $0 = true } } func shutdown() { self.didShutdown.withLockedValue { $0 = true } } + + func shutdown() async { + self.didShutdown.withLockedValue { $0 = true } + } } private extension ByteBuffer { diff --git a/Tests/VaporTests/SessionTests.swift b/Tests/VaporTests/SessionTests.swift index 4d20bda735..1666ca3fd5 100644 --- a/Tests/VaporTests/SessionTests.swift +++ b/Tests/VaporTests/SessionTests.swift @@ -5,6 +5,17 @@ import NIOCore import NIOHTTP1 final class SessionTests: XCTestCase { + var app: Application! + + override func setUp() async throws { + let test = Environment(name: "testing", arguments: ["vapor"]) + app = try await Application.make(test) + } + + override func tearDown() async throws { + try await app.asyncShutdown() + } + func testSessionDestroy() async throws { actor MockKeyedCache: AsyncSessionDriver { var ops: [String] = [] @@ -41,9 +52,6 @@ final class SessionTests: XCTestCase { var cookie: HTTPCookies.Value? - let app = try await Application.make() - defer { app.shutdown() } - let cache = MockKeyedCache() app.sessions.use { _ in cache } let sessions = app.routes.grouped(app.sessions.middleware) @@ -84,9 +92,6 @@ final class SessionTests: XCTestCase { } func testInvalidCookie() throws { - let app = Application(.testing) - defer { app.shutdown() } - // Configure sessions. app.sessions.use(.memory) app.middleware.use(app.sessions.middleware)