170 lines
5.6 KiB
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)
|
|
}
|
|
}
|