Revision control

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
import XCTest
6
@testable import Shared
7
8
private let timeoutPeriod: TimeInterval = 600
9
10
class AsyncReducerTests: XCTestCase {
11
override func setUp() {
12
super.setUp()
13
// Put setup code here. This method is called before the invocation of each test method in the class.
14
}
15
16
override func tearDown() {
17
// Put teardown code here. This method is called after the invocation of each test method in the class.
18
super.tearDown()
19
}
20
21
func testSimpleBehaviour() {
22
let expectation = self.expectation(description: #function)
23
happyCase(expectation, combine: simpleAdder)
24
}
25
26
func testWaitingFillerBehaviour() {
27
let expectation = self.expectation(description: #function)
28
happyCase(expectation, combine: waitingFillingAdder)
29
}
30
31
func testWaitingFillerAppendingBehaviour() {
32
let expectation = self.expectation(description: #function)
33
appendingCase(expectation, combine: waitingFillingAdder)
34
}
35
36
func testFailingCombine() {
37
let expectation = self.expectation(description: #function)
38
let combine = { (a: Int, b: Int) -> Deferred<Maybe<Int>> in
39
if a >= 6 {
40
return deferMaybe(TestError())
41
}
42
return deferMaybe(a + b)
43
}
44
let reducer = AsyncReducer(initialValue: 0, combine: combine)
45
reducer.terminal.upon { res in
46
XCTAssert(res.isFailure)
47
expectation.fulfill()
48
}
49
50
self.append(reducer, items: 1, 2, 3, 4, 5)
51
waitForExpectations(timeout: timeoutPeriod, handler: nil)
52
}
53
54
func testFailingAppend() {
55
let expectation = self.expectation(description: #function)
56
57
let reducer = AsyncReducer(initialValue: 0, combine: simpleAdder)
58
reducer.terminal.upon { res in
59
XCTAssert(res.isSuccess)
60
XCTAssertEqual(res.successValue!, 15)
61
}
62
63
self.append(reducer, items: 1, 2, 3, 4, 5)
64
65
delay(0.1) {
66
do {
67
let _ = try reducer.append(6, 7, 8)
68
XCTFail("Can't append to a reducer that's already finished")
69
} catch let error {
70
XCTAssert(true, "Properly received error on finished reducer \(error)")
71
}
72
expectation.fulfill()
73
}
74
75
waitForExpectations(timeout: timeoutPeriod, handler: nil)
76
}
77
78
func testAccumulation() {
79
var addDuring: [String] = ["bar", "baz"]
80
var reducer: AsyncReducer<[String: Bool], String>!
81
82
func combine(_ t: [String: Bool], u: String) -> Deferred<Maybe<[String: Bool]>> {
83
var out = t
84
out[u] = true
85
86
// Pretend that some new work arrived while we were handling this.
87
if let nextUp = addDuring.popLast() {
88
let _ = try! reducer.append(nextUp)
89
}
90
91
return deferMaybe(out)
92
}
93
94
// Start with 'foo'.
95
reducer = AsyncReducer(initialValue: deferMaybe([:]), combine: combine)
96
let _ = try! reducer.append("foo")
97
98
// Wait for the result. We should have handled all three by the time this returns.
99
let result = reducer.terminal.value
100
XCTAssertTrue(result.isSuccess)
101
XCTAssertEqual(["foo": true, "bar": true, "baz": true], result.successValue!)
102
}
103
}
104
105
extension AsyncReducerTests {
106
func happyCase(_ expectation: XCTestExpectation, combine: @escaping (Int, Int) -> Deferred<Maybe<Int>>) {
107
let reducer = AsyncReducer(initialValue: 0, combine: combine)
108
reducer.terminal.upon { res in
109
XCTAssert(res.isSuccess)
110
XCTAssertEqual(res.successValue!, 15)
111
expectation.fulfill()
112
}
113
114
self.append(reducer, items: 1, 2, 3, 4, 5)
115
waitForExpectations(timeout: timeoutPeriod, handler: nil)
116
}
117
118
func appendingCase(_ expectation: XCTestExpectation, combine: @escaping (Int, Int) -> Deferred<Maybe<Int>>) {
119
let reducer = AsyncReducer(initialValue: 0, combine: combine)
120
reducer.terminal.upon { res in
121
XCTAssert(res.isSuccess)
122
XCTAssertEqual(res.successValue!, 15)
123
expectation.fulfill()
124
}
125
126
self.append(reducer, items: 1, 2)
127
128
delay(0.1) {
129
self.append(reducer, items: 3, 4, 5)
130
}
131
waitForExpectations(timeout: timeoutPeriod, handler: nil)
132
}
133
134
func append(_ reducer: AsyncReducer<Int, Int>, items: Int...) {
135
do {
136
let _ = try reducer.append(items)
137
} catch let error {
138
XCTFail("Append failed with \(error)")
139
}
140
}
141
}
142
143
class TestError: MaybeErrorType {
144
var description = "Error"
145
}
146
147
private let serialQueue = DispatchQueue(label: "com.mozilla.test.serial", attributes: [])
148
private let concurrentQueue = DispatchQueue(label: "com.mozilla.test.concurrent", attributes: DispatchQueue.Attributes.concurrent)
149
150
func delay(_ delay: Double, closure:@escaping () -> Void) {
151
concurrentQueue.asyncAfter(
152
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
153
}
154
155
private func simpleAdder(_ a: Int, b: Int) -> Deferred<Maybe<Int>> {
156
return deferMaybe(a + b)
157
}
158
159
private func waitingFillingAdder(_ a: Int, b: Int) -> Deferred<Maybe<Int>> {
160
let deferred = Deferred<Maybe<Int>>()
161
delay(0.1) {
162
deferred.fill(Maybe(success: a + b))
163
}
164
return deferred
165
}
166