Skip to content

Commit cd90cfb

Browse files
authored
fix TestScheduler repeating actions
1 parent 5b9869f commit cd90cfb

File tree

3 files changed

+141
-14
lines changed

3 files changed

+141
-14
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/EntwineTest.xcscheme

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@
4242
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
4343
shouldUseLaunchSchemeArgsEnv = "YES">
4444
<Testables>
45+
<TestableReference
46+
skipped = "NO">
47+
<BuildableReference
48+
BuildableIdentifier = "primary"
49+
BlueprintIdentifier = "EntwineTestTests"
50+
BuildableName = "EntwineTestTests"
51+
BlueprintName = "EntwineTestTests"
52+
ReferencedContainer = "container:">
53+
</BuildableReference>
54+
</TestableReference>
4555
</Testables>
4656
</TestAction>
4757
<LaunchAction

Sources/EntwineTest/TestScheduler/TestScheduler.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ public class TestScheduler {
5656
}
5757

5858
private var currentTime = VirtualTime(0)
59+
private let maxTime: VirtualTime
5960
private var lastTaskId = -1
6061
private var schedulerQueue: PriorityQueue<TestSchedulerTask>
6162

6263
/// Initialises the scheduler with the given commencement time
63-
public init(initialClock: VirtualTime = 0) {
64+
///
65+
/// - Parameters:
66+
/// - initialClock: The VirtualTime at which the scheduler will start
67+
/// - maxClock: The VirtualTime ceiling after which the scheduler will cease to process tasks
68+
public init(initialClock: VirtualTime = 0, maxClock: VirtualTime = 100_000) {
6469
self.schedulerQueue = PriorityQueue(ascending: true, startingValues: [])
6570
self.currentTime = initialClock
71+
self.maxTime = maxClock
6672
}
6773

6874
/// Schedules the creation and subscription of an arbitrary `Publisher` to a `TestableSubscriber`, and
@@ -149,11 +155,27 @@ public class TestScheduler {
149155
/// more actions remain.
150156
public func resume() {
151157
while let next = findNext() {
158+
guard next.time <= maxTime else {
159+
print("""
160+
⚠️ TestScheduler maxClock (\(maxTime)) reached. Scheduler aborted with \(schedulerQueue.count) remaining tasks.
161+
""")
162+
break
163+
}
152164
if next.time > currentTime {
153165
currentTime = next.time
154166
}
155-
next.action()
156167
schedulerQueue.remove(next)
168+
if next.interval > 0 {
169+
schedulerQueue.push(
170+
.init(
171+
id: next.id,
172+
time: now + max(minimumTolerance, next.interval),
173+
interval: next.interval,
174+
action: next.action
175+
)
176+
)
177+
}
178+
next.action()
157179
}
158180
}
159181

@@ -185,15 +207,18 @@ extension TestScheduler: Scheduler {
185207
public var minimumTolerance: VirtualTimeInterval { 1 }
186208

187209
public func schedule(options: Never?, _ action: @escaping () -> Void) {
188-
schedulerQueue.push(TestSchedulerTask(id: nextTaskId(), time: currentTime, action: action))
210+
schedulerQueue.push(
211+
TestSchedulerTask(id: nextTaskId(), time: currentTime, interval: 0, action: action))
189212
}
190213

191214
public func schedule(after date: VirtualTime, tolerance: VirtualTimeInterval, options: Never?, _ action: @escaping () -> Void) {
192-
schedulerQueue.push(TestSchedulerTask(id: nextTaskId(), time: date, action: action))
215+
schedulerQueue.push(
216+
TestSchedulerTask(id: nextTaskId(), time: date, interval: 0, action: action))
193217
}
194218

195219
public func schedule(after date: VirtualTime, interval: VirtualTimeInterval, tolerance: VirtualTimeInterval, options: Never?, _ action: @escaping () -> Void) -> Cancellable {
196-
let task = TestSchedulerTask(id: nextTaskId(), time: date, action: action)
220+
let task = TestSchedulerTask(
221+
id: nextTaskId(), time: date, interval: interval, action: action)
197222
schedulerQueue.push(task)
198223
return AnyCancellable {
199224
self.schedulerQueue.remove(task)
@@ -209,11 +234,13 @@ struct TestSchedulerTask {
209234

210235
let id: Int
211236
let time: VirtualTime
237+
let interval: VirtualTimeInterval
212238
let action: Action
213239

214-
init(id: Int, time: VirtualTime, action: @escaping Action) {
240+
init(id: Int, time: VirtualTime, interval: VirtualTimeInterval, action: @escaping Action) {
215241
self.id = id
216242
self.time = time
243+
self.interval = interval
217244
self.action = action
218245
}
219246
}

Tests/EntwineTestTests/TestSchedulerTests.swift

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ final class TestSchedulerTests: XCTestCase {
3434

3535
// this should order by time, then id
3636

37-
let t0 = TestSchedulerTask(id: 0, time: 200, action: {})
38-
let t1 = TestSchedulerTask(id: 1, time: 200, action: {})
39-
let t2 = TestSchedulerTask(id: 2, time: 300, action: {})
40-
let t3 = TestSchedulerTask(id: 3, time: 300, action: {})
41-
let t4 = TestSchedulerTask(id: 4, time: 400, action: {})
42-
let t5 = TestSchedulerTask(id: 5, time: 400, action: {})
43-
let t6 = TestSchedulerTask(id: 6, time: 100, action: {})
44-
let t7 = TestSchedulerTask(id: 7, time: 100, action: {})
37+
let t0 = TestSchedulerTask(id: 0, time: 200, interval: 0, action: {})
38+
let t1 = TestSchedulerTask(id: 1, time: 200, interval: 0, action: {})
39+
let t2 = TestSchedulerTask(id: 2, time: 300, interval: 0, action: {})
40+
let t3 = TestSchedulerTask(id: 3, time: 300, interval: 0, action: {})
41+
let t4 = TestSchedulerTask(id: 4, time: 400, interval: 0, action: {})
42+
let t5 = TestSchedulerTask(id: 5, time: 400, interval: 0, action: {})
43+
let t6 = TestSchedulerTask(id: 6, time: 100, interval: 0, action: {})
44+
let t7 = TestSchedulerTask(id: 7, time: 100, interval: 0, action: {})
4545

4646
let unsorted = [t0, t1, t2, t3, t4, t5, t6, t7,]
4747
let sorted = [t6, t7, t0, t1, t2, t3, t4, t5,]
@@ -208,6 +208,90 @@ final class TestSchedulerTests: XCTestCase {
208208

209209
XCTAssertEqual(expected, results.recordedOutput)
210210
}
211+
212+
func testSchedulesAndCancelsRepeatingTask() {
213+
214+
let subject = TestScheduler(initialClock: 0)
215+
let publisher1 = PassthroughSubject<VirtualTime, Never>()
216+
let results = subject.createTestableSubscriber(VirtualTime.self, Never.self)
217+
218+
subject.schedule(after: 100, { publisher1.subscribe(results) })
219+
let cancellable = subject.schedule(
220+
after: 200,
221+
interval: 2,
222+
tolerance: subject.minimumTolerance,
223+
options: nil,
224+
{ publisher1.send(subject.now) }
225+
)
226+
subject.schedule(after: 210, { cancellable.cancel() })
227+
subject.resume()
228+
229+
let expected: TestSequence<VirtualTime, Never> = [
230+
(100, .subscription),
231+
(200, .input(200)),
232+
(202, .input(202)),
233+
(204, .input(204)),
234+
(206, .input(206)),
235+
(208, .input(208)),
236+
(210, .input(210)),
237+
]
238+
239+
XCTAssertEqual(expected, results.recordedOutput)
240+
}
241+
242+
func testIgnoresTasksAfterMaxClock() {
243+
let subject = TestScheduler(initialClock: 0, maxClock: 500)
244+
let publisher1 = PassthroughSubject<VirtualTime, Never>()
245+
let results = subject.createTestableSubscriber(VirtualTime.self, Never.self)
246+
247+
subject.schedule(after: 100, { publisher1.subscribe(results) })
248+
let cancellable = subject.schedule(
249+
after: 0,
250+
interval: 100,
251+
tolerance: subject.minimumTolerance,
252+
options: nil,
253+
{ publisher1.send(subject.now) }
254+
)
255+
subject.resume()
256+
cancellable.cancel()
257+
258+
let expected: TestSequence<VirtualTime, Never> = [
259+
(100, .subscription),
260+
(100, .input(100)),
261+
(200, .input(200)),
262+
(300, .input(300)),
263+
(400, .input(400)),
264+
(500, .input(500)),
265+
]
266+
267+
XCTAssertEqual(expected, results.recordedOutput)
268+
}
269+
270+
func testRemovesTaskCancelledWithinOwnAction() {
271+
let subject = TestScheduler(initialClock: 0, maxClock: 500)
272+
let publisher1 = PassthroughSubject<VirtualTime, Never>()
273+
let results = subject.createTestableSubscriber(VirtualTime.self, Never.self)
274+
var cancellables = Set<AnyCancellable>()
275+
276+
subject.schedule(after: 100, { publisher1.subscribe(results) })
277+
subject.schedule(
278+
after: 200,
279+
interval: 100,
280+
tolerance: subject.minimumTolerance,
281+
options: nil) {
282+
publisher1.send(subject.now)
283+
cancellables.removeAll()
284+
}
285+
.store(in: &cancellables)
286+
subject.resume()
287+
288+
let expected: TestSequence<VirtualTime, Never> = [
289+
(100, .subscription),
290+
(200, .input(200))
291+
]
292+
293+
XCTAssertEqual(expected, results.recordedOutput)
294+
}
211295

212296
static var allTests = [
213297
("testSchedulerTasksSortSensibly", testSchedulerTasksSortSensibly),
@@ -216,5 +300,11 @@ final class TestSchedulerTests: XCTestCase {
216300
("testSchedulerInvokesDeferredTask", testSchedulerInvokesDeferredTask),
217301
("testSchedulerInvokesDeferredTaskScheduledForPastImmediately", testSchedulerInvokesDeferredTaskScheduledForPastImmediately),
218302
("testSchedulerRemovesCancelledTasks", testSchedulerRemovesCancelledTasks),
303+
("testSchedulerQueues", testSchedulerQueues),
304+
("testFiresEventsScheduledBeforeStartCalled", testFiresEventsScheduledBeforeStartCalled),
305+
("testTrampolinesImmediatelyScheduledTasks", testTrampolinesImmediatelyScheduledTasks),
306+
("testSchedulesRepeatingTask", testSchedulesAndCancelsRepeatingTask),
307+
("testIgnoresTasksAfterMaxClock", testIgnoresTasksAfterMaxClock),
308+
("testRemovesTaskCancelledWithinOwnAction", testRemovesTaskCancelledWithinOwnAction)
219309
]
220310
}

0 commit comments

Comments
 (0)