Skip to content

Commit

Permalink
Merge pull request #718 from Quick/timeinterval
Browse files Browse the repository at this point in the history
Replaced TimeInterval with DispatchTimeInterval
  • Loading branch information
ikesyo authored May 3, 2020
2 parents 7c9ad74 + 3f41c8b commit eee98fe
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 46 deletions.
8 changes: 8 additions & 0 deletions Nimble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
0477153523B740AD00402D4E /* DispatchTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477153423B740AD00402D4E /* DispatchTimeInterval.swift */; };
0477153623B740B700402D4E /* DispatchTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477153423B740AD00402D4E /* DispatchTimeInterval.swift */; };
0477153723B740B800402D4E /* DispatchTimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0477153423B740AD00402D4E /* DispatchTimeInterval.swift */; };
1F0648CC19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */; };
1F0648CD19639F5A001F9C46 /* ObjectWithLazyProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */; };
1F0648D41963AAB2001F9C46 /* SynchronousTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F0648D31963AAB2001F9C46 /* SynchronousTest.swift */; };
Expand Down Expand Up @@ -484,6 +487,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
0477153423B740AD00402D4E /* DispatchTimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchTimeInterval.swift; sourceTree = "<group>"; };
1F0648CB19639F5A001F9C46 /* ObjectWithLazyProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectWithLazyProperty.swift; sourceTree = "<group>"; };
1F0648D31963AAB2001F9C46 /* SynchronousTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousTest.swift; sourceTree = "<group>"; };
1F14FB63194180C5009F2A08 /* utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = utils.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -882,6 +886,7 @@
1FD8CD271968AB07008ED995 /* SourceLocation.swift */,
1FD8CD281968AB07008ED995 /* Stringers.swift */,
AE4BA9AC1C88DDB500B73906 /* Errors.swift */,
0477153423B740AD00402D4E /* DispatchTimeInterval.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -1318,6 +1323,7 @@
1FDBD8671AF8A4FF0089F27B /* AssertionDispatcher.swift in Sources */,
AE4BA9AD1C88DDB500B73906 /* Errors.swift in Sources */,
1FD8CD3C1968AB07008ED995 /* BeAnInstanceOf.swift in Sources */,
0477153623B740B700402D4E /* DispatchTimeInterval.swift in Sources */,
7A6AB2C51E7F628900A2F694 /* ToSucceed.swift in Sources */,
1FD8CD501968AB07008ED995 /* BeLogical.swift in Sources */,
1F1871CB1CA89EDB00A34BF2 /* NMBExpectation.swift in Sources */,
Expand Down Expand Up @@ -1501,6 +1507,7 @@
CDD80B851F20307B0002CD65 /* MatcherProtocols.swift in Sources */,
1F5DF1721BDCA0F500C3A531 /* Expectation.swift in Sources */,
7B5358C01C38479700A23FAA /* SatisfyAnyOf.swift in Sources */,
0477153723B740B800402D4E /* DispatchTimeInterval.swift in Sources */,
7B13BA0C1DD361D300C9098C /* ContainElementSatisfying.swift in Sources */,
1F5DF1871BDCA0F500C3A531 /* Match.swift in Sources */,
);
Expand Down Expand Up @@ -1593,6 +1600,7 @@
1F1871E71CA8A18400A34BF2 /* Async.swift in Sources */,
1FDBD8681AF8A4FF0089F27B /* AssertionDispatcher.swift in Sources */,
AE4BA9AE1C88DDB500B73906 /* Errors.swift in Sources */,
0477153523B740AD00402D4E /* DispatchTimeInterval.swift in Sources */,
1FD8CD3D1968AB07008ED995 /* BeAnInstanceOf.swift in Sources */,
1FD8CD511968AB07008ED995 /* BeLogical.swift in Sources */,
1F1871D91CA89EF100A34BF2 /* NMBExpectation.swift in Sources */,
Expand Down
5 changes: 3 additions & 2 deletions Sources/Nimble/Adapters/NMBExpectation.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import Dispatch

#if canImport(Darwin) && !SWIFT_PACKAGE

