Skip to content

Commit f459a20

Browse files
authored
Allow unwrap and pollUnwrap to take in custom descriptions (#1162)
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
1 parent cecacf0 commit f459a20

13 files changed

+129
-40
lines changed

Sources/Nimble/Adapters/AssertionRecorder+Async.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
1515
closure: () async throws -> Void) async {
1616
let environment = NimbleEnvironment.activeInstance
1717
let oldRecorder = environment.assertionHandler
18-
_ = NMBExceptionCapture(handler: nil, finally: ({
18+
defer {
1919
environment.assertionHandler = oldRecorder
20-
}))
20+
}
2121
environment.assertionHandler = tempAssertionHandler
2222

2323
do {
2424
try await closure()
25+
} catch is RequireError {
26+
// ignore this
2527
} catch {
2628
let failureMessage = FailureMessage()
2729
failureMessage.stringValue = "unexpected error thrown: <\(error)>"

Sources/Nimble/Adapters/AssertionRecorder.swift

+2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
7979
try capturer.tryBlockThrows {
8080
try closure()
8181
}
82+
} catch is RequireError {
83+
// specifically ignore RequireError, will be caught by the assertion handler.
8284
} catch {
8385
let failureMessage = FailureMessage()
8486
failureMessage.stringValue = "unexpected error thrown: <\(error)>"

Sources/Nimble/DSL+Require.swift

+16-16
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ public func requirea<T>(fileID: String = #fileID, file: FileString = #filePath,
216216
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
217217
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
218218
@discardableResult
219-
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 {
220-
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
219+
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 {
220+
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
221221
}
222222

223223
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
@@ -226,8 +226,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
226226
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
227227
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
228228
@discardableResult
229-
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 {
230-
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
229+
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 {
230+
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
231231
}
232232

233233
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
@@ -236,8 +236,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
236236
/// `unwraps` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
237237
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
238238
@discardableResult
239-
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 {
240-
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
239+
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 {
240+
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
241241
}
242242

243243
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
@@ -246,8 +246,8 @@ public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, l
246246
/// `unwraps` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
247247
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
248248
@discardableResult
249-
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 {
250-
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
249+
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 {
250+
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
251251
}
252252

253253
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
@@ -256,8 +256,8 @@ public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, l
256256
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
257257
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
258258
@discardableResult
259-
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 {
260-
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil())
259+
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 {
260+
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil(), description: description)
261261
}
262262

263263
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
@@ -266,8 +266,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
266266
/// `unwrap` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
267267
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
268268
@discardableResult
269-
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 {
270-
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
269+
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 {
270+
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
271271
}
272272

273273
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
@@ -276,8 +276,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
276276
/// `unwrapa` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
277277
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
278278
@discardableResult
279-
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 {
280-
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil())
279+
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 {
280+
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil(), description: description)
281281
}
282282

283283
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
@@ -286,6 +286,6 @@ public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, l
286286
/// `unwrapa` will return the result of the expression if it is non-nil, and throw an error if the value is nil.
287287
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
288288
@discardableResult
289-
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 {
290-
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
289+
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 {
290+
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
291291
}

Sources/Nimble/Polling+Require.swift

+14-14
Original file line numberDiff line numberDiff line change
@@ -713,50 +713,50 @@ public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expres
713713
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
714714
/// As you can tell, this is a much less verbose equivalent to `require(expression).toEventuallyNot(beNil())`
715715
@discardableResult
716-
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
717-
try require(file: file, line: line, expression()).toEventuallyNot(beNil())
716+
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 {
717+
try require(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
718718
}
719719

720720
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
721721
/// As you can tell, this is a much less verbose equivalent to `require(expression).toEventuallyNot(beNil())`
722722
@discardableResult
723-
public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
724-
try require(file: file, line: line, expression()).toEventuallyNot(beNil())
723+
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 {
724+
try require(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
725725
}
726726

727727
/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
728728
/// As you can tell, this is a much less verbose equivalent to `require(expression).toEventuallyNot(beNil())`
729729
@discardableResult
730-
public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
731-
try require(file: file, line: line, expression()).toEventuallyNot(beNil())
730+
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 {
731+
try require(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
732732
}
733733

734734
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
735735
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
736736
@discardableResult
737-
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: @escaping () async throws -> T?) async throws -> T {
738-
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil())
737+
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 {
738+
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
739739
}
740740

741741
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
742742
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
743743
@discardableResult
744-
public func pollUnwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T?)) async throws -> T {
745-
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil())
744+
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 {
745+
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
746746
}
747747

748748
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
749749
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
750750
@discardableResult
751-
public func pollUnwrapa<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
752-
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil())
751+
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 {
752+
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
753753
}
754754

755755
/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
756756
/// As you can tell, this is a much less verbose equivalent to `requirea(expression).toEventuallyNot(beNil())`
757757
@discardableResult
758-
public func pollUnwrapa<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
759-
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil())
758+
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 {
759+
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil(), timeout: timeout, pollInterval: pollInterval, description: description)
760760
}
761761

762762
#endif // #if !os(WASI)

Tests/NimbleTests/AsyncAwaitTest+Require.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ final class AsyncAwaitRequireTest: XCTestCase { // swiftlint:disable:this type_b
5959
}
6060

6161
func testPollUnwrapNegativeCase() async {
62-
await failsWithErrorMessage("expected to eventually not be nil, got nil") {
62+
await failsWithErrorMessage("expected to eventually not be nil, got <nil>") {
6363
try await pollUnwrap { nil as Int? }
6464
}
6565
await failsWithErrorMessage("unexpected error thrown: <\(errorToThrow)>") {

Tests/NimbleTests/AsyncAwaitTest.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ final class AsyncAwaitTest: XCTestCase { // swiftlint:disable:this type_body_len
233233
}
234234

235235
func testWaitUntilDetectsStalledMainThreadActivity() async {
236-
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."
236+
let msg = "Waited more than 1.0 second"
237237
await failsWithErrorMessage(msg) {
238238
await waitUntil(timeout: .seconds(1)) { done in
239239
Thread.sleep(forTimeInterval: 3.0)

Tests/NimbleTests/DSLTest.swift

+42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import XCTest
22
import Nimble
3+
#if SWIFT_PACKAGE
4+
import NimbleSharedTestHelpers
5+
#endif
36

47
private func nonThrowingInt() -> Int {
58
return 1
@@ -177,4 +180,43 @@ final class DSLTest: XCTestCase {
177180
expect(records.first?.success).to(beFalse())
178181
expect(records.last?.success).to(beTrue())
179182
}
183+
184+
func testUnwrap() {
185+
expect { try unwrap(Optional.some(1)) }.to(equal(1))
186+
187+
failsWithErrorMessage("expected to not be nil, got <nil>") {
188+
try unwrap(nil as Int?)
189+
}
190+
failsWithErrorMessage("expected to not be nil, got <nil>") {
191+
try unwraps(nil as Int?)
192+
}
193+
failsWithErrorMessage("Custom User Message\nexpected to not be nil, got <nil>") {
194+
try unwrap(description: "Custom User Message", nil as Int?)
195+
}
196+
failsWithErrorMessage("Custom User Message 2\nexpected to not be nil, got <nil>") {
197+
try unwraps(description: "Custom User Message 2", nil as Int?)
198+
}
199+
}
200+
201+
func testUnwrapAsync() async {
202+
@Sendable func asyncOptional(_ value: Int?) async -> Int? {
203+
value
204+
}
205+
206+
await expect { try await unwrap { await asyncOptional(1) } }.to(equal(1))
207+
208+
await failsWithErrorMessage("expected to not be nil, got <nil>") {
209+
try await unwrap { await asyncOptional(nil) }
210+
}
211+
await failsWithErrorMessage("expected to not be nil, got <nil>") {
212+
try await unwrapa(await asyncOptional(nil))
213+
}
214+
215+
await failsWithErrorMessage("Some Message\nexpected to not be nil, got <nil>") {
216+
try await unwrap(description: "Some Message") { await asyncOptional(nil) }
217+
}
218+
await failsWithErrorMessage("Other Message\nexpected to not be nil, got <nil>") {
219+
try await unwrapa(description: "Other Message", await asyncOptional(nil))
220+
}
221+
}
180222
}

0 commit comments

Comments
 (0)