/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import XCTest import Deferred @testable import Shared private let timeoutPeriod: TimeInterval = 600 class AsyncReducerTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testSimpleBehaviour() { let expectation = self.expectation(description: #function) happyCase(expectation, combine: simpleAdder) } func testWaitingFillerBehaviour() { let expectation = self.expectation(description: #function) happyCase(expectation, combine: waitingFillingAdder) } func testWaitingFillerAppendingBehaviour() { let expectation = self.expectation(description: #function) appendingCase(expectation, combine: waitingFillingAdder) } func testFailingCombine() { let expectation = self.expectation(description: #function) let combine = { (a: Int, b: Int) -> Deferred> in if a >= 6 { return deferMaybe(TestError()) } return deferMaybe(a + b) } let reducer = AsyncReducer(initialValue: 0, combine: combine) reducer.terminal.upon { res in XCTAssert(res.isFailure) expectation.fulfill() } self.append(reducer, items: 1, 2, 3, 4, 5) waitForExpectations(timeout: timeoutPeriod, handler: nil) } func testFailingAppend() { let expectation = self.expectation(description: #function) let reducer = AsyncReducer(initialValue: 0, combine: simpleAdder) reducer.terminal.upon { res in XCTAssert(res.isSuccess) XCTAssertEqual(res.successValue!, 15) } self.append(reducer, items: 1, 2, 3, 4, 5) delay(0.1) { do { let _ = try reducer.append(6, 7, 8) XCTFail("Can't append to a reducer that's already finished") } catch let error { XCTAssert(true, "Properly received error on finished reducer \(error)") } expectation.fulfill() } waitForExpectations(timeout: timeoutPeriod, handler: nil) } func testAccumulation() { var addDuring: [String] = ["bar", "baz"] var reducer: AsyncReducer<[String: Bool], String>! func combine(_ t: [String: Bool], u: String) -> Deferred> { var out = t out[u] = true // Pretend that some new work arrived while we were handling this. if let nextUp = addDuring.popLast() { let _ = try! reducer.append(nextUp) } return deferMaybe(out) } // Start with 'foo'. reducer = AsyncReducer(initialValue: deferMaybe([:]), combine: combine) let _ = try! reducer.append("foo") // Wait for the result. We should have handled all three by the time this returns. let result = reducer.terminal.value XCTAssertTrue(result.isSuccess) XCTAssertEqual(["foo": true, "bar": true, "baz": true], result.successValue!) } } extension AsyncReducerTests { func happyCase(_ expectation: XCTestExpectation, combine: @escaping (Int, Int) -> Deferred>) { let reducer = AsyncReducer(initialValue: 0, combine: combine) reducer.terminal.upon { res in XCTAssert(res.isSuccess) XCTAssertEqual(res.successValue!, 15) expectation.fulfill() } self.append(reducer, items: 1, 2, 3, 4, 5) waitForExpectations(timeout: timeoutPeriod, handler: nil) } func appendingCase(_ expectation: XCTestExpectation, combine: @escaping (Int, Int) -> Deferred>) { let reducer = AsyncReducer(initialValue: 0, combine: combine) reducer.terminal.upon { res in XCTAssert(res.isSuccess) XCTAssertEqual(res.successValue!, 15) expectation.fulfill() } self.append(reducer, items: 1, 2) delay(0.1) { self.append(reducer, items: 3, 4, 5) } waitForExpectations(timeout: timeoutPeriod, handler: nil) } func append(_ reducer: AsyncReducer, items: Int...) { do { let _ = try reducer.append(items) } catch let error { XCTFail("Append failed with \(error)") } } } class TestError: MaybeErrorType { var description = "Error" } private let serialQueue = DispatchQueue(label: "com.mozilla.test.serial", attributes: []) private let concurrentQueue = DispatchQueue(label: "com.mozilla.test.concurrent", attributes: DispatchQueue.Attributes.concurrent) func delay(_ delay: Double, closure:@escaping () -> Void) { concurrentQueue.asyncAfter( deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure) } private func simpleAdder(_ a: Int, b: Int) -> Deferred> { return deferMaybe(a + b) } private func waitingFillingAdder(_ a: Int, b: Int) -> Deferred> { let deferred = Deferred>() delay(0.1) { deferred.fill(Maybe(success: a + b)) } return deferred }