Skip to content

Commit

Permalink
Allow unwrap and pollUnwrap to take in custom descriptions (#1162)
Browse files Browse the repository at this point in the history
Also allow pollUnwrap to take in custom timeout and polling intervals.
Fixes a bug in the async version of withAssertionRecorder where we didn't reset the assertion recorder after tests
  • Loading branch information
younata authored Oct 11, 2024
1 parent cecacf0 commit f459a20
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 40 deletions.
6 changes: 4 additions & 2 deletions Sources/Nimble/Adapters/AssertionRecorder+Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
closure: () async throws -> Void) async {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
_ = NMBExceptionCapture(handler: nil, finally: ({
defer {
environment.assertionHandler = oldRecorder
}))
}
environment.assertionHandler = tempAssertionHandler

do {
try await closure()
} catch is RequireError {
// ignore this
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
Expand Down
2 changes: 2 additions & 0 deletions Sources/Nimble/Adapters/AssertionRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
try capturer.tryBlockThrows {
try closure()
}
} catch is RequireError {
// specifically ignore RequireError, will be caught by the assertion handler.
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
Expand Down
32 changes: 16 additions & 16 deletions Sources/Nimble/DSL+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ public func requirea<T>(fileID: String = #fileID, file: FileString = #filePath,
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -226,8 +226,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -236,8 +236,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `unwraps` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -246,8 +246,8 @@ public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, l
/// `unwraps` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -256,8 +256,8 @@ public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, l
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @escaping () async throws -> T?) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil())
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @escaping () async throws -> T?) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -266,8 +266,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: () -> (() async throws -> T?)) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: () -> (() async throws -> T?)) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -276,8 +276,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `unwrapa` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil())
public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -286,6 +286,6 @@ public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, l
/// `unwrapa` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, description: String? = nil, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}
28 changes: 14 additions & 14 deletions Sources/Nimble/Polling+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -713,50 +713,50 @@ public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expres
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `require(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try require(file: file, line: line, expression()).toEventuallyNot(beNil())
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try require(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `require(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try require(file: file, line: line, expression()).toEventuallyNot(beNil())
public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try require(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `require(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try require(file: file, line: line, expression()).toEventuallyNot(beNil())
public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try require(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: @escaping () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil())
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: @escaping () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil())
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: () -> (() async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwrapa<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil())
public func pollUnwrapa<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
@discardableResult
public func pollUnwrapa<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil())
public func pollUnwrapa<T>(file: FileString = #file, line: UInt = #line, timeout: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
}

#endif // #if !os(WASI)
2 changes: 1 addition & 1 deletion Tests/NimbleTests/AsyncAwaitTest+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ final class AsyncAwaitRequireTest: XCTestCase { // swiftlint:disable:this type_b
}

func testPollUnwrapNegativeCase() async {
await failsWithErrorMessage("expected to eventually not be nil, got nil") {
await failsWithErrorMessage("expected to eventually not be nil, got <nil>") {
try await pollUnwrap { nil as Int? }
}
await failsWithErrorMessage("unexpected error thrown: <\(errorToThrow)>") {
Expand Down
2 changes: 1 addition & 1 deletion Tests/NimbleTests/AsyncAwaitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ final class AsyncAwaitTest: XCTestCase { // swiftlint:disable:this type_body_len
}

func testWaitUntilDetectsStalledMainThreadActivity() async {
let msg = "-waitUntil() timed out but was unable to run the timeout handler because the main thread is unresponsive (0.5 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."
let msg = "Waited more than 1.0 second"
await failsWithErrorMessage(msg) {
await waitUntil(timeout: .seconds(1)) { done in
Thread.sleep(forTimeInterval: 3.0)
Expand Down
42 changes: 42 additions & 0 deletions Tests/NimbleTests/DSLTest.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import XCTest
import Nimble
#if SWIFT_PACKAGE
import NimbleSharedTestHelpers
#endif

private func nonThrowingInt() -> Int {
return 1
Expand Down Expand Up @@ -177,4 +180,43 @@ final class DSLTest: XCTestCase {
expect(records.first?.success).to(beFalse())
expect(records.last?.success).to(beTrue())
}

func testUnwrap() {
expect { try unwrap(Optional.some(1)) }.to(equal(1))

failsWithErrorMessage("expected to not be nil, got <nil>") {
try unwrap(nil as Int?)
}
failsWithErrorMessage("expected to not be nil, got <nil>") {
try unwraps(nil as Int?)
}
failsWithErrorMessage("Custom User Message\nexpected to not be nil, got <nil>") {
try unwrap(description: "Custom User Message", nil as Int?)
}
failsWithErrorMessage("Custom User Message 2\nexpected to not be nil, got <nil>") {
try unwraps(description: "Custom User Message 2", nil as Int?)
}
}

func testUnwrapAsync() async {
@Sendable func asyncOptional(_ value: Int?) async -> Int? {
value
}

await expect { try await unwrap { await asyncOptional(1) } }.to(equal(1))

await failsWithErrorMessage("expected to not be nil, got <nil>") {
try await unwrap { await asyncOptional(nil) }
}
await failsWithErrorMessage("expected to not be nil, got <nil>") {
try await unwrapa(await asyncOptional(nil))
}

await failsWithErrorMessage("Some Message\nexpected to not be nil, got <nil>") {
try await unwrap(description: "Some Message") { await asyncOptional(nil) }
}
await failsWithErrorMessage("Other Message\nexpected to not be nil, got <nil>") {
try await unwrapa(description: "Other Message", await asyncOptional(nil))
}
}
}
Loading

0 comments on commit f459a20

Please sign in to comment.