Expand Down Expand Up @@ -37,7 +38,7 @@ public class NMBExpectation: NSObject {
internal var _negative: Bool
internal let _file: FileString
internal let _line: UInt
internal var _timeout: TimeInterval = 1.0
internal var _timeout: DispatchTimeInterval = .seconds(1)
// swiftlint:enable identifier_name

@objc public init(actualBlock: @escaping () -> NSObject?, negative: Bool, file: FileString, line: UInt) {
Expand All @@ -54,7 +55,7 @@ public class NMBExpectation: NSObject {
}

@objc public var withTimeout: (TimeInterval) -> NMBExpectation {
return { timeout in self._timeout = timeout
return { timeout in self._timeout = timeout.dispatchInterval
return self
}
}
Expand Down
28 changes: 13 additions & 15 deletions Sources/Nimble/DSL+Wait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,29 @@ internal class NMBWait: NSObject {
file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
return throwableUntil(timeout: timeout, file: file, line: line) { done in
action(done)
}
// Convert TimeInterval to DispatchTimeInterval
until(timeout: timeout.dispatchInterval, file: file, line: line, action: action)
}
#else
#endif

internal class func until(
timeout: TimeInterval,
timeout: DispatchTimeInterval,
file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
return throwableUntil(timeout: timeout, file: file, line: line) { done in
action(done)
}
}
#endif

// Using a throwable closure makes this method not objc compatible.
internal class func throwableUntil(
timeout: TimeInterval,
timeout: DispatchTimeInterval,
file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) throws -> Void) {
let awaiter = NimbleEnvironment.activeInstance.awaiter
let leeway = timeout / 2.0
let leeway = timeout.divided
// swiftlint:disable:next line_length
let result = awaiter.performBlock(file: file, line: line) { (done: @escaping (ErrorResult) -> Void) throws -> Void in
DispatchQueue.main.async {
Expand Down Expand Up @@ -72,8 +71,7 @@ internal class NMBWait: NSObject {
fail(blockedRunLoopErrorMessageFor("-waitUntil()", leeway: leeway),
file: file, line: line)
case .timedOut:
let pluralize = (timeout == 1 ? "" : "s")
fail("Waited more than \(timeout) second\(pluralize)", file: file, line: line)
fail("Waited more than \(timeout.description)", file: file, line: line)
case let .raisedException(exception):
fail("Unexpected exception raised: \(exception)")
case let .errorThrown(error):
Expand All @@ -93,21 +91,21 @@ internal class NMBWait: NSObject {
_ file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
until(timeout: 1, file: file, line: line, action: action)
until(timeout: .seconds(1), file: file, line: line, action: action)
}
#else
internal class func until(
_ file: FileString = #file,
line: UInt = #line,
action: @escaping (@escaping () -> Void) -> Void) {
until(timeout: 1, file: file, line: line, action: action)
until(timeout: .seconds(1), file: file, line: line, action: action)
}
#endif
}

internal func blockedRunLoopErrorMessageFor(_ fnName: String, leeway: TimeInterval) -> String {
internal func blockedRunLoopErrorMessageFor(_ fnName: String, leeway: DispatchTimeInterval) -> String {
// swiftlint:disable:next line_length
return "\(fnName) timed out but was unable to run the timeout handler because the main thread is unresponsive (\(leeway) seconds is allow after the wait times out). Conditions that may cause this include processing blocking IO on the main thread, calls to sleep(), deadlocks, and synchronous IPC. Nimble forcefully stopped run loop which may cause future failures in test run."
return "\(fnName) timed out but was unable to run the timeout handler because the main thread is unresponsive (\(leeway.description) is allow after the wait times out). Conditions that may cause this include processing blocking IO on the main thread, calls to sleep(), deadlocks, and synchronous IPC. Nimble forcefully stopped run loop which may cause future failures in test run."
}

/// Wait asynchronously until the done closure is called or the timeout has been reached.
Expand All @@ -117,6 +115,6 @@ internal func blockedRunLoopErrorMessageFor(_ fnName: String, leeway: TimeInterv
///
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func waitUntil(timeout: TimeInterval = AsyncDefaults.Timeout, file: FileString = #file, line: UInt = #line, action: @escaping (@escaping () -> Void) -> Void) {
public func waitUntil(timeout: DispatchTimeInterval = AsyncDefaults.timeout, file: FileString = #file, line: UInt = #line, action: @escaping (@escaping () -> Void) -> Void) {
NMBWait.until(timeout: timeout, file: file, line: line, action: action)
}
22 changes: 15 additions & 7 deletions Sources/Nimble/Matchers/Async.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import Foundation
import Dispatch

/// If you are running on a slower machine, it could be useful to increase the default timeout value
/// or slow down poll interval. Default timeout interval is 1, and poll interval is 0.01.
public struct AsyncDefaults {
public static var timeout: DispatchTimeInterval = .seconds(1)
public static var pollInterval: DispatchTimeInterval = .milliseconds(10)
}

extension AsyncDefaults {
@available(*, unavailable, renamed: "timeout")
public static var Timeout: TimeInterval = 1
@available(*, unavailable, renamed: "pollInterval")
public static var PollInterval: TimeInterval = 0.01
}

private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout: TimeInterval, poll: TimeInterval, fnName: String) -> Predicate<T> {
private func async<T>(style: ExpectationStyle, predicate: Predicate<T>, timeout: DispatchTimeInterval, poll: DispatchTimeInterval, fnName: String) -> Predicate<T> {
return Predicate { actualExpression in
let uncachedExpression = actualExpression.withoutCaching()
let fnName = "expect(...).\(fnName)(...)"
Expand Down Expand Up @@ -55,7 +63,7 @@ extension Expectation {
/// @discussion
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func toEventually(_ predicate: Predicate<T>, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil) {
public func toEventually(_ predicate: Predicate<T>, timeout: DispatchTimeInterval = AsyncDefaults.timeout, pollInterval: DispatchTimeInterval = AsyncDefaults.pollInterval, description: String? = nil) {
nimblePrecondition(expression.isClosure, "NimbleInternalError", toEventuallyRequiresClosureError.stringValue)

let (pass, msg) = execute(
Expand All @@ -75,7 +83,7 @@ extension Expectation {
/// @discussion
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func toEventuallyNot(_ predicate: Predicate<T>, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil) {
public func toEventuallyNot(_ predicate: Predicate<T>, timeout: DispatchTimeInterval = AsyncDefaults.timeout, pollInterval: DispatchTimeInterval = AsyncDefaults.pollInterval, description: String? = nil) {
nimblePrecondition(expression.isClosure, "NimbleInternalError", toEventuallyRequiresClosureError.stringValue)

let (pass, msg) = execute(
Expand Down Expand Up @@ -103,7 +111,7 @@ extension Expectation {
/// @discussion
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func toNotEventually(_ predicate: Predicate<T>, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil) {
public func toNotEventually(_ predicate: Predicate<T>, timeout: DispatchTimeInterval = AsyncDefaults.timeout, pollInterval: DispatchTimeInterval = AsyncDefaults.pollInterval, description: String? = nil) {
return toEventuallyNot(predicate, timeout: timeout, pollInterval: pollInterval, description: description)
}
}
Expand All @@ -116,7 +124,7 @@ extension Expectation {
/// @discussion
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func toEventually<U>(_ matcher: U, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil)
public func toEventually<U>(_ matcher: U, timeout: DispatchTimeInterval = AsyncDefaults.timeout, pollInterval: DispatchTimeInterval = AsyncDefaults.pollInterval, description: String? = nil)
where U: Matcher, U.ValueType == T {
if expression.isClosure {
let (pass, msg) = execute(
Expand Down Expand Up @@ -145,7 +153,7 @@ extension Expectation {
/// @discussion
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func toEventuallyNot<U>(_ matcher: U, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil)
public func toEventuallyNot<U>(_ matcher: U, timeout: DispatchTimeInterval = AsyncDefaults.timeout, pollInterval: DispatchTimeInterval = AsyncDefaults.pollInterval, description: String? = nil)
where U: Matcher, U.ValueType == T {
if expression.isClosure {
let (pass, msg) = expressionDoesNotMatch(
Expand Down Expand Up @@ -174,7 +182,7 @@ extension Expectation {
/// @discussion
/// This function manages the main run loop (`NSRunLoop.mainRunLoop()`) while this function
/// is executing. Any attempts to touch the run loop may cause non-deterministic behavior.
public func toNotEventually<U>(_ matcher: U, timeout: TimeInterval = AsyncDefaults.Timeout, pollInterval: TimeInterval = AsyncDefaults.PollInterval, description: String? = nil)
public func toNotEventually<U>(_ matcher: U, timeout: DispatchTimeInterval = AsyncDefaults.timeout, pollInterval: DispatchTimeInterval = AsyncDefaults.pollInterval, description: String? = nil)
where U: Matcher, U.ValueType == T {
return toEventuallyNot(matcher, timeout: timeout, pollInterval: pollInterval, description: description)
}
Expand Down
16 changes: 6 additions & 10 deletions Sources/Nimble/Utils/Await.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import CoreFoundation
import Dispatch
import Foundation

#if canImport(CDispatch)
import CDispatch
#endif

private let timeoutLeeway = DispatchTimeInterval.milliseconds(1)
private let pollLeeway = DispatchTimeInterval.milliseconds(1)

Expand Down Expand Up @@ -152,7 +148,7 @@ internal class AwaitPromiseBuilder<T> {
self.trigger = trigger
}

func timeout(_ timeoutInterval: TimeInterval, forcefullyAbortTimeout: TimeInterval) -> Self {
func timeout(_ timeoutInterval: DispatchTimeInterval, forcefullyAbortTimeout: DispatchTimeInterval) -> Self {
// = Discussion =
//
// There's a lot of technical decisions here that is useful to elaborate on. This is
Expand Down Expand Up @@ -325,12 +321,12 @@ internal class Awaiter {
trigger: trigger)
}

func poll<T>(_ pollInterval: TimeInterval, closure: @escaping () throws -> T?) -> AwaitPromiseBuilder<T> {
func poll<T>(_ pollInterval: DispatchTimeInterval, closure: @escaping () throws -> T?) -> AwaitPromiseBuilder<T> {
let promise = AwaitPromise<T>()
let timeoutSource = createTimerSource(timeoutQueue)
let asyncSource = createTimerSource(asyncQueue)
let trigger = AwaitTrigger(timeoutSource: timeoutSource, actionSource: asyncSource) {
let interval = DispatchTimeInterval.nanoseconds(Int(pollInterval * TimeInterval(NSEC_PER_SEC)))
let interval = pollInterval
asyncSource.schedule(deadline: .now(), repeating: interval, leeway: pollLeeway)
asyncSource.setEventHandler {
do {
Expand All @@ -357,8 +353,8 @@ internal class Awaiter {
}

internal func pollBlock(
pollInterval: TimeInterval,
timeoutInterval: TimeInterval,
pollInterval: DispatchTimeInterval,
timeoutInterval: DispatchTimeInterval,
file: FileString,
line: UInt,
fnName: String = #function,
Expand All @@ -369,7 +365,7 @@ internal func pollBlock(
return true
}
return nil
}.timeout(timeoutInterval, forcefullyAbortTimeout: timeoutInterval / 2.0).wait(fnName, file: file, line: line)
}.timeout(timeoutInterval, forcefullyAbortTimeout: timeoutInterval.divided).wait(fnName, file: file, line: line)

return result
}
38 changes: 38 additions & 0 deletions Sources/Nimble/Utils/DispatchTimeInterval.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Dispatch
import Foundation

#if canImport(CDispatch)
import CDispatch
#endif

extension DispatchTimeInterval {
// ** Note: We cannot simply divide the time interval because DispatchTimeInterval associated value type is Int
var divided: DispatchTimeInterval {
switch self {
case let .seconds(val): return val < 2 ? .milliseconds(Int(Float(val)/2*1000)) : .seconds(val/2)
case let .milliseconds(val): return .milliseconds(val/2)
case let .microseconds(val): return .microseconds(val/2)
case let .nanoseconds(val): return .nanoseconds(val/2)
case .never: return .never
@unknown default: fatalError("Unknown DispatchTimeInterval value")
}
}

var description: String {
switch self {
case let .seconds(val): return val == 1 ? "\(Float(val)) second" : "\(Float(val)) seconds"
case let .milliseconds(val): return "\(Float(val)/1_000) seconds"
case let .microseconds(val): return "\(Float(val)/1_000_000) seconds"
case let .nanoseconds(val): return "\(Float(val)/1_000_000_000) seconds"
default: fatalError("Unknown DispatchTimeInterval value")
}
}
}

extension TimeInterval {
var dispatchInterval: DispatchTimeInterval {
let microseconds = Int64(self * TimeInterval(USEC_PER_SEC))
// perhaps use nanoseconds, though would more often be > Int.max
return microseconds < Int.max ? .microseconds(Int(microseconds)) : .seconds(Int(self))
}
}
Loading

0 comments on commit eee98fe

Please sign in to comment.