Skip to content

Commit

Permalink
Update Require DSL to largely be Sendable
Browse files Browse the repository at this point in the history
  • Loading branch information
younata committed Mar 17, 2024
1 parent 2575114 commit 104b50c
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 21 deletions.
20 changes: 10 additions & 10 deletions Sources/Nimble/DSL+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public func requires(file: FileString = #file, line: UInt = #line, customError:
/// `require` will return the result of the expression if the matcher passes, and throw an error if not.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func require<T>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @escaping () async throws -> T?) -> AsyncRequirement<T> {
public func require<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @escaping @Sendable () async throws -> T?) -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression,
Expand All @@ -137,7 +137,7 @@ public func require<T>(file: FileString = #file, line: UInt = #line, customError
/// `require` will return the result of the expression if the matcher passes, and throw an error if not.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func require<T>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: () -> (() async throws -> T)) -> AsyncRequirement<T> {
public func require<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: () -> (@Sendable () async throws -> T)) -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
Expand All @@ -151,7 +151,7 @@ public func require<T>(file: FileString = #file, line: UInt = #line, customError
/// `require` will return the result of the expression if the matcher passes, and throw an error if not.
/// if a `customError` is given, then that will be thrown. Otherwise, a ``RequireError`` will be thrown.
@discardableResult
public func require<T>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: () -> (() async throws -> T?)) -> AsyncRequirement<T> {
public func require<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: () -> (@Sendable () async throws -> T?)) -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
Expand All @@ -167,7 +167,7 @@ public func require<T>(file: FileString = #file, line: UInt = #line, customError
///
/// This is provided to avoid confusion between `require -> SyncRequirement` and `require -> AsyncRequirement`.
@discardableResult
public func requirea<T>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure @escaping () async throws -> T?) async -> AsyncRequirement<T> {
public func requirea<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure @escaping @Sendable () async throws -> T?) async -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression,
Expand All @@ -183,7 +183,7 @@ public func requirea<T>(file: FileString = #file, line: UInt = #line, customErro
///
/// This is provided to avoid confusion between `require -> SyncRequirement` and `require -> AsyncRequirement`
@discardableResult
public func requirea<T>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure () -> (() async throws -> T)) async -> AsyncRequirement<T> {
public func requirea<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure () -> (@Sendable () async throws -> T)) async -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
Expand All @@ -199,7 +199,7 @@ public func requirea<T>(file: FileString = #file, line: UInt = #line, customErro
///
/// This is provided to avoid confusion between `require -> SyncRequirement` and `require -> AsyncRequirement`
@discardableResult
public func requirea<T>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure () -> (() async throws -> T?)) async -> AsyncRequirement<T> {
public func requirea<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure () -> (@Sendable () async throws -> T?)) async -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
Expand Down Expand Up @@ -256,7 +256,7 @@ public func unwraps<T>(file: FileString = #file, line: UInt = #line, customError
/// `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>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @escaping () async throws -> T?) async throws -> T {
public func unwrap<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @escaping @Sendable () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, customError: customError, try await expression()).toNot(beNil())
}

Expand All @@ -266,7 +266,7 @@ public func unwrap<T>(file: FileString = #file, line: UInt = #line, customError:
/// `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>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: () -> (() async throws -> T?)) async throws -> T {
public func unwrap<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: () -> (@Sendable () async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, customError: customError, expression()).toNot(beNil())
}

Expand All @@ -276,7 +276,7 @@ public func unwrap<T>(file: FileString = #file, line: UInt = #line, customError:
/// `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>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
public func unwrapa<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure @escaping @Sendable () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, customError: customError, try await expression()).toNot(beNil())
}

