aws-appsync-realtime-client.../AppSyncRealTimeClientTests/Support/CountdownTimerTests.swift

170 lines
5.6 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
import XCTest
@testable import AppSyncRealTimeClient
/// Tests for the CountdownTimer internal support type. We do not expose this as
/// part of the public API, but its behavior is critical to the RTC's ability to
/// handle stale connections.
///
/// Clearly many of these tests are timing dependent, and could vary based on the
/// execution environment. We'll try these tests now, and if they turn out to be
/// unstable then we'll remove it and try a different approach.
class CountdownTimerTests: XCTestCase {
func testTimerFires() {
let timerFired = expectation(description: "timerFired")
let timer = CountdownTimer()
timer.start(interval: 0.1) { timerFired.fulfill() }
waitForExpectations(timeout: 1.0)
timer.invalidate()
}
func testTimerDoesNotFireEarly() {
let timerFired = expectation(description: "timerFired")
timerFired.isInverted = true
let timer = CountdownTimer()
timer.start(interval: 0.5) { timerFired.fulfill() }
waitForExpectations(timeout: 0.1)
timer.invalidate()
}
func testTimerFiresOnBackgroundQueue() {
let timerFired = expectation(description: "timerFired")
timerFired.isInverted = true
XCTAssert(Thread.isMainThread)
let timer = CountdownTimer()
timer.start(interval: 1.0) {
timerFired.fulfill()
XCTAssertFalse(Thread.isMainThread)
}
waitForExpectations(timeout: 0.1)
timer.invalidate()
}
func testTimerDoesNotFireAfterInvalidate() {
let timerFired = expectation(description: "timerFired")
timerFired.isInverted = true
let timer = CountdownTimer()
timer.start(interval: 0.1) {
timerFired.fulfill()
}
timer.invalidate()
waitForExpectations(timeout: 0.2)
}
/// Timing test
///
/// Given:
/// - A timer set to fire at a specific interval
/// When:
/// - The the interval elapses
/// Then:
/// - The timer fires then and only then
///
/// Test timing in ms:
/// - 000: Set up a timer with a .2 sec interval
/// - 100: Ensure timer has not yet fired
/// - 300: Ensure timer has fired
func testTimerFiresOnSchedule() {
let timer = CountdownTimer()
let timerHasFired = AtomicValue(initialValue: false)
let timerShouldHaveFired = expectation(description: "the timer should have fired by now")
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(0)) {
timer.start(interval: 0.200) {
timerHasFired.set(true)
}
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(100)) {
XCTAssertFalse(timerHasFired.get(), "The timer should not have fired yet")
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(300)) {
XCTAssert(timerHasFired.get(), "The timer should have fired by now")
timerShouldHaveFired.fulfill()
}
waitForExpectations(timeout: 1.0)
timer.invalidate()
}
/// Timing test
///
/// Given:
/// - A timer set to fire at a specific interval
/// When:
/// - We `resetCountdown` the timer
/// Then:
/// - The timer does not fire until the interval has elapsed from the moment of
/// `resetCountdown`
///
/// Test timing in ms:
/// - 000: Set up a timer with a .3 sec interval
/// - 100: Ensure timer has not yet fired
/// - 200: Issue a `reset` before timer would fire
/// - 400: Ensure timer has not yet fired
/// - 600: Timer should fire around this time
/// - 700: Ensure timer has fired
func testTimerResets() {
let timer = CountdownTimer()
let timerHasFired = AtomicValue(initialValue: false)
let timerShouldHaveFired = expectation(description: "the timer should have fired by now")
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(0)) {
timer.start(interval: 0.300) {
timerHasFired.set(true)
}
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(100)) {
XCTAssertFalse(timerHasFired.get(), "The timer should not have fired yet")
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(200)) {
timer.reset()
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(400)) {
XCTAssertFalse(timerHasFired.get(), "The timer should not have fired yet")
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(700)) {
XCTAssert(timerHasFired.get(), "The timer should have fired by now")
timerShouldHaveFired.fulfill()
}
waitForExpectations(timeout: 1.0)
timer.invalidate()
}
/// Test that concurrent operations on the timer do not result in data races
func testConcurrency() {
let concurrentCount = expectation(description: "timer fired at least once")
concurrentCount.expectedFulfillmentCount = 10_000
let timer = CountdownTimer()
DispatchQueue.concurrentPerform(iterations: 10_000) { _ in
let randomInt = Int.random(in: 1 ... 3)
if randomInt == 1 {
timer.start(interval: 0.01) { }
} else if randomInt == 2 {
timer.invalidate()
} else {
timer.reset()
}
concurrentCount.fulfill()
}
waitForExpectations(timeout: 10)
}
}