Skip to content

Commit

Permalink
Improve waiting methods (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriellanata authored Jul 30, 2021
1 parent 8ae2576 commit 0a07dd6
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 12 deletions.
2 changes: 1 addition & 1 deletion HammerTests.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "HammerTests"
spec.version = "0.12.0"
spec.version = "0.13.0"
spec.summary = "iOS touch and keyboard syntheis library for unit tests."
spec.description = "Hammer is a touch and keyboard synthesis library for emulating user interaction events. It enables new ways of triggering UI actions in unit tests, replicating a real world environment as much as possible."
spec.homepage = "https://github.com/lyft/Hammer"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ Hammer requires Swift 5.3 and iOS 11.0 or later.
#### With [SwiftPM](https://swift.org/package-manager)

```swift
.package(url: "https://github.com/lyft/Hammer.git", from: "0.12.0")
.package(url: "https://github.com/lyft/Hammer.git", from: "0.13.0")
```

#### With [CocoaPods](https://cocoapods.org/)

```ruby
pod 'HammerTests', '~> 0.12.0'
pod 'HammerTests', '~> 0.13.0'
```

## Setup
Expand Down
8 changes: 5 additions & 3 deletions Sources/Hammer/EventGenerator/EventGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public final class EventGenerator {
UIApplication.registerForHIDEvents(ObjectIdentifier(self)) { [weak self] event in
self?.markerEventReceived(event)
}

try self.waitUntilWindowIsReady()
}

/// Initialize an event generator for a specified UIViewController.
Expand Down Expand Up @@ -178,8 +180,8 @@ public final class EventGenerator {

/// Sleeps the current thread until the events have finished sending.
private func waitForEvents() throws {
let runLoop = CFRunLoopGetCurrent()
try self.sendMarkerEvent { CFRunLoopStop(runLoop) }
CFRunLoopRun()
let waiter = Waiter(timeout: 1)
try self.sendMarkerEvent { try? waiter.complete() }
try waiter.start()
}
}
10 changes: 10 additions & 0 deletions Sources/Hammer/Utilties/HammerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public enum HammerError: Error {
case unableToFindView(identifier: String)
case invalidViewType(identifier: String, type: String, expected: String)
case waitConditionTimeout(TimeInterval)

case waiterIsNotRunning
case waiterIsAlreadyRunning
case waiterIsAlreadyCompleted
}

extension HammerError: CustomStringConvertible {
Expand Down Expand Up @@ -79,6 +83,12 @@ extension HammerError: CustomStringConvertible {
return "Invalid type for view: \"\(identifier)\", got \"\(type)\" expected \"\(expected)\""
case .waitConditionTimeout(let timeout):
return "Timeout while waiting for condition exceeded \(timeout) seconds"
case .waiterIsNotRunning:
return "Unable to stop a Waiter that is not running"
case .waiterIsAlreadyRunning:
return "Unable to start a Waiter that is already running"
case .waiterIsAlreadyCompleted:
return "Unable to start or stop a waiter that is already completed"
}
}
}
Expand Down
58 changes: 57 additions & 1 deletion Sources/Hammer/Utilties/Waiting.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,70 @@
import Foundation
import UIKit
import XCTest

extension EventGenerator {
/// Object to handle waiting
public final class Waiter {
public enum State {
case idle
case running
case completed(timeout: Bool)
}

/// The maximum time to wait before stopping itself
public let timeout: TimeInterval

/// The current state of the waiter
public private(set) var state: State = .idle

/// We use XCTestExpectations internally to sleep the execution in a way that is friendly to tests
/// and does not block the main thread.
private let expectation = XCTestExpectation(description: "Hammer-Wait")

/// Initialize a Waiter
///
/// - parameter timeout: The maximum time to wait before stopping itself
public init(timeout: TimeInterval) {
self.timeout = timeout
}

/// Begin waiting
public func start() throws {
if case .running = self.state {
throw HammerError.waiterIsAlreadyRunning
} else if case .completed = self.state {
throw HammerError.waiterIsAlreadyCompleted
}

self.state = .running
let result = XCTWaiter.wait(for: [self.expectation], timeout: self.timeout)
switch result {
case .completed:
self.state = .completed(timeout: false)
default:
self.state = .completed(timeout: true)
}
}

/// Stop waiting before the timeout
public func complete() throws {
if case .idle = self.state {
throw HammerError.waiterIsNotRunning
} else if case .completed = self.state {
throw HammerError.waiterIsAlreadyCompleted
}

self.expectation.fulfill()
}
}

/// Waits for a specified time.
///
/// - parameter interval: The maximum time to wait.
///
/// - throws: An error if there was an issue during waiting.
public func wait(_ interval: TimeInterval) throws {
CFRunLoopRunInMode(CFRunLoopMode.defaultMode, interval, false)
try Waiter(timeout: interval).start()
}

/// Waits for a condition to become true within the specified time.
Expand Down
13 changes: 8 additions & 5 deletions Tests/HammerTests/KeyboardTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,18 @@ final class KeyboardTests: XCTestCase {
view.autocapitalizationType = .none
view.widthAnchor.constraint(equalToConstant: 300).isActive = true

let window = UIWindow(frame: UIScreen.main.bounds)
window.isHidden = false
window.addSubview(view)
let viewController = UIViewController()
viewController.view.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
view.centerYAnchor.constraint(equalTo: window.centerYAnchor),
view.centerXAnchor.constraint(equalTo: window.centerXAnchor),
view.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor),
view.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor),
])

let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = viewController
window.isHidden = false

let eventGenerator = try EventGenerator(window: window)
try eventGenerator.waitUntilHittable(timeout: 1)

Expand Down
2 changes: 2 additions & 0 deletions project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ targets:
platform: iOS
deploymentTarget: "11.0"
sources: Sources/Hammer
settings:
ENABLE_TESTING_SEARCH_PATHS: true
scheme:
testTargets:
- HammerTests
Expand Down

0 comments on commit 0a07dd6

Please sign in to comment.