Expand All @@ -286,6 +286,6 @@ public func unwrapa<T>(file: FileString = #file, line: UInt = #line, customError
/// `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>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
public func unwrapa<T: Sendable>(file: FileString = #file, line: UInt = #line, customError: Error? = nil, _ expression: @autoclosure () -> (@Sendable () async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, customError: customError, expression()).toNot(beNil())
}
2 changes: 1 addition & 1 deletion Sources/Nimble/Matchers/PostNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private func _postNotifications<Out>(
let message = ExpectationMessage
.expectedTo("post notifications - but was called off the main thread.")
.appended(details: "postNotifications and postDistributedNotifications attempted to run their predicate off the main thread. This is a bug in Nimble.")

Check warning on line 82 in Sources/Nimble/Matchers/PostNotification.swift

View workflow job for this annotation

GitHub Actions / lint

Line Length Violation: Line should be 160 characters or less; currently it has 167 characters (line_length)
return PredicateResult(status: .fail, message: message)
return MatcherResult(status: .fail, message: message)
}

let collectorNotificationsExpression = Expression(
Expand Down
10 changes: 6 additions & 4 deletions Sources/Nimble/Polling+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,9 @@ extension SyncRequirement {
public func alwaysTo(_ matcher: Matcher<Value>, until: NimbleTimeInterval = PollingDefaults.timeout, pollInterval: NimbleTimeInterval = PollingDefaults.pollInterval, description: String? = nil) throws -> Value {
return try toAlways(matcher, until: until, pollInterval: pollInterval, description: description)
}
}

extension SyncRequirement where Value: Sendable {
// MARK: - Async Polling with Synchronous Matchers
/// Tests the actual value using a matcher to match by checking continuously
/// at each pollInterval until the timeout is reached.
Expand Down Expand Up @@ -734,28 +736,28 @@ public func pollUnwraps<T>(file: FileString = #file, line: UInt = #line, _ expre
/// 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 {
public func pollUnwrap<T: Sendable>(file: FileString = #file, line: UInt = #line, _ expression: @escaping @Sendable () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil())
}

/// 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 {
public func pollUnwrap<T: Sendable>(file: FileString = #file, line: UInt = #line, _ expression: () -> (@Sendable () async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil())
}

/// 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 {
public func pollUnwrapa<T: Sendable>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping @Sendable () async throws -> T?) async throws -> T {
try await requirea(file: file, line: line, try await expression()).toEventuallyNot(beNil())
}

/// 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 {
public func pollUnwrapa<T: Sendable>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (@Sendable () async throws -> T?)) async throws -> T {
try await requirea(file: file, line: line, expression()).toEventuallyNot(beNil())
}

Expand Down
6 changes: 4 additions & 2 deletions Sources/Nimble/Requirement.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public struct RequireError: Error, CustomNSError {
public struct RequireError: Error, CustomNSError, Sendable {
let message: String
let location: SourceLocation

Expand Down Expand Up @@ -115,7 +115,9 @@ public struct SyncRequirement<Value> {
public func notTo(_ matcher: Matcher<Value>, description: String? = nil) throws -> Value {
try toNot(matcher, description: description)
}
}

extension SyncRequirement where Value: Sendable {
// MARK: - AsyncMatchers
/// Tests the actual value using a matcher to match.
@discardableResult
Expand All @@ -140,7 +142,7 @@ public struct SyncRequirement<Value> {
}
}

public struct AsyncRequirement<Value> {
public struct AsyncRequirement<Value: Sendable>: Sendable {
public let expression: AsyncExpression<Value>

/// A custom error to throw.
Expand Down
8 changes: 4 additions & 4 deletions Tests/NimbleTests/AsyncAwaitTest+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import NimbleSharedTestHelpers

final class AsyncAwaitRequireTest: XCTestCase { // swiftlint:disable:this type_body_length
func testToPositiveMatches() async throws {
func someAsyncFunction() async throws -> Int {
@Sendable func someAsyncFunction() async throws -> Int {
try await Task.sleep(nanoseconds: 1_000_000) // 1 millisecond
return 1
}
Expand Down Expand Up @@ -49,7 +49,7 @@ final class AsyncAwaitRequireTest: XCTestCase { // swiftlint:disable:this type_b
}

func testPollUnwrapPositiveCase() async {
func someAsyncFunction() async throws -> Int {
@Sendable func someAsyncFunction() async throws -> Int {
try await Task.sleep(nanoseconds: 1_000_000) // 1 millisecond
return 1
}
Expand Down Expand Up @@ -141,7 +141,7 @@ final class AsyncAwaitRequireTest: XCTestCase { // swiftlint:disable:this type_b
func testToEventuallyWithAsyncExpectationDoesNotNecessarilyExecutesExpressionOnMainActor() async throws {
// This prevents a "Class property 'isMainThread' is unavailable from asynchronous contexts; Work intended for the main actor should be marked with @MainActor; this is an error in Swift 6" warning.
// However, the functionality actually works as you'd expect it to, you're just expected to tag things to use the main actor.
func isMainThread() -> Bool { Thread.isMainThread }
@Sendable func isMainThread() -> Bool { Thread.isMainThread }

try await requirea(isMainThread()).toEventually(beFalse())
try await requirea(isMainThread()).toEventuallyNot(beTrue())
Expand All @@ -153,7 +153,7 @@ final class AsyncAwaitRequireTest: XCTestCase { // swiftlint:disable:this type_b
func testToEventuallyWithAsyncExpectationDoesExecuteExpressionOnMainActorWhenTestRunsOnMainActor() async throws {
// This prevents a "Class property 'isMainThread' is unavailable from asynchronous contexts; Work intended for the main actor should be marked with @MainActor; this is an error in Swift 6" warning.
// However, the functionality actually works as you'd expect it to, you're just expected to tag things to use the main actor.
func isMainThread() -> Bool { Thread.isMainThread }
@Sendable func isMainThread() -> Bool { Thread.isMainThread }

try await requirea(isMainThread()).toEventually(beTrue())
try await requirea(isMainThread()).toEventuallyNot(beFalse())
Expand Down

0 comments on commit 104b50c

Please sign in to comment.