TM-SGNL-iOS/SignalServiceKit/tests/Schedulers/TestSchedulerTest.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

1073 lines
34 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
@testable import SignalServiceKit
public import XCTest
struct FakeError: Error {}
public class TestSchedulerTest: XCTestCase {
func test_fulfill() {
let scheduler = TestScheduler()
let (promise, future) = Promise<String>.pending()
scheduler.run(atTime: 5) {
future.resolve("Hello")
}
var didObserveResult = false
promise.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), "Hello")
XCTAssertEqual(scheduler.currentTime, 5)
didObserveResult = true
}
scheduler.start()
scheduler.stop()
XCTAssertEqual(try? promise.result?.get(), "Hello")
XCTAssertEqual(scheduler.currentTime, 5)
XCTAssert(didObserveResult)
}
func test_startStop() {
let scheduler = TestScheduler()
let (promise, future) = Promise<String>.pending()
scheduler.run(atTime: 5) {
future.resolve("Hello")
}
var didObserveResult = false
promise.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), "Hello")
XCTAssertEqual(scheduler.currentTime, 5)
didObserveResult = true
}
// Have not started yet, nothing should have run.
XCTAssertNil(promise.result)
XCTAssertEqual(scheduler.currentTime, 0)
XCTAssertFalse(didObserveResult)
scheduler.start()
scheduler.stop()
// Now things should have run.
XCTAssertEqual(try? promise.result?.get(), "Hello")
XCTAssertEqual(scheduler.currentTime, 5)
XCTAssert(didObserveResult)
// Round 2.
let (promise2, future2) = Promise<String>.pending()
scheduler.run(atTime: 10) {
future2.resolve("World")
}
didObserveResult = false
promise2.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), "World")
XCTAssertEqual(scheduler.currentTime, 10)
didObserveResult = true
}
// We stopped before, so nothing should have run.
XCTAssertNil(promise2.result)
XCTAssertEqual(scheduler.currentTime, 5)
XCTAssertFalse(didObserveResult)
// This time leave it running for round 3.
scheduler.start()
// Now things should have run.
XCTAssertEqual(try? promise2.result?.get(), "World")
XCTAssertEqual(scheduler.currentTime, 10)
XCTAssert(didObserveResult)
// Round 3.
let (promise3, future3) = Promise<String>.pending()
scheduler.run(atTime: 20) {
future3.resolve("!")
}
didObserveResult = false
promise3.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), "!")
XCTAssertEqual(scheduler.currentTime, 20)
didObserveResult = true
}
// We left it running, so everything should have executed.
XCTAssertEqual(try? promise3.result?.get(), "!")
XCTAssertEqual(scheduler.currentTime, 20)
XCTAssert(didObserveResult)
}
func test_map() {
let scheduler = TestScheduler()
let promise = Promise<Int>.value(1)
.map(on: scheduler) {
return $0 + 10 // now 11
}
var didObserveResult = false
promise.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), 11)
didObserveResult = true
}
scheduler.start()
scheduler.stop()
XCTAssertEqual(try? promise.result?.get(), 11)
XCTAssert(didObserveResult)
}
func test_startStopMap() {
let scheduler = TestScheduler()
let promise = Promise<Int>.value(1)
.map(on: scheduler) {
return $0 + 10 // now 11
}
var didObserveResult = false
promise.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), 11)
XCTAssertEqual(scheduler.currentTime, 0)
didObserveResult = true
}
// Have not started yet, nothing should have run.
XCTAssertNil(promise.result)
XCTAssertEqual(scheduler.currentTime, 0)
XCTAssertFalse(didObserveResult)
scheduler.start()
scheduler.stop()
// Now things should have run.
XCTAssertEqual(try? promise.result?.get(), 11)
XCTAssertEqual(scheduler.currentTime, 0)
XCTAssert(didObserveResult)
// Round 2.
let promise2 = Promise<Int>.value(2)
.map(on: scheduler) {
return $0 + 20 // now 22
}
didObserveResult = false
promise2.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), 22)
XCTAssertEqual(scheduler.currentTime, 0)
didObserveResult = true
}
// We stopped before, so nothing should have run.
XCTAssertNil(promise2.result)
XCTAssertEqual(scheduler.currentTime, 0)
XCTAssertFalse(didObserveResult)
// This time leave it running for round 3.
scheduler.start()
// Now things should have run.
XCTAssertEqual(try? promise2.result?.get(), 22)
XCTAssertEqual(scheduler.currentTime, 0)
XCTAssert(didObserveResult)
// Round 3.
let promise3 = Promise<Int>.value(3)
.map(on: scheduler) {
return $0 + 30 // now 33
}
didObserveResult = false
promise3.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), 33)
XCTAssertEqual(scheduler.currentTime, 0)
didObserveResult = true
}
// We left it running, so everything should have executed.
XCTAssertEqual(try? promise3.result?.get(), 33)
XCTAssertEqual(scheduler.currentTime, 0)
XCTAssert(didObserveResult)
}
func test_catch() {
let scheduler = TestScheduler()
// Keep it running. Other tests check whether
// starting and stopping works, this one just
// tests whether the operators run at all.
scheduler.start()
var didCatch = false
let promise = Promise<Int>(error: FakeError())
.catch(on: scheduler) { _ in
didCatch = true
}
var didObserveResult = false
promise.observe(on: scheduler) { result in
XCTAssert(didCatch)
switch result {
case .success: XCTFail("Should have failed promise")
case .failure: break
}
didObserveResult = true
}
switch promise.result {
case .success, .none: XCTFail("Should have failed promise")
case .failure: break
}
XCTAssert(didCatch)
XCTAssert(didObserveResult)
}
func test_done() {
let scheduler = TestScheduler()
// Keep it running. Other tests check whether
// starting and stopping works, this one just
// tests whether the operators run at all.
scheduler.start()
var didDone = false
let promise = Promise<Int>.value(1)
.done(on: scheduler) { _ in
didDone = true
}
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssert(didDone)
didObserveResult = true
}
XCTAssert(didDone)
XCTAssert(didObserveResult)
}
func test_asVoid() {
let scheduler = TestScheduler()
// Keep it running. Other tests check whether
// starting and stopping works, this one just
// tests whether the operators run at all.
scheduler.start()
let promise = Promise<Int>.value(1).asVoid(on: scheduler)
var didObserveResult = false
promise.observe(on: scheduler) { _ in
didObserveResult = true
}
XCTAssert(didObserveResult)
}
func test_then() {
let scheduler = TestScheduler()
let (nestedPromise, nestedFuture) = Promise<String>.pending()
var didThen = false
let promise = Promise<Int>.value(1)
.then(on: scheduler) { _ in
didThen = true
return nestedPromise
}
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssert(didThen)
XCTAssertNotNil(nestedPromise.result)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didThen)
XCTAssertFalse(didObserveResult)
scheduler.start()
scheduler.stop()
// Now the then should've executed, but we haven't
// resolved the nested promise so not the final observation.
XCTAssert(didThen)
XCTAssertFalse(didObserveResult)
// Resolve the promise. Clock is stopped so nothing
// should have executed yet.
nestedFuture.resolve("Hello")
XCTAssertFalse(didObserveResult)
// Finally start the clock, which should resolve everything.
scheduler.start()
scheduler.stop()
XCTAssert(didObserveResult)
XCTAssertEqual(try? promise.result?.get(), "Hello")
}
func test_recover() {
let scheduler = TestScheduler()
let (nestedPromise, nestedFuture) = Promise<String>.pending()
var didRecover = false
let promise = Promise<String>(error: FakeError())
.recover(on: scheduler) { _ in
didRecover = true
return nestedPromise
}
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssert(didRecover)
XCTAssertNotNil(nestedPromise.result)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didRecover)
XCTAssertFalse(didObserveResult)
scheduler.start()
scheduler.stop()
// Now the then should've executed, but we haven't
// resolved the nested promise so not the final observation.
XCTAssert(didRecover)
XCTAssertFalse(didObserveResult)
// Resolve the promise. Clock is stopped so nothing
// should have executed yet.
nestedFuture.resolve("Hello")
XCTAssertFalse(didObserveResult)
// Finally start the clock, which should resolve everything.
scheduler.start()
scheduler.stop()
XCTAssert(didObserveResult)
XCTAssertEqual(try? promise.result?.get(), "Hello")
}
// Like test_then, but we use the scheduler
// to schedule fulfillment at particular times.
func test_thenScheduled() {
let scheduler = TestScheduler()
let (nestedPromise, nestedFuture) = Promise<String>.pending()
let (dblNestedPromise, dblNestedFuture) = Promise<Int>.pending()
var didOuterThen = false
var didInnerThen = false
let promise = Promise<Void>.value(())
.then(on: scheduler) { _ in
XCTAssertEqual(scheduler.currentTime, 0)
didOuterThen = true
return nestedPromise.then(on: scheduler) { result in
XCTAssert(didOuterThen)
XCTAssertEqual(result, "Hello")
XCTAssertEqual(scheduler.currentTime, 17)
didInnerThen = true
return dblNestedPromise
}
}
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssert(didOuterThen)
XCTAssert(didInnerThen)
XCTAssertNotNil(nestedPromise.result)
XCTAssertNotNil(dblNestedPromise.result)
XCTAssertEqual(scheduler.currentTime, 100)
didObserveResult = true
}
scheduler.run(atTime: 17) {
nestedFuture.resolve("Hello")
}
scheduler.run(atTime: 100) {
dblNestedFuture.resolve(1)
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didOuterThen)
XCTAssertFalse(didInnerThen)
XCTAssertFalse(didObserveResult)
XCTAssertNil(nestedPromise.result)
XCTAssertNil(dblNestedPromise.result)
// If we advance to _before_ the nested future is resolved,
// only the outermost then (the initial promise) should have executed.
scheduler.tick()
XCTAssert(didOuterThen)
XCTAssertFalse(didInnerThen)
XCTAssertFalse(didObserveResult)
XCTAssertNil(nestedPromise.result)
XCTAssertNil(dblNestedPromise.result)
scheduler.advance(to: 16)
XCTAssertFalse(didInnerThen)
XCTAssertFalse(didObserveResult)
XCTAssertNil(nestedPromise.result)
XCTAssertNil(dblNestedPromise.result)
// Now when we advance to after the nested future is resolved,
// we shoud resolve the outer but not inner nested promise.
scheduler.advance(to: 60)
XCTAssert(didInnerThen)
XCTAssertFalse(didObserveResult)
XCTAssertNotNil(nestedPromise.result)
XCTAssertNil(dblNestedPromise.result)
// And once we advance to the very last bit of scheduled work,
// the inner nested promise should resolve, triggering the final
// observation block.
scheduler.advance(to: 100)
XCTAssert(didObserveResult)
XCTAssertNotNil(dblNestedPromise.result)
}
func test_firstly() {
let scheduler = TestScheduler()
var didFirstly = false
let promise = firstly(on: scheduler) {
didFirstly = true
return "Hello"
}
var didObserveResult = false
promise.observe(on: scheduler) { result in
XCTAssert(didFirstly)
XCTAssertEqual(try? result.get(), "Hello")
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didFirstly)
XCTAssertFalse(didObserveResult)
scheduler.start()
scheduler.stop()
// Now the firstly should've executed.
scheduler.start()
scheduler.stop()
XCTAssert(didFirstly)
XCTAssert(didObserveResult)
XCTAssertEqual(try? promise.result?.get(), "Hello")
// Do it again, but the promise variety.
scheduler.adjustTime(to: 0)
let (nestedPromise, nestedFuture) = Promise<String>.pending()
didFirstly = false
let promise2 = firstly(on: scheduler) {
didFirstly = true
XCTAssertEqual(scheduler.currentTime, 0)
return nestedPromise
}
didObserveResult = false
promise2.observe(on: scheduler) { _ in
XCTAssert(didFirstly)
XCTAssertNotNil(nestedPromise.result)
XCTAssertEqual(scheduler.currentTime, 1)
didObserveResult = true
}
scheduler.run(atTime: 1) {
nestedFuture.resolve("Hello")
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didFirstly)
XCTAssertFalse(didObserveResult)
scheduler.advance(to: 0)
// Now the firstly should've executed, but we haven't
// resolved the nested promise so not the final observation.
XCTAssert(didFirstly)
XCTAssertFalse(didObserveResult)
// Now we tick which resolves the nested promise.
scheduler.tick()
XCTAssert(didObserveResult)
XCTAssertEqual(try? promise.result?.get(), "Hello")
}
func test_whenFulfilled() {
let scheduler = TestScheduler()
var promises = [Promise<Int>]()
var futures = [Future<Int>]()
for _ in 0...10 {
let (promise, future) = Promise<Int>.pending()
promises.append(promise)
futures.append(future)
}
let whenPromise: Promise<[Int]> = Promise.when(on: scheduler, fulfilled: promises)
var didObserve = false
whenPromise.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), Array(0...10))
XCTAssertEqual(scheduler.currentTime, 10)
didObserve = true
}
for i in 0...10 {
scheduler.run(atTime: i) {
futures[i].resolve(i)
}
}
for i in 0..<10 {
scheduler.advance(to: i)
XCTAssertFalse(didObserve)
XCTAssertNil(whenPromise.result)
}
scheduler.advance(to: 10)
XCTAssert(didObserve)
}
func test_whenFulfilledFailure() {
let scheduler = TestScheduler()
var promises = [Promise<Int>]()
var futures = [Future<Int>]()
for _ in 0...10 {
let (promise, future) = Promise<Int>.pending()
promises.append(promise)
futures.append(future)
}
let whenPromise: Promise<[Int]> = Promise.when(on: scheduler, fulfilled: promises)
var didObserve = false
whenPromise.observe(on: scheduler) { result in
switch result {
case .success: XCTFail("Should have failed promise")
case .failure: break
}
// Should have early exited at time 5.
XCTAssertEqual(scheduler.currentTime, 5)
didObserve = true
}
for i in 0...4 {
scheduler.run(atTime: i) {
futures[i].resolve(i)
}
}
// Fail the 5th one.
scheduler.run(atTime: 5) {
futures[5].reject(FakeError())
}
// Resolve the rest.
for i in 6...10 {
scheduler.run(atTime: i) {
futures[i].resolve(i)
}
}
for i in 0..<5 {
scheduler.advance(to: i)
XCTAssertFalse(didObserve)
XCTAssertNil(whenPromise.result)
}
// Advance to 10, but expect the observe to have
// happened at 5 above, when the failure happens.
scheduler.advance(to: 10)
XCTAssert(didObserve)
}
func test_whenResolved() {
let scheduler = TestScheduler()
var promises = [Promise<Int>]()
var futures = [Future<Int>]()
for _ in 0...10 {
let (promise, future) = Promise<Int>.pending()
promises.append(promise)
futures.append(future)
}
let whenPromise: Guarantee<[Result<Int, Error>]> = Promise<Int>.when(on: scheduler, resolved: promises)
var didObserve = false
whenPromise.observe(on: scheduler) { result in
switch result {
case .success(let results):
for i in 0...10 {
if i % 2 == 0 {
XCTAssertEqual(try? results[i].get(), i)
} else {
switch results[i] {
case .success: XCTFail("Should have individual failed promise")
case .failure: break
}
}
}
case .failure:
XCTFail("Should have overall success for when promise")
}
XCTAssertEqual(scheduler.currentTime, 10)
didObserve = true
}
// Events get value, odds get error.
for i in 0...10 {
scheduler.run(atTime: i) {
if i % 2 == 0 {
futures[i].resolve(i)
} else {
futures[i].reject(FakeError())
}
}
}
for i in 0..<10 {
scheduler.advance(to: i)
XCTAssertFalse(didObserve)
XCTAssertNil(whenPromise.result)
}
scheduler.advance(to: 10)
XCTAssert(didObserve)
}
func test_promiseRace() {
let scheduler = TestScheduler()
var promises = [Promise<Int>]()
var futures = [Future<Int>]()
for _ in 0...10 {
let (promise, future) = Promise<Int>.pending()
promises.append(promise)
futures.append(future)
}
let racePromise: Promise<Int> = Promise.race(on: scheduler, promises)
var didObserve = false
racePromise.observe(on: scheduler) { result in
// 7 won the race
XCTAssertEqual(try? result.get(), 7)
// Should have early exited at time 1.
XCTAssertEqual(scheduler.currentTime, 1)
didObserve = true
}
// Let 7 win the race at time 1
scheduler.run(atTime: 1) {
futures[7].resolve(7)
}
// Resolve all the others later on.
for i in 0...6 {
scheduler.run(atTime: i + 10) {
futures[i].resolve(i)
}
}
for i in 8...10 {
scheduler.run(atTime: i + 10) {
futures[i].resolve(i)
}
}
// Advance to 100, but expect the observe to have
// happened at 1 above, when 7 wins the race.
scheduler.advance(to: 100)
XCTAssert(didObserve)
}
func test_guaranteeRace() {
let scheduler = TestScheduler()
var guarantees = [Guarantee<Int>]()
var futures = [GuaranteeFuture<Int>]()
for _ in 0...10 {
let (promise, future) = Guarantee<Int>.pending()
guarantees.append(promise)
futures.append(future)
}
let raceGuarantee: Guarantee<Int> = Guarantee<Int>.race(on: scheduler, guarantees)
var didObserve = false
raceGuarantee.observe(on: scheduler) { result in
// 7 won the race
XCTAssertEqual(try? result.get(), 7)
// Should have early exited at time 1.
XCTAssertEqual(scheduler.currentTime, 1)
didObserve = true
}
// Let 7 win the race at time 1
scheduler.run(atTime: 1) {
futures[7].resolve(7)
}
// Resolve all the others later on.
for i in 0...6 {
scheduler.run(atTime: i + 10) {
futures[i].resolve(i)
}
}
for i in 8...10 {
scheduler.run(atTime: i + 10) {
futures[i].resolve(i)
}
}
// Advance to 100, but expect the observe to have
// happened at 1 above, when 7 wins the race.
scheduler.advance(to: 100)
XCTAssert(didObserve)
}
func test_afterOneTickPerSecond() {
let scheduler = TestScheduler(secondsPerTick: 1)
var promise = Guarantee.after(on: scheduler, seconds: 10)
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssertEqual(scheduler.currentTime, 10)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 9)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 10)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
// Reset.
scheduler.adjustTime(to: 0)
// At a granularity our clock doesn't support,
// since it is in 1 second increments.
promise = Guarantee.after(on: scheduler, seconds: 1.5)
didObserveResult = false
promise.observe(on: scheduler) { _ in
// should round up to 2 ticks.
XCTAssertEqual(scheduler.currentTime, 2)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 1)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 2)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
}
func test_afterMoreTicksPerSecond() {
let scheduler = TestScheduler(secondsPerTick: 0.5)
var promise = Guarantee.after(on: scheduler, seconds: 10)
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssertEqual(scheduler.currentTime, 20)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 9)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// We are counting 2 ticks per second, so nothing
// should happen after 10, either.
scheduler.advance(to: 15)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 20)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
// Reset.
scheduler.adjustTime(to: 0)
// Now we support half-second granularity.
promise = Guarantee.after(on: scheduler, seconds: 1.5)
didObserveResult = false
promise.observe(on: scheduler) { _ in
// should match exactly 3 ticks.
XCTAssertEqual(scheduler.currentTime, 3)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 2)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 3)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
}
func test_afterFewerTicksPerSecond() {
let scheduler = TestScheduler(secondsPerTick: 10)
// 10 seconds now becomes 1 tick.
var promise = Guarantee.after(on: scheduler, seconds: 10)
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssertEqual(scheduler.currentTime, 1)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.tick()
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
// Reset.
scheduler.adjustTime(to: 0)
// We can't hit sub-10 second granularuty.
promise = Guarantee.after(on: scheduler, seconds: 5)
didObserveResult = false
promise.observe(on: scheduler) { _ in
// We can't hit sub-10 second granularuty,
// so it rounds to 1 tick.
XCTAssertEqual(scheduler.currentTime, 1)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 1)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
}
func test_afterWallTime() {
let scheduler = TestScheduler(secondsPerTick: 1)
var promise = Guarantee.after(on: scheduler, wallInterval: 10)
var didObserveResult = false
promise.observe(on: scheduler) { _ in
XCTAssertEqual(scheduler.currentTime, 10)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 9)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 10)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
// Reset.
scheduler.adjustTime(to: 0)
// At a granularity our clock doesn't support,
// since it is in 1 second increments.
promise = Guarantee.after(on: scheduler, wallInterval: 1.5)
didObserveResult = false
promise.observe(on: scheduler) { _ in
// should round up to 2 ticks.
XCTAssertEqual(scheduler.currentTime, 2)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 1)
XCTAssertFalse(didObserveResult)
XCTAssertNil(promise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 2)
XCTAssert(didObserveResult)
XCTAssertNotNil(promise.result)
}
func test_timeout() {
let scheduler = TestScheduler(secondsPerTick: 1)
var (promise, _) = Promise<Int>.pending()
var timeoutPromise = promise
.timeout(
on: scheduler,
seconds: 10,
substituteValue: 99
)
var didObserveResult = false
timeoutPromise
.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), 99)
XCTAssertEqual(scheduler.currentTime, 10)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 9)
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 10)
XCTAssert(didObserveResult)
XCTAssertNotNil(timeoutPromise.result)
// Reset.
scheduler.adjustTime(to: 0)
(promise, _) = Promise<Int>.pending()
timeoutPromise = promise
.timeout(
on: scheduler,
seconds: 1.5, // At a granularity the clock doesn't support.
substituteValue: 52
)
didObserveResult = false
timeoutPromise
.observe(on: scheduler) { result in
XCTAssertEqual(try? result.get(), 52)
// should round up to 2 ticks.
XCTAssertEqual(scheduler.currentTime, 2)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 1)
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 2)
XCTAssert(didObserveResult)
XCTAssertNotNil(timeoutPromise.result)
}
func test_timeoutWhileSuspended() {
let scheduler = TestScheduler(secondsPerTick: 1)
var (promise, _) = Promise<Int>.pending()
var timeoutPromise = promise
.timeout(
on: scheduler,
seconds: 10,
ticksWhileSuspended: true,
timeoutErrorBlock: { return FakeError() }
)
var didObserveResult = false
timeoutPromise
.observe(on: scheduler) { result in
switch result {
case .success: XCTFail("Should have failed promise")
case .failure: break
}
XCTAssertEqual(scheduler.currentTime, 10)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 9)
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 10)
XCTAssert(didObserveResult)
XCTAssertNotNil(timeoutPromise.result)
// Reset.
scheduler.adjustTime(to: 0)
(promise, _) = Promise<Int>.pending()
timeoutPromise = promise
.timeout(
on: scheduler,
seconds: 1.5, // At a granularity the clock doesn't support.
ticksWhileSuspended: true,
timeoutErrorBlock: { return FakeError() }
)
didObserveResult = false
timeoutPromise
.observe(on: scheduler) { result in
switch result {
case .success: XCTFail("Should have failed promise")
case .failure: break
}
// should round up to 2 ticks.
XCTAssertEqual(scheduler.currentTime, 2)
didObserveResult = true
}
// Nothing should've executed without starting the clock.
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Nothing should happen if it isn't time yet.
scheduler.advance(to: 1)
XCTAssertFalse(didObserveResult)
XCTAssertNil(timeoutPromise.result)
// Once we go past the time, it should resolve.
scheduler.advance(to: 2)
XCTAssert(didObserveResult)
XCTAssertNotNil(timeoutPromise.result)
}
func test_advanceToSameTime() {
let scheduler = TestScheduler()
var flag = false
scheduler.run(atTime: 5) {
XCTAssertFalse(flag, "We should only run this once")
flag = true
}
scheduler.advance(to: 4)
scheduler.advance(to: 4)
XCTAssertFalse(flag)
scheduler.advance(to: 5)
scheduler.advance(to: 5)
XCTAssertTrue(flag)
}
}