From 4f7dd8a0a91ec597a93ed1d75aea2749de86f937 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Mon, 26 Aug 2019 10:46:05 -0400 Subject: [PATCH 1/2] eventloop + thread fixes --- Sources/Development/routes.swift | 2 +- Sources/Vapor/Application.swift | 25 +- Sources/Vapor/Request/Request.swift | 21 +- Sources/Vapor/Server/HTTPServer.swift | 8 +- .../Server/HTTPServerRequestDecoder.swift | 3 +- Sources/Vapor/Services/Services+Default.swift | 4 +- Sources/Vapor/Utilities/Lock.swift | 20 ++ Sources/XCTVapor/XCTApplication.swift | 219 +++++++------ Tests/VaporTests/ApplicationTests.swift | 298 +++++++++++------- Tests/VaporTests/AuthenticationTests.swift | 102 +++--- 10 files changed, 434 insertions(+), 268 deletions(-) create mode 100644 Sources/Vapor/Utilities/Lock.swift diff --git a/Sources/Development/routes.swift b/Sources/Development/routes.swift index f147f83db8..d791d8178b 100644 --- a/Sources/Development/routes.swift +++ b/Sources/Development/routes.swift @@ -61,7 +61,7 @@ public func routes(_ r: Routes, _ c: Container) throws { } } - let ip = req.channel.remoteAddress?.description ?? "" + let ip = req.remoteAddress?.description ?? "" ws.send("Hello 👋 \(ip)") } diff --git a/Sources/Vapor/Application.swift b/Sources/Vapor/Application.swift index 3f2a3f75a7..8debb7c027 100644 --- a/Sources/Vapor/Application.swift +++ b/Sources/Vapor/Application.swift @@ -7,15 +7,28 @@ public final class Application { public var userInfo: [AnyHashable: Any] - public let lock: NSLock + public let sync: Lock private let configure: (inout Services) throws -> () private let threadPool: NIOThreadPool private var didShutdown: Bool + + public var running: Running? { + get { + self.sync.lock() + defer { self.sync.unlock() } + return self._running + } + set { + self.sync.lock() + defer { self.sync.unlock() } + self._running = newValue + } + } - public var running: Running? + private var _running: Running? public var logger: Logger @@ -24,11 +37,7 @@ public final class Application { public struct Running { public var stop: () -> Void public init(stop: @escaping () -> Void) { - self.stop = { - DispatchQueue.global().async { - stop() - } - } + self.stop = stop } } @@ -41,7 +50,7 @@ public final class Application { self.userInfo = [:] self.didShutdown = false self.configure = configure - self.lock = NSLock() + self.sync = Lock() self.threadPool = .init(numberOfThreads: 1) self.threadPool.start() self.logger = .init(label: "codes.vapor.application") diff --git a/Sources/Vapor/Request/Request.swift b/Sources/Vapor/Request/Request.swift index 347b8e97f2..4c136047ff 100644 --- a/Sources/Vapor/Request/Request.swift +++ b/Sources/Vapor/Request/Request.swift @@ -111,14 +111,10 @@ public final class Request: CustomStringConvertible { desc.append(self.body.description) return desc.joined(separator: "\n") } + + public let remoteAddress: SocketAddress? - // public var upgrader: HTTPClientProtocolUpgrader? - - public let channel: Channel - - public var eventLoop: EventLoop { - return self.channel.eventLoop - } + public let eventLoop: EventLoop public var parameters: Parameters @@ -130,7 +126,8 @@ public final class Request: CustomStringConvertible { version: HTTPVersion = .init(major: 1, minor: 1), headers: HTTPHeaders = .init(), collectedBody: ByteBuffer? = nil, - on channel: Channel + remoteAddress: SocketAddress? = nil, + on eventLoop: EventLoop ) { self.init( method: method, @@ -138,7 +135,7 @@ public final class Request: CustomStringConvertible { version: version, headersNoUpdate: headers, collectedBody: collectedBody, - on: channel + on: eventLoop ) if let body = collectedBody { self.headers.updateContentLength(body.readableBytes) @@ -151,7 +148,8 @@ public final class Request: CustomStringConvertible { version: HTTPVersion = .init(major: 1, minor: 1), headersNoUpdate headers: HTTPHeaders = .init(), collectedBody: ByteBuffer? = nil, - on channel: Channel + remoteAddress: SocketAddress? = nil, + on eventLoop: EventLoop ) { self.method = method self.url = url @@ -162,7 +160,8 @@ public final class Request: CustomStringConvertible { } else { self.bodyStorage = .none } - self.channel = channel + self.remoteAddress = remoteAddress + self.eventLoop = eventLoop self.parameters = .init() self.userInfo = [:] self.isKeepAlive = true diff --git a/Sources/Vapor/Server/HTTPServer.swift b/Sources/Vapor/Server/HTTPServer.swift index 8a58c6044c..20566cc1ae 100644 --- a/Sources/Vapor/Server/HTTPServer.swift +++ b/Sources/Vapor/Server/HTTPServer.swift @@ -153,11 +153,17 @@ public final class HTTPServer: Server { configuration: self.configuration, on: self.application.eventLoopGroup ) - self.connection = try connection.wait() + + try self.application.sync.do { + self.connection = try connection.wait() + } self.didStart = true } public func shutdown() { + self.application.sync.lock() + defer { self.application.sync.unlock() } + guard let connection = self.connection else { fatalError("Called shutdown before start") } diff --git a/Sources/Vapor/Server/HTTPServerRequestDecoder.swift b/Sources/Vapor/Server/HTTPServerRequestDecoder.swift index eec7839875..71a89ede05 100644 --- a/Sources/Vapor/Server/HTTPServerRequestDecoder.swift +++ b/Sources/Vapor/Server/HTTPServerRequestDecoder.swift @@ -47,7 +47,8 @@ final class HTTPServerRequestDecoder: ChannelDuplexHandler, RemovableChannelHand url: .init(string: head.uri), version: head.version, headersNoUpdate: head.headers, - on: context.channel + remoteAddress: context.channel.remoteAddress, + on: context.channel.eventLoop ) switch head.version.major { case 2: diff --git a/Sources/Vapor/Services/Services+Default.swift b/Sources/Vapor/Services/Services+Default.swift index 4db7139e92..2b18091a52 100644 --- a/Sources/Vapor/Services/Services+Default.swift +++ b/Sources/Vapor/Services/Services+Default.swift @@ -54,8 +54,8 @@ extension Services { } s.register(MemorySessions.Storage.self) { c in let app = try c.make(Application.self) - app.lock.lock() - defer { app.lock.unlock() } + app.sync.lock() + defer { app.sync.unlock() } let key = "memory-sessions-storage" if let existing = app.userInfo[key] as? MemorySessions.Storage { return existing diff --git a/Sources/Vapor/Utilities/Lock.swift b/Sources/Vapor/Utilities/Lock.swift new file mode 100644 index 0000000000..474624ece6 --- /dev/null +++ b/Sources/Vapor/Utilities/Lock.swift @@ -0,0 +1,20 @@ +public struct Lock { + private let nslock: NSLock + init() { + self.nslock = .init() + } + + public func lock() { + self.nslock.lock() + } + + public func unlock() { + self.nslock.unlock() + } + + public func `do`(_ closure: () throws -> Void) rethrows { + self.lock() + defer { self.unlock() } + try closure() + } +} diff --git a/Sources/XCTVapor/XCTApplication.swift b/Sources/XCTVapor/XCTApplication.swift index 2828d15eab..99d3710383 100644 --- a/Sources/XCTVapor/XCTApplication.swift +++ b/Sources/XCTVapor/XCTApplication.swift @@ -21,109 +21,51 @@ public final class XCTApplication { } return self } - - public final class InMemory { - let container: () throws -> Container - - init(container: @escaping () throws -> Container) throws { - self.container = container - } - @discardableResult - public func test( - _ method: HTTPMethod, - _ path: String, - headers: HTTPHeaders = [:], - json: Body, - file: StaticString = #file, - line: UInt = #line, - closure: (XCTHTTPResponse) throws -> () = { _ in } - ) throws -> InMemory - where Body: Encodable - { - var body = ByteBufferAllocator().buffer(capacity: 0) - try body.writeBytes(JSONEncoder().encode(json)) - var headers = HTTPHeaders() - headers.contentType = .json - return try self.test(method, path, headers: headers, body: body, closure: closure) + public enum Method { + case inMemory + case running(port: Int) + public static var running: Method { + return .running(port: 8080) } - - @discardableResult - public func test( - _ method: HTTPMethod, - _ path: String, - headers: HTTPHeaders = [:], - body: ByteBuffer? = nil, - file: StaticString = #file, - line: UInt = #line, - closure: (XCTHTTPResponse) throws -> () = { _ in } - ) throws -> InMemory { - let container = try self.container() - defer { container.shutdown() } - let responder = try container.make(Responder.self) + } - var headers = headers - if let body = body { - headers.replaceOrAdd(name: .contentLength, value: body.readableBytes.description) - } - let response: XCTHTTPResponse - let request = Request( - method: method, - url: .init(string: path), - headers: headers, - collectedBody: body, - on: EmbeddedChannel() - ) - let res = try responder.respond(to: request).wait() - response = XCTHTTPResponse(status: res.status, headers: res.headers, body: res.body) - try closure(response) - return self + public func start(method: Method = .inMemory) throws -> XCTApplicationTester { + switch method { + case .inMemory: + return try InMemory(container: self.container()) + case .running(let port): + return try Live(container: self.container(), port: port) } } - public func inMemory() throws -> InMemory { - return try InMemory(container: self.container) - } - - public final class Live { - let container: () throws -> Container + private struct Live: XCTApplicationTester { + let container: Container + let server: Server let port: Int - public struct Running { - let container: Container - let server: Server - - public func shutdown() { - self.server.shutdown() - self.container.shutdown() - } - } - - init(container: @escaping () throws -> Container, port: Int) throws { + init(container: Container, port: Int) throws { self.container = container self.port = port + self.server = try container.make(Server.self) + try server.start(hostname: "localhost", port: port) } - public func start() throws -> Running { - let container = try self.container() - let server = try container.make(Server.self) - try server.start(hostname: "localhost", port: port) - return Running(container: container, server: server) + public func shutdown() { + self.server.shutdown() + self.container.shutdown() } @discardableResult - public func test( - _ method: HTTPMethod, - _ path: String, - headers: HTTPHeaders = [:], - body: ByteBuffer? = nil, - file: StaticString = #file, - line: UInt = #line, - closure: (XCTHTTPResponse) throws -> () = { _ in } - ) throws -> Live { - let server = try self.start() - defer { server.shutdown() } - + public func performTest( + method: HTTPMethod, + path: String, + headers: HTTPHeaders, + body: ByteBuffer?, + file: StaticString, + line: UInt, + closure: (XCTHTTPResponse) throws -> () + ) throws -> XCTApplicationTester { let client = HTTPClient(eventLoopGroupProvider: .createNew) defer { try! client.syncShutdown() } var request = try HTTPClient.Request(url: "http://localhost:\(self.port)\(path)") @@ -141,9 +83,47 @@ public final class XCTApplication { return self } } - - public func live(port: Int) throws -> Live { - return try Live(container: self.container, port: port) + + private struct InMemory: XCTApplicationTester { + let container: Container + + init(container: Container) throws { + self.container = container + } + + public func shutdown() { + self.container.shutdown() + } + + @discardableResult + public func performTest( + method: HTTPMethod, + path: String, + headers: HTTPHeaders, + body: ByteBuffer?, + file: StaticString, + line: UInt, + closure: (XCTHTTPResponse) throws -> () + ) throws -> XCTApplicationTester { + let responder = try self.container.make(Responder.self) + var headers = headers + if let body = body { + headers.replaceOrAdd(name: .contentLength, value: body.readableBytes.description) + } + let response: XCTHTTPResponse + let request = Request( + method: method, + url: .init(string: path), + headers: headers, + collectedBody: body, + remoteAddress: nil, + on: self.container.eventLoop.next() + ) + let res = try responder.respond(to: request).wait() + response = XCTHTTPResponse(status: res.status, headers: res.headers, body: res.body) + try closure(response) + return self + } } private func container() throws -> Container { @@ -159,3 +139,60 @@ public final class XCTApplication { return try c.wait() } } + +public protocol XCTApplicationTester { + @discardableResult + func performTest( + method: HTTPMethod, + path: String, + headers: HTTPHeaders, + body: ByteBuffer?, + file: StaticString, + line: UInt, + closure: (XCTHTTPResponse) throws -> () + ) throws -> XCTApplicationTester + + func shutdown() +} + +extension XCTApplicationTester { + @discardableResult + public func test( + _ method: HTTPMethod, + _ path: String, + headers: HTTPHeaders = [:], + body: ByteBuffer? = nil, + file: StaticString = #file, + line: UInt = #line, + closure: (XCTHTTPResponse) throws -> () = { _ in } + ) throws -> XCTApplicationTester { + return try self.performTest( + method: method, + path: path, + headers: headers, + body: body, + file: file, + line: line, + closure: closure + ) + } + + @discardableResult + public func test( + _ method: HTTPMethod, + _ path: String, + headers: HTTPHeaders = [:], + json: Body, + file: StaticString = #file, + line: UInt = #line, + closure: (XCTHTTPResponse) throws -> () = { _ in } + ) throws -> XCTApplicationTester + where Body: Encodable + { + var body = ByteBufferAllocator().buffer(capacity: 0) + try body.writeBytes(JSONEncoder().encode(json)) + var headers = HTTPHeaders() + headers.contentType = .json + return try self.test(method, path, headers: headers, body: body, closure: closure) + } +} diff --git a/Tests/VaporTests/ApplicationTests.swift b/Tests/VaporTests/ApplicationTests.swift index ede598bbc7..e6a3483c01 100644 --- a/Tests/VaporTests/ApplicationTests.swift +++ b/Tests/VaporTests/ApplicationTests.swift @@ -6,8 +6,7 @@ final class ApplicationTests: XCTestCase { let test = Environment(name: "testing", arguments: ["vapor"]) let app = Application(environment: test) try app.boot() - DispatchQueue.global().async { - COperatingSystem.sleep(1) + DispatchQueue.global().asyncAfter(deadline: .now() + 1) { app.running?.stop() } try app.run() @@ -47,7 +46,7 @@ final class ApplicationTests: XCTestCase { func testContent() throws { let request = Request( collectedBody: .init(string: #"{"hello": "world"}"#), - on: EmbeddedChannel() + on: EmbeddedEventLoop() ) request.headers.contentType = .json try XCTAssertEqual(request.content.get(at: "hello"), "world") @@ -83,13 +82,13 @@ final class ApplicationTests: XCTestCase { ] } """ - let request = Request(collectedBody: .init(string: complexJSON), on: EmbeddedChannel()) + let request = Request(collectedBody: .init(string: complexJSON), on: EmbeddedEventLoop()) request.headers.contentType = .json try XCTAssertEqual(request.content.get(at: "batters", "batter", 1, "type"), "Chocolate") } func testQuery() throws { - let request = Request(on: EmbeddedChannel()) + let request = Request(on: EmbeddedEventLoop()) request.headers.contentType = .json request.url.path = "/foo" print(request.url.string) @@ -109,19 +108,20 @@ final class ApplicationTests: XCTestCase { } }) defer { app.shutdown() } - - try app.testable().inMemory() - .test(.GET, "/hello/vapor") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertContains(res.body.string, "vapor") - } - .test(.POST, "/hello/vapor") { res in - XCTAssertEqual(res.status, .notFound) - } - .test(.GET, "/hello/vapor/development") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, #"["vapor","development"]"#) - } + + + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/hello/vapor") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertContains(res.body.string, "vapor") + }.test(.POST, "/hello/vapor") { res in + XCTAssertEqual(res.status, .notFound) + }.test(.GET, "/hello/vapor/development") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, #"["vapor","development"]"#) + } } func testJSON() throws { @@ -133,11 +133,13 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/json") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, #"{"foo":"bar"}"#) - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/json") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, #"{"foo":"bar"}"#) + } } func testRootGet() throws { @@ -152,15 +154,17 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "root") - } - .test(.GET, "/foo") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "foo") - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "root") + } + .test(.GET, "/foo") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "foo") + } } do { let app = Application.create(routes: { r, c in @@ -173,15 +177,17 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "root") - } - .test(.GET, "/foo") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "foo") - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "root") + } + .test(.GET, "/foo") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "foo") + } } } @@ -192,12 +198,14 @@ final class ApplicationTests: XCTestCase { } }) defer { app.shutdown() } + + let server = try app.testable().start() + defer { server.shutdown() } - try app.testable().live(port: 8080) - .test(.GET, "/ping") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "123") - } + try server.test(.GET, "/ping") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "123") + } } // https://github.com/vapor/vapor/issues/1537 @@ -209,11 +217,13 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().live(port: 8080) - .test(.GET, "/todos?a=b") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "hi") - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/todos?a=b") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "hi") + } } func testGH1534() throws { @@ -233,11 +243,13 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/decode_error") { res in - XCTAssertEqual(res.status, .badRequest) - XCTAssertContains(res.body.string, "Value of type 'Int' required for key 'bar'") - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/decode_error") { res in + XCTAssertEqual(res.status, .badRequest) + XCTAssertContains(res.body.string, "Value of type 'Int' required for key 'bar'") + } } func testContentContainer() throws { @@ -259,11 +271,13 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/encode") { res in - XCTAssertEqual(res.status, .ok) - XCTAssertContains(res.body.string, "hi") - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/encode") { res in + XCTAssertEqual(res.status, .ok) + XCTAssertContains(res.body.string, "hi") + } } func testMultipartDecode() throws { @@ -300,13 +314,15 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/multipart", headers: [ - "Content-Type": "multipart/form-data; boundary=123" - ], body: .init(string: data)) { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqualJSON(res.body.string, expected) - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/multipart", headers: [ + "Content-Type": "multipart/form-data; boundary=123" + ], body: .init(string: data)) { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqualJSON(res.body.string, expected) + } } func testMultipartEncode() throws { @@ -324,7 +340,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/multipart") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/multipart") { res in XCTAssertEqual(res.status, .ok) let boundary = res.headers.contentType?.parameters["boundary"] ?? "none" XCTAssertContains(res.body.string, "Content-Disposition: form-data; name=\"name\"") @@ -340,7 +359,7 @@ final class ApplicationTests: XCTestCase { let promise = req.eventLoop.makePromise(of: String.self) return WebSocket.connect( to: "ws://echo.websocket.org/", - on: c.eventLoop + on: req.eventLoop ) { ws in ws.send("Hello, world!") ws.onText { ws, text in @@ -354,7 +373,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/ws") { res in + let server = try app.testable().start(method: .inMemory) + defer { server.shutdown() } + + try server.test(.GET, "/ws") { res in XCTAssertEqual(res.status, .ok) XCTAssertEqual(res.body.string, "Hello, world!") } @@ -371,7 +393,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/view") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/view") { res in XCTAssertEqual(res.status.code, 200) XCTAssertEqual(res.headers.contentType, .html) XCTAssertEqual(res.body.string, "

hello

") @@ -401,7 +426,11 @@ final class ApplicationTests: XCTestCase { var body = ByteBufferAllocator().buffer(capacity: 0) body.writeString("name=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]=7") - try app.testable().inMemory().test(.GET, "/urlencodedform", headers: headers, body: body) { res in + + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/urlencodedform", headers: headers, body: body) { res in XCTAssertEqual(res.status.code, 200) } } @@ -421,7 +450,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/urlencodedform") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/urlencodedform") { res in debugPrint(res) XCTAssertEqual(res.status.code, 200) XCTAssertEqual(res.headers.contentType, .urlEncodedForm) @@ -451,8 +483,11 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start(method: .inMemory) + defer { server.shutdown() } + let data = "name=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]=7" - try app.testable().inMemory().test(.GET, "/urlencodedform?\(data)") { res in + try server.test(.GET, "/urlencodedform?\(data)") { res in XCTAssertEqual(res.status.code, 200) } } @@ -503,7 +538,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().live(port: 8080).test(.GET, "/file-stream") { res in + let server = try app.testable().start(method: .running) + defer { server.shutdown() } + + try server.test(.GET, "/file-stream") { res in let test = "the quick brown fox" XCTAssertNotNil(res.headers.firstValue(name: .eTag)) XCTAssertContains(res.body.string, test) @@ -519,9 +557,13 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start(method: .running) + defer { server.shutdown() } + var headers = HTTPHeaders() headers.replaceOrAdd(name: .connection, value: "close") - try app.testable().live(port: 8080).test(.GET, "/file-stream", headers: headers) { res in + + try server.test(.GET, "/file-stream", headers: headers) { res in let test = "the quick brown fox" XCTAssertNotNil(res.headers.firstValue(name: .eTag)) XCTAssertContains(res.body.string, test) @@ -540,7 +582,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/custom-encode") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/custom-encode") { res in XCTAssertEqual(res.body.string, """ { "hello" : "world" @@ -563,13 +608,16 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start() + defer { server.shutdown() } + var body = ByteBufferAllocator().buffer(capacity: 0) body.writeString(#"{"here":"hi"}"#) var headers = HTTPHeaders() headers.replaceOrAdd(name: .contentLength, value: body.readableBytes.description) headers.contentType = .json - try app.testable().inMemory().test(.POST, "/decode-fail", headers: headers, body: body) { res in + try server.test(.POST, "/decode-fail", headers: headers, body: body) { res in XCTAssertEqual(res.status, .badRequest) XCTAssertContains(res.body.string, "missing") } @@ -596,7 +644,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.POST, "/users", json: ["name": "vapor", "email": "foo"]) { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.POST, "/users", json: ["name": "vapor", "email": "foo"]) { res in XCTAssertEqual(res.status, .badRequest) XCTAssertContains(res.body.string, "email is not a valid email address") } @@ -614,7 +665,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/foo?number=true") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/foo?number=true") { res in XCTAssertEqual(res.status, .ok) XCTAssertEqual(res.body.string, "42") }.test(.GET, "/foo?number=false") { res in @@ -649,7 +703,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/foo?number=true") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/foo?number=true") { res in XCTAssertEqual(res.status, .ok) XCTAssertEqual(res.body.string, "42") }.test(.GET, "/foo?number=false") { res in @@ -730,8 +787,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start() + defer { server.shutdown() } - try app.testable().inMemory().test(.POST, "/users", json: ["name": "vapor"]) { res in + try server.test(.POST, "/users", json: ["name": "vapor"]) { res in XCTAssertEqual(res.status, .created) XCTAssertEqual(res.headers.contentType, .json) XCTAssertEqual(res.body.string, """ @@ -749,8 +808,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start(method: .running) + defer { server.shutdown() } - try app.testable().live(port: 8080).test(.HEAD, "/hello") { res in + try server.test(.HEAD, "/hello") { res in XCTAssertEqual(res.status, .ok) XCTAssertEqual(res.headers.firstValue(name: .contentLength), "2") XCTAssertEqual(res.body.count, 0) @@ -766,10 +827,13 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start() + defer { server.shutdown() } + var headers = HTTPHeaders() headers.cookie["vapor-session"] = "asdf" - try app.testable().inMemory().test(.GET, "/get", headers: headers) { res in + try server.test(.GET, "/get", headers: headers) { res in XCTAssertEqual(res.status, .ok) XCTAssertNotNil(res.headers[.setCookie]) XCTAssertEqual(res.body.string, "n/a") @@ -798,7 +862,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/order") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/order") { res in XCTAssertEqual(res.status, .ok) XCTAssertEqual(OrderMiddleware.order, ["a", "b", "c"]) XCTAssertEqual(res.body.string, "done") @@ -807,42 +874,40 @@ final class ApplicationTests: XCTestCase { func testSessionDestroy() throws { final class MockKeyedCache: Sessions { - var ops: [String] + static var ops: [String] = [] - var eventLoop: EventLoop { - return EmbeddedEventLoop() - } + let eventLoop: EventLoop - init() { - self.ops = [] + init(on eventLoop: EventLoop) { + self.eventLoop = eventLoop } func createSession(_ data: SessionData) -> EventLoopFuture { - self.ops.append("create \(data)") + Self.ops.append("create \(data)") return self.eventLoop.makeSucceededFuture(.init(string: "a")) } func readSession(_ sessionID: SessionID) -> EventLoopFuture { - self.ops.append("read \(sessionID)") + Self.ops.append("read \(sessionID)") return self.eventLoop.makeSucceededFuture(SessionData()) } func updateSession(_ sessionID: SessionID, to data: SessionData) -> EventLoopFuture { - self.ops.append("update \(sessionID) to \(data)") + Self.ops.append("update \(sessionID) to \(data)") return self.eventLoop.makeSucceededFuture(sessionID) } func deleteSession(_ sessionID: SessionID) -> EventLoopFuture { - self.ops.append("delete \(sessionID)") + Self.ops.append("delete \(sessionID)") return self.eventLoop.makeSucceededFuture(()) } } - let mockCache = MockKeyedCache() var cookie: HTTPCookies.Value? - let app = Application.create(configure: { s in - s.instance(Sessions.self, mockCache) + s.register(Sessions.self) { c in + return MockKeyedCache(on: c.eventLoop) + } }, routes: { r, c in let sessions = try r.grouped(c.make(SessionsMiddleware.self)) sessions.get("set") { req -> String in @@ -856,24 +921,26 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start() + defer { server.shutdown() } - let tester = try app.testable().inMemory().test(.GET, "/set") { res in + try server.test(.GET, "/set") { res in XCTAssertEqual(res.body.string, "set") cookie = res.headers.setCookie["vapor-session"] XCTAssertNotNil(cookie) - XCTAssertEqual(mockCache.ops, [ + XCTAssertEqual(MockKeyedCache.ops, [ #"create SessionData(storage: ["foo": "bar"])"#, ]) - mockCache.ops = [] + MockKeyedCache.ops = [] } XCTAssertEqual(cookie?.string, "a") var headers = HTTPHeaders() headers.cookie["vapor-session"] = cookie - try tester.test(.GET, "/del", headers: headers) { res in + try server.test(.GET, "/del", headers: headers) { res in XCTAssertEqual(res.body.string, "del") - XCTAssertEqual(mockCache.ops, [ + XCTAssertEqual(MockKeyedCache.ops, [ #"read SessionID(string: "a")"#, #"delete SessionID(string: "a")"# ]) @@ -885,7 +952,7 @@ final class ApplicationTests: XCTestCase { struct TestQueryStringContainer: Content { var name: String } - let req = Request(on: EmbeddedChannel()) + let req = Request(on: EmbeddedEventLoop()) try req.query.encode(TestQueryStringContainer(name: "Vapor Test")) XCTAssertEqual(req.url.query, "name=Vapor%20Test") } @@ -899,7 +966,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().live(port: 8080).test(.GET, "/no-content") { res in + let server = try app.testable().start(method: .running) + defer { server.shutdown() } + + try server.test(.GET, "/no-content") { res in XCTAssertEqual(res.status.code, 204) XCTAssertEqual(res.body.count, 0) } @@ -915,7 +985,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/user") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/user") { res in XCTAssertEqual(res.status, .unsupportedMediaType) } } @@ -929,7 +1002,10 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - try app.testable().inMemory().test(.GET, "/error") { res in + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/error") { res in XCTAssertEqual(res.status, .internalServerError) } } @@ -986,7 +1062,7 @@ final class ApplicationTests: XCTestCase { }) defer { app.shutdown() } - let server = try app.testable().live(port: 8085).start() + let server = try app.testable().start() defer { server.shutdown() } let future = WebSocket.connect( @@ -1055,8 +1131,8 @@ final class ApplicationTests: XCTestCase { promise = app.eventLoopGroup.next().makePromise(of: Void.self) defer { app.shutdown() } - let test = try app.testable().live(port: 8080).start() - defer { test.shutdown() } + let server = try app.testable().start(method: .running) + defer { server.shutdown() } try WebSocket.connect( to: "ws://127.0.0.1:8080/foo", diff --git a/Tests/VaporTests/AuthenticationTests.swift b/Tests/VaporTests/AuthenticationTests.swift index 073731f872..9aa1696763 100755 --- a/Tests/VaporTests/AuthenticationTests.swift +++ b/Tests/VaporTests/AuthenticationTests.swift @@ -9,32 +9,40 @@ final class AuthenticationTests: XCTestCase { struct TestAuthenticator: BearerAuthenticator { typealias User = Test + let eventLoop: EventLoop + + init(on eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + func authenticate(bearer: BearerAuthorization) -> EventLoopFuture { guard bearer.token == "test" else { - return EmbeddedEventLoop().makeSucceededFuture(nil) + return self.eventLoop.makeSucceededFuture(nil) } let test = Test(name: "Vapor") - return EmbeddedEventLoop().makeSucceededFuture(test) + return self.eventLoop.makeSucceededFuture(test) } } let app = Application.create(routes: { r, c in r.grouped([ - TestAuthenticator().middleware(), Test.guardMiddleware() + TestAuthenticator(on: c.eventLoop).middleware(), Test.guardMiddleware() ]).get("test") { req -> String in return try req.requireAuthenticated(Test.self).name } }) defer { app.shutdown() } - try app.testable().inMemory() - .test(.GET, "/test") { res in - XCTAssertEqual(res.status, .unauthorized) - } - .test(.GET, "/test", headers: ["Authorization": "Bearer test"]) { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "Vapor") - } + let server = try app.testable().start() + defer { server.shutdown() } + + try server.test(.GET, "/test") { res in + XCTAssertEqual(res.status, .unauthorized) + } + .test(.GET, "/test", headers: ["Authorization": "Bearer test"]) { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "Vapor") + } } func testBasicAuthenticator() throws { @@ -45,32 +53,40 @@ final class AuthenticationTests: XCTestCase { struct TestAuthenticator: BasicAuthenticator { typealias User = Test + let eventLoop: EventLoop + + init(on eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + func authenticate(basic: BasicAuthorization) -> EventLoopFuture { guard basic.username == "test" && basic.password == "secret" else { - return EmbeddedEventLoop().makeSucceededFuture(nil) + return self.eventLoop.makeSucceededFuture(nil) } let test = Test(name: "Vapor") - return EmbeddedEventLoop().makeSucceededFuture(test) + return self.eventLoop.makeSucceededFuture(test) } } let app = Application.create(routes: { r, c in r.grouped([ - TestAuthenticator().middleware(), Test.guardMiddleware() + TestAuthenticator(on: c.eventLoop).middleware(), Test.guardMiddleware() ]).get("test") { req -> String in return try req.requireAuthenticated(Test.self).name } }) defer { app.shutdown() } + let server = try app.testable().start() + defer { server.shutdown() } + let basic = "test:secret".data(using: .utf8)!.base64EncodedString() - try app.testable().inMemory() - .test(.GET, "/test") { res in - XCTAssertEqual(res.status, .unauthorized) - } - .test(.GET, "/test", headers: ["Authorization": "Basic \(basic)"]) { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "Vapor") + try server.test(.GET, "/test") { res in + XCTAssertEqual(res.status, .unauthorized) + } + .test(.GET, "/test", headers: ["Authorization": "Basic \(basic)"]) { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "Vapor") } } @@ -115,29 +131,31 @@ final class AuthenticationTests: XCTestCase { }) defer { app.shutdown() } + let server = try app.testable().start() + defer { server.shutdown() } + var sessionCookie: HTTPCookies.Value? - try app.testable().inMemory() - .test(.GET, "/test") { res in - XCTAssertEqual(res.status, .unauthorized) - XCTAssertNil(res.headers.firstValue(name: .setCookie)) - } - .test(.GET, "/test", headers: ["Authorization": "Bearer test"]) { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "Vapor") - if - let cookies = HTTPCookies.parse(setCookieHeaders: res.headers[.setCookie]), - let cookie = cookies["vapor-session"] - { - sessionCookie = cookie - } else { - XCTFail("No set cookie header") - } - } - .test(.GET, "/test", headers: ["Cookie": sessionCookie!.serialize(name: "vapor-session")]) { res in - XCTAssertEqual(res.status, .ok) - XCTAssertEqual(res.body.string, "Vapor") - XCTAssertNotNil(res.headers.firstValue(name: .setCookie)) + try server.test(.GET, "/test") { res in + XCTAssertEqual(res.status, .unauthorized) + XCTAssertNil(res.headers.firstValue(name: .setCookie)) + } + .test(.GET, "/test", headers: ["Authorization": "Bearer test"]) { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "Vapor") + if + let cookies = HTTPCookies.parse(setCookieHeaders: res.headers[.setCookie]), + let cookie = cookies["vapor-session"] + { + sessionCookie = cookie + } else { + XCTFail("No set cookie header") } + } + .test(.GET, "/test", headers: ["Cookie": sessionCookie!.serialize(name: "vapor-session")]) { res in + XCTAssertEqual(res.status, .ok) + XCTAssertEqual(res.body.string, "Vapor") + XCTAssertNotNil(res.headers.firstValue(name: .setCookie)) + } } func testMiddlewareConfigExistential() { From 68df41dd5c4f02f55147e785f6b2c07dd3d934e4 Mon Sep 17 00:00:00 2001 From: Tanner Nelson Date: Mon, 26 Aug 2019 10:55:25 -0400 Subject: [PATCH 2/2] merge master, fix request tests, regen linuxmain --- Tests/VaporTests/ApplicationTests.swift | 37 ++++++++++++++++++++++ Tests/VaporTests/RequestTests.swift | 41 ------------------------- Tests/VaporTests/XCTestManifests.swift | 11 +------ 3 files changed, 38 insertions(+), 51 deletions(-) delete mode 100644 Tests/VaporTests/RequestTests.swift diff --git a/Tests/VaporTests/ApplicationTests.swift b/Tests/VaporTests/ApplicationTests.swift index e6a3483c01..bf6942bcd8 100644 --- a/Tests/VaporTests/ApplicationTests.swift +++ b/Tests/VaporTests/ApplicationTests.swift @@ -97,6 +97,43 @@ final class ApplicationTests: XCTestCase { try XCTAssertEqual(request.query.get(String.self, at: "hello"), "world") } + func testQueryGet() throws { + var req: Request + + // + req = Request(method: .GET, url: .init(string: "/path?foo=a"), on: EmbeddedEventLoop()) + + XCTAssertEqual(try req.query.get(String.self, at: "foo"), "a") + XCTAssertThrowsError(try req.query.get(Int.self, at: "foo")) { error in + if case .typeMismatch(_, let context) = error as? DecodingError { + XCTAssertEqual(context.debugDescription, "Data found at 'foo' was not Int") + } else { + XCTFail("Catched error \"\(error)\", but not the expected: \"DecodingError.typeMismatch\"") + } + } + XCTAssertThrowsError(try req.query.get(String.self, at: "bar")) { error in + if case .valueNotFound(_, let context) = error as? DecodingError { + XCTAssertEqual(context.debugDescription, "No String was found at 'bar'") + } else { + XCTFail("Catched error \"\(error)\", but not the expected: \"DecodingError.valueNotFound\"") + } + } + + XCTAssertEqual(req.query[String.self, at: "foo"], "a") + XCTAssertEqual(req.query[String.self, at: "bar"], nil) + + // + req = Request(method: .GET, url: .init(string: "/path"), on: EmbeddedEventLoop()) + XCTAssertThrowsError(try req.query.get(Int.self, at: "foo")) { error in + if let error = error as? Abort { + XCTAssertEqual(error.status, .unsupportedMediaType) + } else { + XCTFail("Catched error \"\(error)\", but not the expected: \"\(Abort(.unsupportedMediaType))\"") + } + } + XCTAssertEqual(req.query[String.self, at: "foo"], nil) + } + func testParameter() throws { let app = Application.create(routes: { r, c in r.get("hello", ":a") { req in diff --git a/Tests/VaporTests/RequestTests.swift b/Tests/VaporTests/RequestTests.swift deleted file mode 100644 index af2c735f31..0000000000 --- a/Tests/VaporTests/RequestTests.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Vapor -import XCTest - -class RequestTests: XCTestCase { - func testQueryGet() throws { - var req: Request - - // - req = Request(method: .GET, url: .init(string: "/path?foo=a"), on: EmbeddedChannel()) - - XCTAssertEqual(try req.query.get(String.self, at: "foo"), "a") - XCTAssertThrowsError(try req.query.get(Int.self, at: "foo")) { error in - if case .typeMismatch(_, let context) = error as? DecodingError { - XCTAssertEqual(context.debugDescription, "Data found at 'foo' was not Int") - } else { - XCTFail("Catched error \"\(error)\", but not the expected: \"DecodingError.typeMismatch\"") - } - } - XCTAssertThrowsError(try req.query.get(String.self, at: "bar")) { error in - if case .valueNotFound(_, let context) = error as? DecodingError { - XCTAssertEqual(context.debugDescription, "No String was found at 'bar'") - } else { - XCTFail("Catched error \"\(error)\", but not the expected: \"DecodingError.valueNotFound\"") - } - } - - XCTAssertEqual(req.query[String.self, at: "foo"], "a") - XCTAssertEqual(req.query[String.self, at: "bar"], nil) - - // - req = Request(method: .GET, url: .init(string: "/path"), on: EmbeddedChannel()) - XCTAssertThrowsError(try req.query.get(Int.self, at: "foo")) { error in - if let error = error as? Abort { - XCTAssertEqual(error.status, .unsupportedMediaType) - } else { - XCTFail("Catched error \"\(error)\", but not the expected: \"\(Abort(.unsupportedMediaType))\"") - } - } - XCTAssertEqual(req.query[String.self, at: "foo"], nil) - } -} diff --git a/Tests/VaporTests/XCTestManifests.swift b/Tests/VaporTests/XCTestManifests.swift index 10231f4dba..6fd6b8f0a5 100644 --- a/Tests/VaporTests/XCTestManifests.swift +++ b/Tests/VaporTests/XCTestManifests.swift @@ -29,6 +29,7 @@ extension ApplicationTests { ("testMultipartEncode", testMultipartEncode), ("testParameter", testParameter), ("testQuery", testQuery), + ("testQueryGet", testQueryGet), ("testQueryStringRunning", testQueryStringRunning), ("testRequestQueryStringPercentEncoding", testRequestQueryStringPercentEncoding), ("testResponseEncodableStatus", testResponseEncodableStatus), @@ -95,15 +96,6 @@ extension MultipartTests { ] } -extension RequestTests { - // DO NOT MODIFY: This is autogenerated, use: - // `swift test --generate-linuxmain` - // to regenerate. - static let __allTests__RequestTests = [ - ("testQueryGet", testQueryGet), - ] -} - extension URLEncodedFormTests { // DO NOT MODIFY: This is autogenerated, use: // `swift test --generate-linuxmain` @@ -152,7 +144,6 @@ public func __allTests() -> [XCTestCaseEntry] { testCase(AuthenticationTests.__allTests__AuthenticationTests), testCase(BCryptTests.__allTests__BCryptTests), testCase(MultipartTests.__allTests__MultipartTests), - testCase(RequestTests.__allTests__RequestTests), testCase(URLEncodedFormTests.__allTests__URLEncodedFormTests), testCase(ValidationTests.__allTests__ValidationTests), ]