From 4d9771ab5dfe8e3eca339b46cb84318c48acb5e2 Mon Sep 17 00:00:00 2001 From: Romain Pouclet Date: Thu, 25 Apr 2019 13:14:40 -0700 Subject: [PATCH 1/3] Add an `always` method on EventLoopFuture Motivation: https://github.com/apple/swift-nio/issues/665 Modifications: * Add `always` in EventLoopFuture.swift * Add tests Result: NIO Users will be able to use `.always` to, well, always run an action wether the future succeed or not. --- Sources/NIO/EventLoopFuture.swift | 15 +++++++ .../NIOTests/EventLoopFutureTest+XCTest.swift | 2 + Tests/NIOTests/EventLoopFutureTest.swift | 40 +++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/Sources/NIO/EventLoopFuture.swift b/Sources/NIO/EventLoopFuture.swift index a3c17477f4..d76a234f93 100644 --- a/Sources/NIO/EventLoopFuture.swift +++ b/Sources/NIO/EventLoopFuture.swift @@ -1267,3 +1267,18 @@ func executeAndComplete(_ promise: EventLoopPromise?, _ body: () t promise?.fail(e) } } + + +extension EventLoopFuture { + /// Adds an observer callback to this `EventLoopFuture` that is called when the + /// `EventLoopFuture` has any result. + /// + /// - parameters: + /// - callback: the callback that is called when the `EventLoopFuture` is fulfilled. + /// - returns: the current `EventLoopFuture` + public func always(_ callback: @escaping () -> Void) -> EventLoopFuture { + self.whenComplete { _ in callback() } + return self + } + +} diff --git a/Tests/NIOTests/EventLoopFutureTest+XCTest.swift b/Tests/NIOTests/EventLoopFutureTest+XCTest.swift index 1db1a5b39d..73102ebc1f 100644 --- a/Tests/NIOTests/EventLoopFutureTest+XCTest.swift +++ b/Tests/NIOTests/EventLoopFutureTest+XCTest.swift @@ -70,6 +70,8 @@ extension EventLoopFutureTest { ("testWhenAllCompleteResultsWithFailuresStillSucceed", testWhenAllCompleteResultsWithFailuresStillSucceed), ("testWhenAllCompleteResults", testWhenAllCompleteResults), ("testWhenAllCompleteResolvesAfterFutures", testWhenAllCompleteResolvesAfterFutures), + ("testAlways", testAlways), + ("testAlwaysWithFailingPromise", testAlwaysWithFailingPromise), ] } } diff --git a/Tests/NIOTests/EventLoopFutureTest.swift b/Tests/NIOTests/EventLoopFutureTest.swift index 5a3258b1d6..4261a9bb58 100644 --- a/Tests/NIOTests/EventLoopFutureTest.swift +++ b/Tests/NIOTests/EventLoopFutureTest.swift @@ -1015,4 +1015,44 @@ class EventLoopFutureTest : XCTestCase { let results = try assertNoThrowWithValue(mainFuture.wait().map { try $0.get() }) XCTAssertEqual(results, [0, 1, 2, 3, 4]) } + + struct DatabaseError: Error {} + struct Database { + let query: () -> EventLoopFuture<[String]> + + var closed = false + + init(query: @escaping () -> EventLoopFuture<[String]>) { + self.query = query + } + + func runQuery() -> EventLoopFuture<[String]> { + return query() + } + + mutating func close() { + self.closed = true + } + } + + func testAlways() throws { + let group = EmbeddedEventLoop() + let loop = group.next() + var db = Database { loop.makeSucceededFuture(["Item 1", "Item 2", "Item 3"]) } + + XCTAssertFalse(db.closed) + let _ = try assertNoThrowWithValue(db.runQuery().always { db.close() }.map { $0.map { $0.uppercased() }}.wait()) + XCTAssertTrue(db.closed) + } + + func testAlwaysWithFailingPromise() throws { + let group = EmbeddedEventLoop() + let loop = group.next() + var db = Database { loop.makeFailedFuture(DatabaseError()) } + + XCTAssertFalse(db.closed) + let _ = try XCTAssertThrowsError(db.runQuery().always { db.close() }.map { $0.map { $0.uppercased() }}.wait()) { XCTAssertTrue($0 is DatabaseError) } + XCTAssertTrue(db.closed) + + } } From 839fe1172dc5d2132101127417f1767bea21966c Mon Sep 17 00:00:00 2001 From: Romain Pouclet Date: Wed, 8 May 2019 15:37:22 -0700 Subject: [PATCH 2/3] Pass the result as argument of the always closure Motivation: Can be useful to developers and is consistent with the rest of the codebase Modifications: Add `Result` to the callback Result: Result is available --- Sources/NIO/EventLoopFuture.swift | 4 ++-- Tests/NIOTests/EventLoopFutureTest.swift | 15 ++++++++++----- Tests/NIOTests/TestUtils.swift | 8 ++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Sources/NIO/EventLoopFuture.swift b/Sources/NIO/EventLoopFuture.swift index d76a234f93..e6a3f9f335 100644 --- a/Sources/NIO/EventLoopFuture.swift +++ b/Sources/NIO/EventLoopFuture.swift @@ -1276,8 +1276,8 @@ extension EventLoopFuture { /// - parameters: /// - callback: the callback that is called when the `EventLoopFuture` is fulfilled. /// - returns: the current `EventLoopFuture` - public func always(_ callback: @escaping () -> Void) -> EventLoopFuture { - self.whenComplete { _ in callback() } + public func always(_ callback: @escaping (Result) -> Void) -> EventLoopFuture { + self.whenComplete { result in callback(result) } return self } diff --git a/Tests/NIOTests/EventLoopFutureTest.swift b/Tests/NIOTests/EventLoopFutureTest.swift index 4261a9bb58..c9b3ab5b1e 100644 --- a/Tests/NIOTests/EventLoopFutureTest.swift +++ b/Tests/NIOTests/EventLoopFutureTest.swift @@ -1021,7 +1021,7 @@ class EventLoopFutureTest : XCTestCase { let query: () -> EventLoopFuture<[String]> var closed = false - + init(query: @escaping () -> EventLoopFuture<[String]>) { self.query = query } @@ -1041,7 +1041,10 @@ class EventLoopFutureTest : XCTestCase { var db = Database { loop.makeSucceededFuture(["Item 1", "Item 2", "Item 3"]) } XCTAssertFalse(db.closed) - let _ = try assertNoThrowWithValue(db.runQuery().always { db.close() }.map { $0.map { $0.uppercased() }}.wait()) + let _ = try assertNoThrowWithValue(db.runQuery().always { result in + assertSuccess(result) + db.close() + }.map { $0.map { $0.uppercased() }}.wait()) XCTAssertTrue(db.closed) } @@ -1049,10 +1052,12 @@ class EventLoopFutureTest : XCTestCase { let group = EmbeddedEventLoop() let loop = group.next() var db = Database { loop.makeFailedFuture(DatabaseError()) } - + XCTAssertFalse(db.closed) - let _ = try XCTAssertThrowsError(db.runQuery().always { db.close() }.map { $0.map { $0.uppercased() }}.wait()) { XCTAssertTrue($0 is DatabaseError) } + let _ = try XCTAssertThrowsError(db.runQuery().always { result in + assertFailure(result) + db.close() + }.map { $0.map { $0.uppercased() }}.wait()) { XCTAssertTrue($0 is DatabaseError) } XCTAssertTrue(db.closed) - } } diff --git a/Tests/NIOTests/TestUtils.swift b/Tests/NIOTests/TestUtils.swift index 3b7de71993..6530595bcc 100644 --- a/Tests/NIOTests/TestUtils.swift +++ b/Tests/NIOTests/TestUtils.swift @@ -250,3 +250,11 @@ func getBoolSocketOption(channel: Channel, level: IntTyp file: file, line: line).wait() != 0 } + +func assertSuccess(_ result: Result, file: StaticString = #file, line: UInt = #line) { + guard case .success = result else { return XCTFail("Expected result to be successful", file: file, line: line) } +} + +func assertFailure(_ result: Result, file: StaticString = #file, line: UInt = #line) { + guard case .failure = result else { return XCTFail("Expected result to be a failure", file: file, line: line) } +} From f22c1cbee5971d71bcb883af0de34607737caf02 Mon Sep 17 00:00:00 2001 From: Johannes Weiss Date: Thu, 9 May 2019 15:04:29 +0100 Subject: [PATCH 3/3] Update EventLoopFuture.swift --- Sources/NIO/EventLoopFuture.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/NIO/EventLoopFuture.swift b/Sources/NIO/EventLoopFuture.swift index e6a3f9f335..35b661fbef 100644 --- a/Sources/NIO/EventLoopFuture.swift +++ b/Sources/NIO/EventLoopFuture.swift @@ -1280,5 +1280,4 @@ extension EventLoopFuture { self.whenComplete { result in callback(result) } return self } - }