Skip to content

Commit

Permalink
fix TestScheduler repeating actions
Browse files Browse the repository at this point in the history
  • Loading branch information
tcldr authored Jul 14, 2020
1 parent 5b9869f commit cd90cfb
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 14 deletions.
10 changes: 10 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/EntwineTest.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EntwineTestTests"
BuildableName = "EntwineTestTests"
BlueprintName = "EntwineTestTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
39 changes: 33 additions & 6 deletions Sources/EntwineTest/TestScheduler/TestScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ public class TestScheduler {
}

private var currentTime = VirtualTime(0)
private let maxTime: VirtualTime
private var lastTaskId = -1
private var schedulerQueue: PriorityQueue<TestSchedulerTask>

/// Initialises the scheduler with the given commencement time
public init(initialClock: VirtualTime = 0) {
///
/// - Parameters:
/// - initialClock: The VirtualTime at which the scheduler will start
/// - maxClock: The VirtualTime ceiling after which the scheduler will cease to process tasks
public init(initialClock: VirtualTime = 0, maxClock: VirtualTime = 100_000) {
self.schedulerQueue = PriorityQueue(ascending: true, startingValues: [])
self.currentTime = initialClock
self.maxTime = maxClock
}

/// Schedules the creation and subscription of an arbitrary `Publisher` to a `TestableSubscriber`, and
Expand Down Expand Up @@ -149,11 +155,27 @@ public class TestScheduler {
/// more actions remain.
public func resume() {
while let next = findNext() {
guard next.time <= maxTime else {
print("""
⚠️ TestScheduler maxClock (\(maxTime)) reached. Scheduler aborted with \(schedulerQueue.count) remaining tasks.
""")
break
}
if next.time > currentTime {
currentTime = next.time
}
next.action()
schedulerQueue.remove(next)
if next.interval > 0 {
schedulerQueue.push(
.init(
id: next.id,
time: now + max(minimumTolerance, next.interval),
interval: next.interval,
action: next.action
)
)
}
next.action()
}
}

Expand Down Expand Up @@ -185,15 +207,18 @@ extension TestScheduler: Scheduler {
public var minimumTolerance: VirtualTimeInterval { 1 }

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

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

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

let id: Int
let time: VirtualTime
let interval: VirtualTimeInterval
let action: Action

init(id: Int, time: VirtualTime, action: @escaping Action) {
init(id: Int, time: VirtualTime, interval: VirtualTimeInterval, action: @escaping Action) {
self.id = id
self.time = time
self.interval = interval
self.action = action
}
}
Expand Down
106 changes: 98 additions & 8 deletions Tests/EntwineTestTests/TestSchedulerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ final class TestSchedulerTests: XCTestCase {

// this should order by time, then id

let t0 = TestSchedulerTask(id: 0, time: 200, action: {})
let t1 = TestSchedulerTask(id: 1, time: 200, action: {})
let t2 = TestSchedulerTask(id: 2, time: 300, action: {})
let t3 = TestSchedulerTask(id: 3, time: 300, action: {})
let t4 = TestSchedulerTask(id: 4, time: 400, action: {})
let t5 = TestSchedulerTask(id: 5, time: 400, action: {})
let t6 = TestSchedulerTask(id: 6, time: 100, action: {})
let t7 = TestSchedulerTask(id: 7, time: 100, action: {})
let t0 = TestSchedulerTask(id: 0, time: 200, interval: 0, action: {})
let t1 = TestSchedulerTask(id: 1, time: 200, interval: 0, action: {})
let t2 = TestSchedulerTask(id: 2, time: 300, interval: 0, action: {})
let t3 = TestSchedulerTask(id: 3, time: 300, interval: 0, action: {})
let t4 = TestSchedulerTask(id: 4, time: 400, interval: 0, action: {})
let t5 = TestSchedulerTask(id: 5, time: 400, interval: 0, action: {})
let t6 = TestSchedulerTask(id: 6, time: 100, interval: 0, action: {})
let t7 = TestSchedulerTask(id: 7, time: 100, interval: 0, action: {})

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

XCTAssertEqual(expected, results.recordedOutput)
}

func testSchedulesAndCancelsRepeatingTask() {

let subject = TestScheduler(initialClock: 0)
let publisher1 = PassthroughSubject<VirtualTime, Never>()
let results = subject.createTestableSubscriber(VirtualTime.self, Never.self)

subject.schedule(after: 100, { publisher1.subscribe(results) })
let cancellable = subject.schedule(
after: 200,
interval: 2,
tolerance: subject.minimumTolerance,
options: nil,
{ publisher1.send(subject.now) }
)
subject.schedule(after: 210, { cancellable.cancel() })
subject.resume()

let expected: TestSequence<VirtualTime, Never> = [
(100, .subscription),
(200, .input(200)),
(202, .input(202)),
(204, .input(204)),
(206, .input(206)),
(208, .input(208)),
(210, .input(210)),
]

XCTAssertEqual(expected, results.recordedOutput)
}

func testIgnoresTasksAfterMaxClock() {
let subject = TestScheduler(initialClock: 0, maxClock: 500)
let publisher1 = PassthroughSubject<VirtualTime, Never>()
let results = subject.createTestableSubscriber(VirtualTime.self, Never.self)

subject.schedule(after: 100, { publisher1.subscribe(results) })
let cancellable = subject.schedule(
after: 0,
interval: 100,
tolerance: subject.minimumTolerance,
options: nil,
{ publisher1.send(subject.now) }
)
subject.resume()
cancellable.cancel()

let expected: TestSequence<VirtualTime, Never> = [
(100, .subscription),
(100, .input(100)),
(200, .input(200)),
(300, .input(300)),
(400, .input(400)),
(500, .input(500)),
]

XCTAssertEqual(expected, results.recordedOutput)
}

func testRemovesTaskCancelledWithinOwnAction() {
let subject = TestScheduler(initialClock: 0, maxClock: 500)
let publisher1 = PassthroughSubject<VirtualTime, Never>()
let results = subject.createTestableSubscriber(VirtualTime.self, Never.self)
var cancellables = Set<AnyCancellable>()

subject.schedule(after: 100, { publisher1.subscribe(results) })
subject.schedule(
after: 200,
interval: 100,
tolerance: subject.minimumTolerance,
options: nil) {
publisher1.send(subject.now)
cancellables.removeAll()
}
.store(in: &cancellables)
subject.resume()

let expected: TestSequence<VirtualTime, Never> = [
(100, .subscription),
(200, .input(200))
]

XCTAssertEqual(expected, results.recordedOutput)
}

static var allTests = [
("testSchedulerTasksSortSensibly", testSchedulerTasksSortSensibly),
Expand All @@ -216,5 +300,11 @@ final class TestSchedulerTests: XCTestCase {
("testSchedulerInvokesDeferredTask", testSchedulerInvokesDeferredTask),
("testSchedulerInvokesDeferredTaskScheduledForPastImmediately", testSchedulerInvokesDeferredTaskScheduledForPastImmediately),
("testSchedulerRemovesCancelledTasks", testSchedulerRemovesCancelledTasks),
("testSchedulerQueues", testSchedulerQueues),
("testFiresEventsScheduledBeforeStartCalled", testFiresEventsScheduledBeforeStartCalled),
("testTrampolinesImmediatelyScheduledTasks", testTrampolinesImmediatelyScheduledTasks),
("testSchedulesRepeatingTask", testSchedulesAndCancelsRepeatingTask),
("testIgnoresTasksAfterMaxClock", testIgnoresTasksAfterMaxClock),
("testRemovesTaskCancelledWithinOwnAction", testRemovesTaskCancelledWithinOwnAction)
]
}

0 comments on commit cd90cfb

Please sign in to comment.