1464 lines
53 KiB
Swift
1464 lines
53 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import XCTest
|
|
import Dispatch
|
|
@testable import NIOCore
|
|
import NIOEmbedded
|
|
import NIOPosix
|
|
|
|
enum EventLoopFutureTestError : Error {
|
|
case example
|
|
}
|
|
|
|
class EventLoopFutureTest : XCTestCase {
|
|
func testFutureFulfilledIfHasResult() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let f = EventLoopFuture(eventLoop: eventLoop, value: 5, file: #file, line: #line)
|
|
XCTAssertTrue(f.isFulfilled)
|
|
}
|
|
|
|
func testFutureFulfilledIfHasError() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let f = EventLoopFuture<Void>(eventLoop: eventLoop, error: EventLoopFutureTestError.example, file: #file, line: #line)
|
|
XCTAssertTrue(f.isFulfilled)
|
|
}
|
|
|
|
func testFoldWithMultipleEventLoops() throws {
|
|
let nThreads = 3
|
|
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: nThreads)
|
|
defer {
|
|
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
|
|
}
|
|
|
|
let eventLoop0 = eventLoopGroup.next()
|
|
let eventLoop1 = eventLoopGroup.next()
|
|
let eventLoop2 = eventLoopGroup.next()
|
|
|
|
XCTAssert(eventLoop0 !== eventLoop1)
|
|
XCTAssert(eventLoop1 !== eventLoop2)
|
|
XCTAssert(eventLoop0 !== eventLoop2)
|
|
|
|
let f0: EventLoopFuture<[Int]> = eventLoop0.submit { [0] }
|
|
let f1s: [EventLoopFuture<Int>] = (1...4).map { id in eventLoop1.submit { id } }
|
|
let f2s: [EventLoopFuture<Int>] = (5...8).map { id in eventLoop2.submit { id } }
|
|
|
|
var fN = f0.fold(f1s) { (f1Value: [Int], f2Value: Int) -> EventLoopFuture<[Int]> in
|
|
XCTAssert(eventLoop0.inEventLoop)
|
|
return eventLoop1.makeSucceededFuture(f1Value + [f2Value])
|
|
}
|
|
|
|
fN = fN.fold(f2s) { (f1Value: [Int], f2Value: Int) -> EventLoopFuture<[Int]> in
|
|
XCTAssert(eventLoop0.inEventLoop)
|
|
return eventLoop2.makeSucceededFuture(f1Value + [f2Value])
|
|
}
|
|
|
|
let allValues = try fN.wait()
|
|
XCTAssert(fN.eventLoop === f0.eventLoop)
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertEqual(allValues, [0, 1, 2, 3, 4, 5, 6, 7, 8])
|
|
}
|
|
|
|
func testFoldWithSuccessAndAllSuccesses() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let secondEventLoop = EmbeddedEventLoop()
|
|
let f0 = eventLoop.makeSucceededFuture([0])
|
|
|
|
let futures: [EventLoopFuture<Int>] = (1...5).map { (id: Int) in secondEventLoop.makeSucceededFuture(id) }
|
|
|
|
let fN = f0.fold(futures) { (f1Value: [Int], f2Value: Int) -> EventLoopFuture<[Int]> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return secondEventLoop.makeSucceededFuture(f1Value + [f2Value])
|
|
}
|
|
|
|
let allValues = try fN.wait()
|
|
XCTAssert(fN.eventLoop === f0.eventLoop)
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertEqual(allValues, [0, 1, 2, 3, 4, 5])
|
|
}
|
|
|
|
func testFoldWithSuccessAndOneFailure() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let secondEventLoop = EmbeddedEventLoop()
|
|
let f0: EventLoopFuture<Int> = eventLoop.makeSucceededFuture(0)
|
|
|
|
let promises: [EventLoopPromise<Int>] = (0..<100).map { (_: Int) in secondEventLoop.makePromise() }
|
|
var futures = promises.map { $0.futureResult }
|
|
let failedFuture: EventLoopFuture<Int> = secondEventLoop.makeFailedFuture(E())
|
|
futures.insert(failedFuture, at: futures.startIndex)
|
|
|
|
let fN = f0.fold(futures) { (f1Value: Int, f2Value: Int) -> EventLoopFuture<Int> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return secondEventLoop.makeSucceededFuture(f1Value + f2Value)
|
|
}
|
|
|
|
_ = promises.map { $0.succeed(0) }
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testFoldWithSuccessAndEmptyFutureList() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let f0 = eventLoop.makeSucceededFuture(0)
|
|
|
|
let futures: [EventLoopFuture<Int>] = []
|
|
|
|
let fN = f0.fold(futures) { (f1Value: Int, f2Value: Int) -> EventLoopFuture<Int> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return eventLoop.makeSucceededFuture(f1Value + f2Value)
|
|
}
|
|
|
|
let summationResult = try fN.wait()
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertEqual(summationResult, 0)
|
|
}
|
|
|
|
func testFoldWithFailureAndEmptyFutureList() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let f0: EventLoopFuture<Int> = eventLoop.makeFailedFuture(E())
|
|
|
|
let futures: [EventLoopFuture<Int>] = []
|
|
|
|
let fN = f0.fold(futures) { (f1Value: Int, f2Value: Int) -> EventLoopFuture<Int> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return eventLoop.makeSucceededFuture(f1Value + f2Value)
|
|
}
|
|
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testFoldWithFailureAndAllSuccesses() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let secondEventLoop = EmbeddedEventLoop()
|
|
let f0: EventLoopFuture<Int> = eventLoop.makeFailedFuture(E())
|
|
|
|
let promises: [EventLoopPromise<Int>] = (0..<100).map { (_: Int) in secondEventLoop.makePromise() }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN = f0.fold(futures) { (f1Value: Int, f2Value: Int) -> EventLoopFuture<Int> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return secondEventLoop.makeSucceededFuture(f1Value + f2Value)
|
|
}
|
|
|
|
_ = promises.map { $0.succeed(1) }
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testFoldWithFailureAndAllUnfulfilled() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let secondEventLoop = EmbeddedEventLoop()
|
|
let f0: EventLoopFuture<Int> = eventLoop.makeFailedFuture(E())
|
|
|
|
let promises: [EventLoopPromise<Int>] = (0..<100).map { (_: Int) in secondEventLoop.makePromise() }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN = f0.fold(futures) { (f1Value: Int, f2Value: Int) -> EventLoopFuture<Int> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return secondEventLoop.makeSucceededFuture(f1Value + f2Value)
|
|
}
|
|
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testFoldWithFailureAndAllFailures() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let secondEventLoop = EmbeddedEventLoop()
|
|
let f0: EventLoopFuture<Int> = eventLoop.makeFailedFuture(E())
|
|
|
|
let futures: [EventLoopFuture<Int>] = (0..<100).map { (_: Int) in secondEventLoop.makeFailedFuture(E()) }
|
|
|
|
let fN = f0.fold(futures) { (f1Value: Int, f2Value: Int) -> EventLoopFuture<Int> in
|
|
XCTAssert(eventLoop.inEventLoop)
|
|
return secondEventLoop.makeSucceededFuture(f1Value + f2Value)
|
|
}
|
|
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testAndAllWithEmptyFutureList() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let futures: [EventLoopFuture<Void>] = []
|
|
|
|
let fN = EventLoopFuture.andAllSucceed(futures, on: eventLoop)
|
|
|
|
XCTAssert(fN.isFulfilled)
|
|
}
|
|
|
|
func testAndAllWithAllSuccesses() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promises: [EventLoopPromise<Void>] = (0..<100).map { (_: Int) in eventLoop.makePromise() }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN = EventLoopFuture.andAllSucceed(futures, on: eventLoop)
|
|
_ = promises.map { $0.succeed(()) }
|
|
() = try fN.wait()
|
|
}
|
|
|
|
func testAndAllWithAllFailures() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promises: [EventLoopPromise<Void>] = (0..<100).map { (_: Int) in eventLoop.makePromise() }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN = EventLoopFuture.andAllSucceed(futures, on: eventLoop)
|
|
_ = promises.map { $0.fail(E()) }
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testAndAllWithOneFailure() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var promises: [EventLoopPromise<Void>] = (0..<100).map { (_: Int) in eventLoop.makePromise() }
|
|
_ = promises.map { $0.succeed(()) }
|
|
let failedPromise = eventLoop.makePromise(of: Void.self)
|
|
failedPromise.fail(E())
|
|
promises.append(failedPromise)
|
|
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN = EventLoopFuture.andAllSucceed(futures, on: eventLoop)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testReduceWithAllSuccesses() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promises: [EventLoopPromise<Int>] = (0..<5).map { (_: Int) in eventLoop.makePromise() }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN: EventLoopFuture<[Int]> = EventLoopFuture<[Int]>.reduce(into: [], futures, on: eventLoop) {
|
|
$0.append($1)
|
|
}
|
|
for i in 1...5 {
|
|
promises[i - 1].succeed((i))
|
|
}
|
|
let results = try fN.wait()
|
|
XCTAssertEqual(results, [1, 2, 3, 4, 5])
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
}
|
|
|
|
func testReduceWithOnlyInitialValue() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let futures: [EventLoopFuture<Int>] = []
|
|
|
|
let fN: EventLoopFuture<[Int]> = EventLoopFuture<[Int]>.reduce(into: [], futures, on: eventLoop) {
|
|
$0.append($1)
|
|
}
|
|
|
|
let results = try fN.wait()
|
|
XCTAssertEqual(results, [])
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
}
|
|
|
|
func testReduceWithAllFailures() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promises: [EventLoopPromise<Int>] = (0..<100).map { (_: Int) in eventLoop.makePromise() }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN: EventLoopFuture<Int> = EventLoopFuture<Int>.reduce(0, futures, on: eventLoop, +)
|
|
_ = promises.map { $0.fail(E()) }
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testReduceWithOneFailure() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var promises: [EventLoopPromise<Int>] = (0..<100).map { (_: Int) in eventLoop.makePromise() }
|
|
_ = promises.map { $0.succeed((1)) }
|
|
let failedPromise = eventLoop.makePromise(of: Int.self)
|
|
failedPromise.fail(E())
|
|
promises.append(failedPromise)
|
|
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
let fN: EventLoopFuture<Int> = EventLoopFuture<Int>.reduce(0, futures, on: eventLoop, +)
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testReduceWhichDoesFailFast() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var promises: [EventLoopPromise<Int>] = (0..<100).map { (_: Int) in eventLoop.makePromise() }
|
|
|
|
let failedPromise = eventLoop.makePromise(of: Int.self)
|
|
promises.insert(failedPromise, at: promises.startIndex)
|
|
|
|
let futures = promises.map { $0.futureResult }
|
|
let fN: EventLoopFuture<Int> = EventLoopFuture<Int>.reduce(0, futures, on: eventLoop, +)
|
|
|
|
failedPromise.fail(E())
|
|
|
|
XCTAssertTrue(fN.isFulfilled)
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testReduceIntoWithAllSuccesses() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let futures: [EventLoopFuture<Int>] = [1, 2, 2, 3, 3, 3].map { (id: Int) in eventLoop.makeSucceededFuture(id) }
|
|
|
|
let fN: EventLoopFuture<[Int: Int]> = EventLoopFuture<[Int: Int]>.reduce(into: [:], futures, on: eventLoop) { (freqs, elem) in
|
|
if let value = freqs[elem] {
|
|
freqs[elem] = value + 1
|
|
} else {
|
|
freqs[elem] = 1
|
|
}
|
|
}
|
|
|
|
let results = try fN.wait()
|
|
XCTAssertEqual(results, [1: 1, 2: 2, 3: 3])
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
}
|
|
|
|
func testReduceIntoWithEmptyFutureList() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let futures: [EventLoopFuture<Int>] = []
|
|
|
|
let fN: EventLoopFuture<[Int: Int]> = EventLoopFuture<[Int: Int]>.reduce(into: [:], futures, on: eventLoop) { (freqs, elem) in
|
|
if let value = freqs[elem] {
|
|
freqs[elem] = value + 1
|
|
} else {
|
|
freqs[elem] = 1
|
|
}
|
|
}
|
|
|
|
let results = try fN.wait()
|
|
XCTAssert(results.isEmpty)
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
}
|
|
|
|
func testReduceIntoWithAllFailure() throws {
|
|
struct E: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let futures: [EventLoopFuture<Int>] = [1, 2, 2, 3, 3, 3].map { (id: Int) in eventLoop.makeFailedFuture(E()) }
|
|
|
|
let fN: EventLoopFuture<[Int: Int]> = EventLoopFuture<[Int: Int]>.reduce(into: [:], futures, on: eventLoop) { (freqs, elem) in
|
|
if let value = freqs[elem] {
|
|
freqs[elem] = value + 1
|
|
} else {
|
|
freqs[elem] = 1
|
|
}
|
|
}
|
|
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssert(fN.eventLoop === eventLoop)
|
|
XCTAssertThrowsError(try fN.wait()) { error in
|
|
XCTAssertNotNil(error as? E)
|
|
}
|
|
}
|
|
|
|
func testReduceIntoWithMultipleEventLoops() throws {
|
|
let nThreads = 3
|
|
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: nThreads)
|
|
defer {
|
|
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
|
|
}
|
|
|
|
let eventLoop0 = eventLoopGroup.next()
|
|
let eventLoop1 = eventLoopGroup.next()
|
|
let eventLoop2 = eventLoopGroup.next()
|
|
|
|
XCTAssert(eventLoop0 !== eventLoop1)
|
|
XCTAssert(eventLoop1 !== eventLoop2)
|
|
XCTAssert(eventLoop0 !== eventLoop2)
|
|
|
|
let f0: EventLoopFuture<[Int:Int]> = eventLoop0.submit { [:] }
|
|
let f1s: [EventLoopFuture<Int>] = (1...4).map { id in eventLoop1.submit { id / 2 } }
|
|
let f2s: [EventLoopFuture<Int>] = (5...8).map { id in eventLoop2.submit { id / 2 } }
|
|
|
|
let fN = EventLoopFuture<[Int:Int]>.reduce(into: [:], f1s + f2s, on: eventLoop0) { (freqs, elem) in
|
|
XCTAssert(eventLoop0.inEventLoop)
|
|
if let value = freqs[elem] {
|
|
freqs[elem] = value + 1
|
|
} else {
|
|
freqs[elem] = 1
|
|
}
|
|
}
|
|
|
|
let allValues = try fN.wait()
|
|
XCTAssert(fN.eventLoop === f0.eventLoop)
|
|
XCTAssert(fN.isFulfilled)
|
|
XCTAssertEqual(allValues, [0: 1, 1: 2, 2: 2, 3: 2, 4: 1])
|
|
}
|
|
|
|
func testThenThrowingWhichDoesNotThrow() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var ran = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.map {
|
|
$0.count
|
|
}.flatMapThrowing {
|
|
1 + $0
|
|
}.whenSuccess {
|
|
ran = true
|
|
XCTAssertEqual($0, 6)
|
|
}
|
|
p.succeed("hello")
|
|
XCTAssertTrue(ran)
|
|
}
|
|
|
|
func testThenThrowingWhichDoesThrow() {
|
|
enum DummyError: Error, Equatable {
|
|
case dummyError
|
|
}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var ran = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.map {
|
|
$0.count
|
|
}.flatMapThrowing { (x: Int) throws -> Int in
|
|
XCTAssertEqual(5, x)
|
|
throw DummyError.dummyError
|
|
}.map { (x: Int) -> Int in
|
|
XCTFail("shouldn't have been called")
|
|
return x
|
|
}.whenFailure {
|
|
ran = true
|
|
XCTAssertEqual(.some(DummyError.dummyError), $0 as? DummyError)
|
|
}
|
|
p.succeed("hello")
|
|
XCTAssertTrue(ran)
|
|
}
|
|
|
|
func testflatMapErrorThrowingWhichDoesNotThrow() {
|
|
enum DummyError: Error, Equatable {
|
|
case dummyError
|
|
}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var ran = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.map {
|
|
$0.count
|
|
}.flatMapErrorThrowing {
|
|
XCTAssertEqual(.some(DummyError.dummyError), $0 as? DummyError)
|
|
return 5
|
|
}.flatMapErrorThrowing { (_: Error) in
|
|
XCTFail("shouldn't have been called")
|
|
return 5
|
|
}.whenSuccess {
|
|
ran = true
|
|
XCTAssertEqual($0, 5)
|
|
}
|
|
p.fail(DummyError.dummyError)
|
|
XCTAssertTrue(ran)
|
|
}
|
|
|
|
func testflatMapErrorThrowingWhichDoesThrow() {
|
|
enum DummyError: Error, Equatable {
|
|
case dummyError1
|
|
case dummyError2
|
|
}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var ran = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.map {
|
|
$0.count
|
|
}.flatMapErrorThrowing { (x: Error) throws -> Int in
|
|
XCTAssertEqual(.some(DummyError.dummyError1), x as? DummyError)
|
|
throw DummyError.dummyError2
|
|
}.map { (x: Int) -> Int in
|
|
XCTFail("shouldn't have been called")
|
|
return x
|
|
}.whenFailure {
|
|
ran = true
|
|
XCTAssertEqual(.some(DummyError.dummyError2), $0 as? DummyError)
|
|
}
|
|
p.fail(DummyError.dummyError1)
|
|
XCTAssertTrue(ran)
|
|
}
|
|
|
|
func testOrderOfFutureCompletion() throws {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
var state = 0
|
|
let p: EventLoopPromise<Void> = EventLoopPromise(eventLoop: eventLoop, file: #file, line: #line)
|
|
p.futureResult.map {
|
|
XCTAssertEqual(state, 0)
|
|
state += 1
|
|
}.map {
|
|
XCTAssertEqual(state, 1)
|
|
state += 1
|
|
}.whenSuccess {
|
|
XCTAssertEqual(state, 2)
|
|
state += 1
|
|
}
|
|
p.succeed(())
|
|
XCTAssertTrue(p.futureResult.isFulfilled)
|
|
XCTAssertEqual(state, 3)
|
|
}
|
|
|
|
func testEventLoopHoppingInThen() throws {
|
|
let n = 20
|
|
let elg = MultiThreadedEventLoopGroup(numberOfThreads: n)
|
|
var prev: EventLoopFuture<Int> = elg.next().makeSucceededFuture(0)
|
|
(1..<20).forEach { (i: Int) in
|
|
let p = elg.next().makePromise(of: Int.self)
|
|
prev.flatMap { (i2: Int) -> EventLoopFuture<Int> in
|
|
XCTAssertEqual(i - 1, i2)
|
|
p.succeed(i)
|
|
return p.futureResult
|
|
}.whenSuccess { i2 in
|
|
XCTAssertEqual(i, i2)
|
|
}
|
|
prev = p.futureResult
|
|
}
|
|
XCTAssertEqual(n-1, try prev.wait())
|
|
XCTAssertNoThrow(try elg.syncShutdownGracefully())
|
|
}
|
|
|
|
func testEventLoopHoppingInThenWithFailures() throws {
|
|
enum DummyError: Error {
|
|
case dummy
|
|
}
|
|
let n = 20
|
|
let elg = MultiThreadedEventLoopGroup(numberOfThreads: n)
|
|
var prev: EventLoopFuture<Int> = elg.next().makeSucceededFuture(0)
|
|
(1..<n).forEach { (i: Int) in
|
|
let p = elg.next().makePromise(of: Int.self)
|
|
prev.flatMap { (i2: Int) -> EventLoopFuture<Int> in
|
|
XCTAssertEqual(i - 1, i2)
|
|
if i == n/2 {
|
|
p.fail(DummyError.dummy)
|
|
} else {
|
|
p.succeed(i)
|
|
}
|
|
return p.futureResult
|
|
}.flatMapError { error in
|
|
p.fail(error)
|
|
return p.futureResult
|
|
}.whenSuccess { i2 in
|
|
XCTAssertEqual(i, i2)
|
|
}
|
|
prev = p.futureResult
|
|
}
|
|
XCTAssertThrowsError(try prev.wait()) { error in
|
|
XCTAssertNotNil(error as? DummyError)
|
|
}
|
|
XCTAssertNoThrow(try elg.syncShutdownGracefully())
|
|
}
|
|
|
|
func testEventLoopHoppingAndAll() throws {
|
|
let n = 20
|
|
let elg = MultiThreadedEventLoopGroup(numberOfThreads: n)
|
|
let ps = (0..<n).map { (_: Int) -> EventLoopPromise<Void> in
|
|
elg.next().makePromise()
|
|
}
|
|
let allOfEm = EventLoopFuture.andAllSucceed(ps.map { $0.futureResult }, on: elg.next())
|
|
ps.reversed().forEach { p in
|
|
DispatchQueue.global().async {
|
|
p.succeed(())
|
|
}
|
|
}
|
|
try allOfEm.wait()
|
|
XCTAssertNoThrow(try elg.syncShutdownGracefully())
|
|
}
|
|
|
|
func testEventLoopHoppingAndAllWithFailures() throws {
|
|
enum DummyError: Error { case dummy }
|
|
let n = 20
|
|
let fireBackEl = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
let elg = MultiThreadedEventLoopGroup(numberOfThreads: n)
|
|
let ps = (0..<n).map { (_: Int) -> EventLoopPromise<Void> in
|
|
elg.next().makePromise()
|
|
}
|
|
let allOfEm = EventLoopFuture.andAllSucceed(ps.map { $0.futureResult }, on: fireBackEl.next())
|
|
ps.reversed().enumerated().forEach { idx, p in
|
|
DispatchQueue.global().async {
|
|
if idx == n / 2 {
|
|
p.fail(DummyError.dummy)
|
|
} else {
|
|
p.succeed(())
|
|
}
|
|
}
|
|
}
|
|
XCTAssertThrowsError(try allOfEm.wait()) { error in
|
|
XCTAssertNotNil(error as? DummyError)
|
|
}
|
|
XCTAssertNoThrow(try elg.syncShutdownGracefully())
|
|
XCTAssertNoThrow(try fireBackEl.syncShutdownGracefully())
|
|
}
|
|
|
|
func testFutureInVariousScenarios() throws {
|
|
enum DummyError: Error { case dummy0; case dummy1 }
|
|
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
let el1 = elg.next()
|
|
let el2 = elg.next()
|
|
precondition(el1 !== el2)
|
|
let q1 = DispatchQueue(label: "q1")
|
|
let q2 = DispatchQueue(label: "q2")
|
|
|
|
// this determines which promise is fulfilled first (and (true, true) meaning they race)
|
|
for whoGoesFirst in [(false, true), (true, false), (true, true)] {
|
|
// this determines what EventLoops the Promises are created on
|
|
for eventLoops in [(el1, el1), (el1, el2), (el2, el1), (el2, el2)] {
|
|
// this determines if the promises fail or succeed
|
|
for whoSucceeds in [(false, false), (false, true), (true, false), (true, true)] {
|
|
let p0 = eventLoops.0.makePromise(of: Int.self)
|
|
let p1 = eventLoops.1.makePromise(of: String.self)
|
|
let fAll = p0.futureResult.and(p1.futureResult)
|
|
|
|
// preheat both queues so we have a better chance of racing
|
|
let sem1 = DispatchSemaphore(value: 0)
|
|
let sem2 = DispatchSemaphore(value: 0)
|
|
let g = DispatchGroup()
|
|
q1.async(group: g) {
|
|
sem2.signal()
|
|
sem1.wait()
|
|
}
|
|
q2.async(group: g) {
|
|
sem1.signal()
|
|
sem2.wait()
|
|
}
|
|
g.wait()
|
|
|
|
if whoGoesFirst.0 {
|
|
q1.async {
|
|
if whoSucceeds.0 {
|
|
p0.succeed(7)
|
|
} else {
|
|
p0.fail(DummyError.dummy0)
|
|
}
|
|
if !whoGoesFirst.1 {
|
|
q2.asyncAfter(deadline: .now() + 0.1) {
|
|
if whoSucceeds.1 {
|
|
p1.succeed("hello")
|
|
} else {
|
|
p1.fail(DummyError.dummy1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if whoGoesFirst.1 {
|
|
q2.async {
|
|
if whoSucceeds.1 {
|
|
p1.succeed("hello")
|
|
} else {
|
|
p1.fail(DummyError.dummy1)
|
|
}
|
|
if !whoGoesFirst.0 {
|
|
q1.asyncAfter(deadline: .now() + 0.1) {
|
|
if whoSucceeds.0 {
|
|
p0.succeed(7)
|
|
} else {
|
|
p0.fail(DummyError.dummy0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
do {
|
|
let result = try fAll.wait()
|
|
if !whoSucceeds.0 || !whoSucceeds.1 {
|
|
XCTFail("unexpected success")
|
|
} else {
|
|
XCTAssert((7, "hello") == result)
|
|
}
|
|
} catch let e as DummyError {
|
|
switch e {
|
|
case .dummy0:
|
|
XCTAssertFalse(whoSucceeds.0)
|
|
case .dummy1:
|
|
XCTAssertFalse(whoSucceeds.1)
|
|
}
|
|
} catch {
|
|
XCTFail("unexpected error: \(error)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XCTAssertNoThrow(try elg.syncShutdownGracefully())
|
|
}
|
|
|
|
func testLoopHoppingHelperSuccess() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
let loop1 = group.next()
|
|
let loop2 = group.next()
|
|
XCTAssertFalse(loop1 === loop2)
|
|
|
|
let succeedingPromise = loop1.makePromise(of: Void.self)
|
|
let succeedingFuture = succeedingPromise.futureResult.map {
|
|
XCTAssertTrue(loop1.inEventLoop)
|
|
}.hop(to: loop2).map {
|
|
XCTAssertTrue(loop2.inEventLoop)
|
|
}
|
|
succeedingPromise.succeed(())
|
|
XCTAssertNoThrow(try succeedingFuture.wait())
|
|
}
|
|
|
|
func testLoopHoppingHelperFailure() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
let loop1 = group.next()
|
|
let loop2 = group.next()
|
|
XCTAssertFalse(loop1 === loop2)
|
|
|
|
let failingPromise = loop2.makePromise(of: Void.self)
|
|
let failingFuture = failingPromise.futureResult.flatMapErrorThrowing { error in
|
|
XCTAssertEqual(error as? EventLoopFutureTestError, EventLoopFutureTestError.example)
|
|
XCTAssertTrue(loop2.inEventLoop)
|
|
throw error
|
|
}.hop(to: loop1).recover { error in
|
|
XCTAssertEqual(error as? EventLoopFutureTestError, EventLoopFutureTestError.example)
|
|
XCTAssertTrue(loop1.inEventLoop)
|
|
}
|
|
|
|
failingPromise.fail(EventLoopFutureTestError.example)
|
|
XCTAssertNoThrow(try failingFuture.wait())
|
|
}
|
|
|
|
func testLoopHoppingHelperNoHopping() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
let loop1 = group.next()
|
|
let loop2 = group.next()
|
|
XCTAssertFalse(loop1 === loop2)
|
|
|
|
let noHoppingPromise = loop1.makePromise(of: Void.self)
|
|
let noHoppingFuture = noHoppingPromise.futureResult.hop(to: loop1)
|
|
XCTAssertTrue(noHoppingFuture === noHoppingPromise.futureResult)
|
|
noHoppingPromise.succeed(())
|
|
}
|
|
|
|
func testFlatMapResultHappyPath() {
|
|
let el = EmbeddedEventLoop()
|
|
defer {
|
|
XCTAssertNoThrow(try el.syncShutdownGracefully())
|
|
}
|
|
|
|
let p = el.makePromise(of: Int.self)
|
|
let f = p.futureResult.flatMapResult { (_: Int) in
|
|
return Result<String, Never>.success("hello world")
|
|
}
|
|
p.succeed(1)
|
|
XCTAssertNoThrow(XCTAssertEqual("hello world", try f.wait()))
|
|
}
|
|
|
|
func testFlatMapResultFailurePath() {
|
|
struct DummyError: Error {}
|
|
let el = EmbeddedEventLoop()
|
|
defer {
|
|
XCTAssertNoThrow(try el.syncShutdownGracefully())
|
|
}
|
|
|
|
let p = el.makePromise(of: Int.self)
|
|
let f = p.futureResult.flatMapResult { (_: Int) in
|
|
return Result<Int, Error>.failure(DummyError())
|
|
}
|
|
p.succeed(1)
|
|
XCTAssertThrowsError(try f.wait()) { error in
|
|
XCTAssert(type(of: error) == DummyError.self)
|
|
}
|
|
}
|
|
|
|
func testWhenAllSucceedFailsImmediately() {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
func doTest(promise: EventLoopPromise<[Int]>?) {
|
|
let promises = [group.next().makePromise(of: Int.self),
|
|
group.next().makePromise(of: Int.self)]
|
|
let futures = promises.map { $0.futureResult }
|
|
let futureResult: EventLoopFuture<[Int]>
|
|
|
|
if let promise = promise {
|
|
futureResult = promise.futureResult
|
|
EventLoopFuture.whenAllSucceed(futures, promise: promise)
|
|
} else {
|
|
futureResult = EventLoopFuture.whenAllSucceed(futures, on: group.next())
|
|
}
|
|
|
|
promises[0].fail(EventLoopFutureTestError.example)
|
|
XCTAssertThrowsError(try futureResult.wait()) { error in
|
|
XCTAssert(type(of: error) == EventLoopFutureTestError.self)
|
|
}
|
|
}
|
|
|
|
doTest(promise: nil)
|
|
doTest(promise: group.next().makePromise())
|
|
}
|
|
|
|
func testWhenAllSucceedResolvesAfterFutures() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 6)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
func doTest(promise: EventLoopPromise<[Int]>?) throws {
|
|
let promises = (0..<5).map { _ in group.next().makePromise(of: Int.self) }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
var succeeded = false
|
|
var completedPromises = false
|
|
|
|
let mainFuture: EventLoopFuture<[Int]>
|
|
|
|
if let promise = promise {
|
|
mainFuture = promise.futureResult
|
|
EventLoopFuture.whenAllSucceed(futures, promise: promise)
|
|
} else {
|
|
mainFuture = EventLoopFuture.whenAllSucceed(futures, on: group.next())
|
|
}
|
|
|
|
mainFuture.whenSuccess { _ in
|
|
XCTAssertTrue(completedPromises)
|
|
XCTAssertFalse(succeeded)
|
|
succeeded = true
|
|
}
|
|
|
|
// Should be false, as none of the promises have completed yet
|
|
XCTAssertFalse(succeeded)
|
|
|
|
// complete the first four promises
|
|
for (index, promise) in promises.dropLast().enumerated() {
|
|
promise.succeed(index)
|
|
}
|
|
|
|
// Should still be false, as one promise hasn't completed yet
|
|
XCTAssertFalse(succeeded)
|
|
|
|
// Complete the last promise
|
|
completedPromises = true
|
|
promises.last!.succeed(4)
|
|
|
|
let results = try assertNoThrowWithValue(mainFuture.wait())
|
|
XCTAssertEqual(results, [0, 1, 2, 3, 4])
|
|
}
|
|
|
|
XCTAssertNoThrow(try doTest(promise: nil))
|
|
XCTAssertNoThrow(try doTest(promise: group.next().makePromise()))
|
|
}
|
|
|
|
func testWhenAllSucceedIsIndependentOfFulfillmentOrder() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 6)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
func doTest(promise: EventLoopPromise<[Int]>?) throws {
|
|
let expected = Array(0..<1000)
|
|
let promises = expected.map { _ in group.next().makePromise(of: Int.self) }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
var succeeded = false
|
|
var completedPromises = false
|
|
|
|
let mainFuture: EventLoopFuture<[Int]>
|
|
|
|
if let promise = promise {
|
|
mainFuture = promise.futureResult
|
|
EventLoopFuture.whenAllSucceed(futures, promise: promise)
|
|
} else {
|
|
mainFuture = EventLoopFuture.whenAllSucceed(futures, on: group.next())
|
|
}
|
|
|
|
mainFuture.whenSuccess { _ in
|
|
XCTAssertTrue(completedPromises)
|
|
XCTAssertFalse(succeeded)
|
|
succeeded = true
|
|
}
|
|
|
|
for index in expected.reversed() {
|
|
if index == 0 {
|
|
completedPromises = true
|
|
}
|
|
promises[index].succeed(index)
|
|
}
|
|
|
|
let results = try assertNoThrowWithValue(mainFuture.wait())
|
|
XCTAssertEqual(results, expected)
|
|
}
|
|
|
|
XCTAssertNoThrow(try doTest(promise: nil))
|
|
XCTAssertNoThrow(try doTest(promise: group.next().makePromise()))
|
|
}
|
|
|
|
func testWhenAllCompleteResultsWithFailuresStillSucceed() {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
func doTest(promise: EventLoopPromise<[Result<Bool, Error>]>?) {
|
|
let futures: [EventLoopFuture<Bool>] = [
|
|
group.next().makeFailedFuture(EventLoopFutureTestError.example),
|
|
group.next().makeSucceededFuture(true)
|
|
]
|
|
let future: EventLoopFuture<[Result<Bool, Error>]>
|
|
|
|
if let promise = promise {
|
|
future = promise.futureResult
|
|
EventLoopFuture.whenAllComplete(futures, promise: promise)
|
|
} else {
|
|
future = EventLoopFuture.whenAllComplete(futures, on: group.next())
|
|
}
|
|
|
|
XCTAssertNoThrow(try future.wait())
|
|
}
|
|
|
|
doTest(promise: nil)
|
|
doTest(promise: group.next().makePromise())
|
|
}
|
|
|
|
func testWhenAllCompleteResults() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
func doTest(promise: EventLoopPromise<[Result<Int, Error>]>?) throws {
|
|
let futures: [EventLoopFuture<Int>] = [
|
|
group.next().makeSucceededFuture(3),
|
|
group.next().makeFailedFuture(EventLoopFutureTestError.example),
|
|
group.next().makeSucceededFuture(10),
|
|
group.next().makeFailedFuture(EventLoopFutureTestError.example),
|
|
group.next().makeSucceededFuture(5)
|
|
]
|
|
let future: EventLoopFuture<[Result<Int, Error>]>
|
|
|
|
if let promise = promise {
|
|
future = promise.futureResult
|
|
EventLoopFuture.whenAllComplete(futures, promise: promise)
|
|
} else {
|
|
future = EventLoopFuture.whenAllComplete(futures, on: group.next())
|
|
}
|
|
|
|
let results = try assertNoThrowWithValue(future.wait())
|
|
|
|
XCTAssertEqual(try results[0].get(), 3)
|
|
XCTAssertThrowsError(try results[1].get())
|
|
XCTAssertEqual(try results[2].get(), 10)
|
|
XCTAssertThrowsError(try results[3].get())
|
|
XCTAssertEqual(try results[4].get(), 5)
|
|
}
|
|
|
|
XCTAssertNoThrow(try doTest(promise: nil))
|
|
XCTAssertNoThrow(try doTest(promise: group.next().makePromise()))
|
|
}
|
|
|
|
func testWhenAllCompleteResolvesAfterFutures() throws {
|
|
let group = MultiThreadedEventLoopGroup(numberOfThreads: 6)
|
|
defer {
|
|
XCTAssertNoThrow(try group.syncShutdownGracefully())
|
|
}
|
|
|
|
func doTest(promise: EventLoopPromise<[Result<Int, Error>]>?) throws {
|
|
let promises = (0..<5).map { _ in group.next().makePromise(of: Int.self) }
|
|
let futures = promises.map { $0.futureResult }
|
|
|
|
var succeeded = false
|
|
var completedPromises = false
|
|
|
|
let mainFuture: EventLoopFuture<[Result<Int, Error>]>
|
|
|
|
if let promise = promise {
|
|
mainFuture = promise.futureResult
|
|
EventLoopFuture.whenAllComplete(futures, promise: promise)
|
|
} else {
|
|
mainFuture = EventLoopFuture.whenAllComplete(futures, on: group.next())
|
|
}
|
|
|
|
mainFuture.whenSuccess { _ in
|
|
XCTAssertTrue(completedPromises)
|
|
XCTAssertFalse(succeeded)
|
|
succeeded = true
|
|
}
|
|
|
|
// Should be false, as none of the promises have completed yet
|
|
XCTAssertFalse(succeeded)
|
|
|
|
// complete the first four promises
|
|
for (index, promise) in promises.dropLast().enumerated() {
|
|
promise.succeed(index)
|
|
}
|
|
|
|
// Should still be false, as one promise hasn't completed yet
|
|
XCTAssertFalse(succeeded)
|
|
|
|
// Complete the last promise
|
|
completedPromises = true
|
|
promises.last!.succeed(4)
|
|
|
|
let results = try assertNoThrowWithValue(mainFuture.wait().map { try $0.get() })
|
|
XCTAssertEqual(results, [0, 1, 2, 3, 4])
|
|
}
|
|
|
|
XCTAssertNoThrow(try doTest(promise: nil))
|
|
XCTAssertNoThrow(try doTest(promise: group.next().makePromise()))
|
|
}
|
|
|
|
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 { result in
|
|
assertSuccess(result)
|
|
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 { result in
|
|
assertFailure(result)
|
|
db.close()
|
|
}.map { $0.map { $0.uppercased() }}.wait()) { XCTAssertTrue($0 is DatabaseError) }
|
|
XCTAssertTrue(db.closed)
|
|
}
|
|
|
|
func testPromiseCompletedWithSuccessfulFuture() throws {
|
|
let group = EmbeddedEventLoop()
|
|
let loop = group.next()
|
|
|
|
let future = loop.makeSucceededFuture("yay")
|
|
let promise = loop.makePromise(of: String.self)
|
|
|
|
promise.completeWith(future)
|
|
XCTAssertEqual(try promise.futureResult.wait(), "yay")
|
|
}
|
|
|
|
func testPromiseCompletedWithFailedFuture() throws {
|
|
let group = EmbeddedEventLoop()
|
|
let loop = group.next()
|
|
|
|
let future: EventLoopFuture<EventLoopFutureTestError> = loop.makeFailedFuture(EventLoopFutureTestError.example)
|
|
let promise = loop.makePromise(of: EventLoopFutureTestError.self)
|
|
|
|
promise.completeWith(future)
|
|
XCTAssertThrowsError(try promise.futureResult.wait()) { error in
|
|
XCTAssert(type(of: error) == EventLoopFutureTestError.self)
|
|
}
|
|
}
|
|
|
|
func testPromiseCompletedWithSuccessfulResult() throws {
|
|
let group = EmbeddedEventLoop()
|
|
let loop = group.next()
|
|
|
|
let promise = loop.makePromise(of: Void.self)
|
|
|
|
let result: Result<Void, Error> = .success(())
|
|
promise.completeWith(result)
|
|
XCTAssertNoThrow(try promise.futureResult.wait())
|
|
}
|
|
|
|
func testPromiseCompletedWithFailedResult() throws {
|
|
let group = EmbeddedEventLoop()
|
|
let loop = group.next()
|
|
|
|
let promise = loop.makePromise(of: Void.self)
|
|
|
|
let result: Result<Void, Error> = .failure(EventLoopFutureTestError.example)
|
|
promise.completeWith(result)
|
|
XCTAssertThrowsError(try promise.futureResult.wait()) { error in
|
|
XCTAssert(type(of: error) == EventLoopFutureTestError.self)
|
|
}
|
|
}
|
|
|
|
func testAndAllCompleteWithZeroFutures() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let done = DispatchWorkItem {}
|
|
EventLoopFuture<Void>.andAllComplete([], on: eventLoop).whenComplete { (result: Result<Void, Error>) in
|
|
_ = result.mapError { error -> Error in
|
|
XCTFail("unexpected error \(error)")
|
|
return error
|
|
}
|
|
done.perform()
|
|
}
|
|
done.wait()
|
|
}
|
|
|
|
func testAndAllSucceedWithZeroFutures() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let done = DispatchWorkItem {}
|
|
EventLoopFuture<Void>.andAllSucceed([], on: eventLoop).whenComplete { result in
|
|
_ = result.mapError { error -> Error in
|
|
XCTFail("unexpected error \(error)")
|
|
return error
|
|
}
|
|
done.perform()
|
|
}
|
|
done.wait()
|
|
}
|
|
|
|
func testAndAllCompleteWithPreSucceededFutures() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let succeeded = eventLoop.makeSucceededFuture(())
|
|
|
|
for i in 0..<10 {
|
|
XCTAssertNoThrow(try EventLoopFuture<Void>.andAllComplete(Array(repeating: succeeded, count: i),
|
|
on: eventLoop).wait())
|
|
}
|
|
}
|
|
|
|
func testAndAllCompleteWithPreFailedFutures() {
|
|
struct Dummy: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let failed: EventLoopFuture<Void> = eventLoop.makeFailedFuture(Dummy())
|
|
|
|
for i in 0..<10 {
|
|
XCTAssertNoThrow(try EventLoopFuture<Void>.andAllComplete(Array(repeating: failed, count: i),
|
|
on: eventLoop).wait())
|
|
}
|
|
}
|
|
|
|
func testAndAllCompleteWithMixOfPreSuccededAndNotYetCompletedFutures() {
|
|
struct Dummy: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let succeeded = eventLoop.makeSucceededFuture(())
|
|
let incompletes = [eventLoop.makePromise(of: Void.self), eventLoop.makePromise(of: Void.self),
|
|
eventLoop.makePromise(of: Void.self), eventLoop.makePromise(of: Void.self),
|
|
eventLoop.makePromise(of: Void.self)]
|
|
var futures: [EventLoopFuture<Void>] = []
|
|
|
|
for i in 0..<10 {
|
|
if i % 2 == 0 {
|
|
futures.append(succeeded)
|
|
} else {
|
|
futures.append(incompletes[i/2].futureResult)
|
|
}
|
|
}
|
|
|
|
let overall = EventLoopFuture<Void>.andAllComplete(futures, on: eventLoop)
|
|
XCTAssertFalse(overall.isFulfilled)
|
|
for (idx, incomplete) in incompletes.enumerated() {
|
|
XCTAssertFalse(overall.isFulfilled)
|
|
if idx % 2 == 0 {
|
|
incomplete.succeed(())
|
|
} else {
|
|
incomplete.fail(Dummy())
|
|
}
|
|
}
|
|
XCTAssertNoThrow(try overall.wait())
|
|
}
|
|
|
|
func testWhenAllCompleteWithMixOfPreSuccededAndNotYetCompletedFutures() {
|
|
struct Dummy: Error {}
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let succeeded = eventLoop.makeSucceededFuture(())
|
|
let incompletes = [eventLoop.makePromise(of: Void.self), eventLoop.makePromise(of: Void.self),
|
|
eventLoop.makePromise(of: Void.self), eventLoop.makePromise(of: Void.self),
|
|
eventLoop.makePromise(of: Void.self)]
|
|
var futures: [EventLoopFuture<Void>] = []
|
|
|
|
for i in 0..<10 {
|
|
if i % 2 == 0 {
|
|
futures.append(succeeded)
|
|
} else {
|
|
futures.append(incompletes[i/2].futureResult)
|
|
}
|
|
}
|
|
|
|
let overall = EventLoopFuture<Void>.whenAllComplete(futures, on: eventLoop)
|
|
XCTAssertFalse(overall.isFulfilled)
|
|
for (idx, incomplete) in incompletes.enumerated() {
|
|
XCTAssertFalse(overall.isFulfilled)
|
|
if idx % 2 == 0 {
|
|
incomplete.succeed(())
|
|
} else {
|
|
incomplete.fail(Dummy())
|
|
}
|
|
}
|
|
let expected: [Result<Void, Error>] = [.success(()), .success(()),
|
|
.success(()), .failure(Dummy()),
|
|
.success(()), .success(()),
|
|
.success(()), .failure(Dummy()),
|
|
.success(()), .success(())]
|
|
func assertIsEqual(_ expecteds: [Result<Void, Error>], _ actuals: [Result<Void, Error>]) {
|
|
XCTAssertEqual(expecteds.count, actuals.count, "counts not equal")
|
|
for i in expecteds.indices {
|
|
let expected = expecteds[i]
|
|
let actual = actuals[i]
|
|
switch (expected, actual) {
|
|
case (.success(()), .success(())):
|
|
()
|
|
case (.failure(let le), .failure(let re)):
|
|
XCTAssert(le is Dummy)
|
|
XCTAssert(re is Dummy)
|
|
default:
|
|
XCTFail("\(expecteds) and \(actuals) not equal")
|
|
}
|
|
}
|
|
}
|
|
XCTAssertNoThrow(assertIsEqual(expected, try overall.wait()))
|
|
}
|
|
|
|
func testRepeatedTaskOffEventLoopGroupFuture() throws {
|
|
let elg1: EventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try elg1.syncShutdownGracefully())
|
|
}
|
|
|
|
let elg2: EventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
|
defer {
|
|
XCTAssertNoThrow(try elg2.syncShutdownGracefully())
|
|
}
|
|
|
|
let exitPromise: EventLoopPromise<Void> = elg1.next().makePromise()
|
|
var callNumber = 0
|
|
_ = elg1.next().scheduleRepeatedAsyncTask(initialDelay: .nanoseconds(0), delay: .nanoseconds(0)) { task in
|
|
struct Dummy: Error {}
|
|
|
|
callNumber += 1
|
|
switch callNumber {
|
|
case 1:
|
|
return elg2.next().makeSucceededFuture(())
|
|
case 2:
|
|
task.cancel(promise: exitPromise)
|
|
return elg2.next().makeFailedFuture(Dummy())
|
|
default:
|
|
XCTFail("shouldn't be called \(callNumber)")
|
|
return elg2.next().makeFailedFuture(Dummy())
|
|
}
|
|
}
|
|
|
|
try exitPromise.futureResult.wait()
|
|
}
|
|
|
|
func testEventLoopFutureOrErrorNoThrow() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promise = eventLoop.makePromise(of: Int?.self)
|
|
let result: Result<Int?, Error> = .success(42)
|
|
promise.completeWith(result)
|
|
|
|
XCTAssertEqual(try promise.futureResult.unwrap(orError: EventLoopFutureTestError.example).wait(), 42)
|
|
}
|
|
|
|
func testEventLoopFutureOrThrows() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promise = eventLoop.makePromise(of: Int?.self)
|
|
let result: Result<Int?, Error> = .success(nil)
|
|
promise.completeWith(result)
|
|
|
|
XCTAssertThrowsError(try _ = promise.futureResult.unwrap(orError: EventLoopFutureTestError.example).wait()) { (error) -> Void in
|
|
XCTAssertEqual(error as! EventLoopFutureTestError, EventLoopFutureTestError.example)
|
|
}
|
|
}
|
|
|
|
func testEventLoopFutureOrNoReplacement() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promise = eventLoop.makePromise(of: Int?.self)
|
|
let result: Result<Int?, Error> = .success(42)
|
|
promise.completeWith(result)
|
|
|
|
XCTAssertEqual(try! promise.futureResult.unwrap(orReplace: 41).wait(), 42)
|
|
}
|
|
|
|
func testEventLoopFutureOrReplacement() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promise = eventLoop.makePromise(of: Int?.self)
|
|
let result: Result<Int?, Error> = .success(nil)
|
|
promise.completeWith(result)
|
|
|
|
XCTAssertEqual(try! promise.futureResult.unwrap(orReplace: 42).wait(), 42)
|
|
}
|
|
|
|
func testEventLoopFutureOrNoElse() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promise = eventLoop.makePromise(of: Int?.self)
|
|
let result: Result<Int?, Error> = .success(42)
|
|
promise.completeWith(result)
|
|
|
|
XCTAssertEqual(try! promise.futureResult.unwrap(orElse: { 41 } ).wait(), 42)
|
|
}
|
|
|
|
func testEventLoopFutureOrElse() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let promise = eventLoop.makePromise(of: Int?.self)
|
|
let result: Result<Int?, Error> = .success(4)
|
|
promise.completeWith(result)
|
|
|
|
let x = 2
|
|
XCTAssertEqual(try! promise.futureResult.unwrap(orElse: { x * 2 } ).wait(), 4)
|
|
}
|
|
|
|
func testFlatBlockingMapOnto() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var blockingRan = false
|
|
var nonBlockingRan = false
|
|
p.futureResult.map {
|
|
$0.count
|
|
}.flatMapBlocking(onto: DispatchQueue.global()) { value -> Int in
|
|
sem.wait() // Block in chained EventLoopFuture
|
|
blockingRan = true
|
|
return 1 + value
|
|
}.whenSuccess {
|
|
XCTAssertEqual($0, 6)
|
|
XCTAssertTrue(blockingRan)
|
|
XCTAssertTrue(nonBlockingRan)
|
|
}
|
|
p.succeed("hello")
|
|
|
|
let p2 = eventLoop.makePromise(of: Bool.self)
|
|
p2.futureResult.whenSuccess { _ in
|
|
nonBlockingRan = true
|
|
}
|
|
p2.succeed(true)
|
|
|
|
sem.signal()
|
|
}
|
|
|
|
func testWhenSuccessBlocking() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var nonBlockingRan = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.whenSuccessBlocking(onto: DispatchQueue.global()) {
|
|
sem.wait() // Block in callback
|
|
XCTAssertEqual($0, "hello")
|
|
XCTAssertTrue(nonBlockingRan)
|
|
}
|
|
p.succeed("hello")
|
|
|
|
let p2 = eventLoop.makePromise(of: Bool.self)
|
|
p2.futureResult.whenSuccess { _ in
|
|
nonBlockingRan = true
|
|
}
|
|
p2.succeed(true)
|
|
|
|
sem.signal()
|
|
}
|
|
|
|
func testWhenFailureBlocking() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var nonBlockingRan = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.whenFailureBlocking (onto: DispatchQueue.global()) { err in
|
|
sem.wait() // Block in callback
|
|
XCTAssertEqual(err as! EventLoopFutureTestError, EventLoopFutureTestError.example)
|
|
XCTAssertTrue(nonBlockingRan)
|
|
}
|
|
p.fail(EventLoopFutureTestError.example)
|
|
|
|
let p2 = eventLoop.makePromise(of: Bool.self)
|
|
p2.futureResult.whenSuccess { _ in
|
|
nonBlockingRan = true
|
|
}
|
|
p2.succeed(true)
|
|
|
|
sem.signal()
|
|
}
|
|
|
|
func testWhenCompleteBlockingSuccess() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var nonBlockingRan = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.whenCompleteBlocking (onto: DispatchQueue.global()) { _ in
|
|
sem.wait() // Block in callback
|
|
XCTAssertTrue(nonBlockingRan)
|
|
}
|
|
p.succeed("hello")
|
|
|
|
let p2 = eventLoop.makePromise(of: Bool.self)
|
|
p2.futureResult.whenSuccess { _ in
|
|
nonBlockingRan = true
|
|
}
|
|
p2.succeed(true)
|
|
|
|
sem.signal()
|
|
}
|
|
|
|
|
|
func testWhenCompleteBlockingFailure() {
|
|
let eventLoop = EmbeddedEventLoop()
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var nonBlockingRan = false
|
|
let p = eventLoop.makePromise(of: String.self)
|
|
p.futureResult.whenCompleteBlocking (onto: DispatchQueue.global()) { _ in
|
|
sem.wait() // Block in callback
|
|
XCTAssertTrue(nonBlockingRan)
|
|
}
|
|
p.fail(EventLoopFutureTestError.example)
|
|
|
|
let p2 = eventLoop.makePromise(of: Bool.self)
|
|
p2.futureResult.whenSuccess { _ in
|
|
nonBlockingRan = true
|
|
}
|
|
p2.succeed(true)
|
|
|
|
sem.signal()
|
|
}
|
|
|
|
}
|