Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nimble Next #1068

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
47e7f75
Make AsyncExpression conform to Sendable (#1067)
younata Jul 29, 2023
7c66a9f
Make expect for async closures take in Sendable closures (#1070)
younata Jul 29, 2023
a3301f0
The async variants of expect now require Sendable values (#1071)
younata Jul 29, 2023
63743e7
Make AsyncPredicate Sendable and operate only on Sendable types (#1072)
younata Aug 13, 2023
48d606f
Update Require DSL to be (mostly) Sendable. (#1130)
younata Mar 19, 2024
8b5e280
Make FailureMessage sendable. (#1131)
younata Mar 19, 2024
696e860
Make MatcherResult conform to Sendable (#1132)
younata Mar 19, 2024
bde016a
Make the Polling Helpers Sendable (#1133)
younata Mar 19, 2024
a321e37
Make AssertionHandler Sendable (#1141)
younata May 13, 2024
5beb29e
Mark the protocol conformances in BeLogical as retroactive
younata Jun 18, 2024
8b75e85
Mark the synchronous DSL funcs as sending
younata Jun 18, 2024
c60c441
Mark values in AsyncExpression as sending
younata Jun 18, 2024
3abba9d
Reimplement MemoizedClosure to avoid the actor reentrant problem
younata Jun 18, 2024
be285e2
Mark the closures in AsyncExpression as sending
younata Jun 18, 2024
49c8316
Update the PostNotification matcher to be sendable
younata Jun 18, 2024
01b1ed5
Update the BeResult matchers to take in sendable closures
younata Jun 18, 2024
5cd0493
Update the Map matchers to take in sendable closures
younata Jun 18, 2024
b504403
Update the waitUntil DSL to be sendable... ish
younata Jun 18, 2024
599568b
Make NMBMatcher unchecked sendable
younata Jun 18, 2024
27cef92
Mork work on getting the async-version of polling expectations to be …
younata Jun 18, 2024
322f9be
Eliminate concurrency warnings in polling expectations
younata Jul 18, 2024
b4b2dad
Fix concurrency warnings for more objective-c/foundation types (#1165)
younata Oct 14, 2024
ff61b40
Fix the current set of concurrency warnings in tests (#1166)
younata Oct 14, 2024
9c13be6
All parts of the dsl must take in sending (#1167)
younata Oct 14, 2024
7a336bb
Remove usage of the swift 6 only sending keyword (#1168)
younata Oct 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Nimble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
895644DF2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 895644DE2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift */; };
896962412A5FABD000A7929D /* AsyncAllPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896962402A5FABD000A7929D /* AsyncAllPass.swift */; };
8969624A2A5FAD5F00A7929D /* AsyncAllPassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896962452A5FAD4500A7929D /* AsyncAllPassTest.swift */; };
897F84F42BA922B500BF354B /* NSLocking+Nimble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897F84F32BA922B500BF354B /* NSLocking+Nimble.swift */; };
898F28B025D9F4C30052B8D0 /* AlwaysFailMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */; };
899441EF2902EE4B00C1FAF9 /* AsyncAwaitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */; };
899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */; };
Expand All @@ -149,6 +150,7 @@
89D8AC852B3211C600410644 /* CwlCatchException in Frameworks */ = {isa = PBXBuildFile; productRef = 89D8AC842B3211C600410644 /* CwlCatchException */; };
89D8AC872B3211EA00410644 /* CwlPosixPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; platformFilters = (tvos, watchos, ); productRef = 89D8AC862B3211EA00410644 /* CwlPosixPreconditionTesting */; };
89D8AC892B3211EA00410644 /* CwlPreconditionTesting in Frameworks */ = {isa = PBXBuildFile; platformFilters = (driverkit, ios, maccatalyst, macos, xros, ); productRef = 89D8AC882B3211EA00410644 /* CwlPreconditionTesting */; };
89E5E1682BC78724002D54ED /* LockedContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89E5E1672BC78724002D54ED /* LockedContainer.swift */; };
89EEF5A52A03293100988224 /* AsyncMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89EEF5A42A03293100988224 /* AsyncMatcher.swift */; };
89EEF5B72A032C3200988224 /* AsyncPredicateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89EEF5B22A032C2500988224 /* AsyncPredicateTest.swift */; };
89EEF5C02A06211C00988224 /* AsyncHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89EEF5BB2A06210D00988224 /* AsyncHelpers.swift */; };
Expand Down Expand Up @@ -330,13 +332,15 @@
895644DE2C1B71DE0006EC12 /* SwiftTestingSupportTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTestingSupportTest.swift; sourceTree = "<group>"; };
896962402A5FABD000A7929D /* AsyncAllPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAllPass.swift; sourceTree = "<group>"; };
896962452A5FAD4500A7929D /* AsyncAllPassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAllPassTest.swift; sourceTree = "<group>"; };
897F84F32BA922B500BF354B /* NSLocking+Nimble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocking+Nimble.swift"; sourceTree = "<group>"; };
898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysFailMatcher.swift; sourceTree = "<group>"; };
899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitTest.swift; sourceTree = "<group>"; };
899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DSL+AsyncAwait.swift"; sourceTree = "<group>"; };
89B8C60E2C6476A6001F12D3 /* Negation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Negation.swift; sourceTree = "<group>"; };
89B8C6102C6478F2001F12D3 /* NegationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NegationTest.swift; sourceTree = "<group>"; };
89C297CB2A911CDA002A143F /* AsyncTimerSequenceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncTimerSequenceTest.swift; sourceTree = "<group>"; };
89C297CD2A92AB34002A143F /* AsyncPromiseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPromiseTest.swift; sourceTree = "<group>"; };
89E5E1672BC78724002D54ED /* LockedContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockedContainer.swift; sourceTree = "<group>"; };
89EEF5A42A03293100988224 /* AsyncMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncMatcher.swift; sourceTree = "<group>"; };
89EEF5B22A032C2500988224 /* AsyncPredicateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncPredicateTest.swift; sourceTree = "<group>"; };
89EEF5BB2A06210D00988224 /* AsyncHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHelpers.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -624,12 +628,14 @@
isa = PBXGroup;
children = (
1FD8CD261968AB07008ED995 /* PollAwait.swift */,
897F84F32BA922B500BF354B /* NSLocking+Nimble.swift */,
89F5E08B290B8D22001F9377 /* AsyncAwait.swift */,
891A04702AB0164500B46613 /* AsyncTimerSequence.swift */,
1FD8CD271968AB07008ED995 /* SourceLocation.swift */,
1FD8CD281968AB07008ED995 /* Stringers.swift */,
AE4BA9AC1C88DDB500B73906 /* Errors.swift */,
0477153423B740AD00402D4E /* NimbleTimeInterval.swift */,
89E5E1672BC78724002D54ED /* LockedContainer.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -864,6 +870,7 @@
1F1871D91CA89EF100A34BF2 /* NMBExpectation.swift in Sources */,
DA9E8C831A414BB9002633C2 /* DSL+Wait.swift in Sources */,
DDB1BC7A1A92235600F743C3 /* AllPass.swift in Sources */,
89E5E1682BC78724002D54ED /* LockedContainer.swift in Sources */,
1FD8CD3F1968AB07008ED995 /* BeAKindOf.swift in Sources */,
1FD8CD2F1968AB07008ED995 /* AssertionRecorder.swift in Sources */,
7B13BA061DD360AA00C9098C /* ContainElementSatisfying.swift in Sources */,
Expand Down Expand Up @@ -892,6 +899,7 @@
1FD8CD571968AB07008ED995 /* Contain.swift in Sources */,
7A0A26231E7F52360092A34E /* ToSucceed.swift in Sources */,
89F5E0862908E655001F9377 /* Polling+AsyncAwait.swift in Sources */,
897F84F42BA922B500BF354B /* NSLocking+Nimble.swift in Sources */,
899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */,
1FD8CD491968AB07008ED995 /* BeGreaterThanOrEqualTo.swift in Sources */,
1FE661571E6574E30035F243 /* ExpectationMessage.swift in Sources */,
Expand Down
16 changes: 13 additions & 3 deletions Sources/Nimble/Adapters/AdapterProtocols.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Protocol for the assertion handler that Nimble uses for all expectations.
public protocol AssertionHandler {
public protocol AssertionHandler: Sendable {
func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation)
}

Expand All @@ -10,11 +10,21 @@ public protocol AssertionHandler {
/// before using any matchers, otherwise Nimble will abort the program.
///
/// @see AssertionHandler
public var NimbleAssertionHandler: AssertionHandler = { () -> AssertionHandler in
public var NimbleAssertionHandler: AssertionHandler {
// swiftlint:disable:previous identifier_name
get {
_NimbleAssertionHandler.value
}
set {
_NimbleAssertionHandler.set(newValue)
}
}

private let _NimbleAssertionHandler = LockedContainer<AssertionHandler> {
// swiftlint:disable:previous identifier_name
if isSwiftTestingAvailable() || isXCTestAvailable() {
return NimbleTestingHandler()
}

return NimbleTestingUnavailableHandler()
}()
}
2 changes: 1 addition & 1 deletion Sources/Nimble/Adapters/AssertionDispatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// @warning Does not fully dispatch if one of the handlers raises an exception.
/// This is possible with XCTest-based assertion handlers.
///
public class AssertionDispatcher: AssertionHandler {
public final class AssertionDispatcher: AssertionHandler {
let handlers: [AssertionHandler]

public init(handlers: [AssertionHandler]) {
Expand Down
14 changes: 11 additions & 3 deletions Sources/Nimble/Adapters/AssertionRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
///
/// @see AssertionRecorder
/// @see AssertionHandler
public struct AssertionRecord: CustomStringConvertible {
public struct AssertionRecord: CustomStringConvertible, Sendable {
/// Whether the assertion succeeded or failed
public let success: Bool
/// The failure message the assertion would display on failure.
Expand All @@ -20,9 +20,17 @@ public struct AssertionRecord: CustomStringConvertible {
/// This is useful for testing failure messages for matchers.
///
/// @see AssertionHandler
public class AssertionRecorder: AssertionHandler {
public final class AssertionRecorder: AssertionHandler {
/// All the assertions that were captured by this recorder
public var assertions = [AssertionRecord]()
public var assertions: [AssertionRecord] {
get {
_assertion.value
}
set {
_assertion.set(newValue)
}
}
private let _assertion = LockedContainer([AssertionRecord]())

public init() {}

Expand Down
29 changes: 22 additions & 7 deletions Sources/Nimble/Adapters/NMBExpectation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,42 @@ private func from(objcMatcher: NMBMatcher) -> Matcher<NSObject> {
}

// Equivalent to Expectation, but for Nimble's Objective-C interface
public class NMBExpectation: NSObject {
internal let _actualBlock: () -> NSObject?
internal var _negative: Bool
public final class NMBExpectation: NSObject, Sendable {
internal let _actualBlock: @Sendable () -> NSObject?
internal let _negative: Bool
internal let _file: FileString
internal let _line: UInt
internal var _timeout: NimbleTimeInterval = .seconds(1)
internal let _timeout: NimbleTimeInterval

@objc public init(actualBlock: @escaping () -> NSObject?, negative: Bool, file: FileString, line: UInt) {
@objc public init(actualBlock: @escaping @Sendable () -> NSObject?, negative: Bool, file: FileString, line: UInt) {
self._actualBlock = actualBlock
self._negative = negative
self._file = file
self._line = line
self._timeout = .seconds(1)
}

private init(actualBlock: @escaping @Sendable () -> NSObject?, negative: Bool, file: FileString, line: UInt, timeout: NimbleTimeInterval) {
self._actualBlock = actualBlock
self._negative = negative
self._file = file
self._line = line
self._timeout = timeout
}

private var expectValue: SyncExpectation<NSObject> {
return expect(file: _file, line: _line, self._actualBlock() as NSObject?)
}

@objc public var withTimeout: (TimeInterval) -> NMBExpectation {
return { timeout in self._timeout = timeout.nimbleInterval
return self
return { timeout in
NMBExpectation(
actualBlock: self._actualBlock,
negative: self._negative,
file: self._file,
line: self._line,
timeout: timeout.nimbleInterval
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Nimble/Adapters/NimbleSwiftTestingHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
@_implementationOnly import Testing
#endif

public class NimbleSwiftTestingHandler: AssertionHandler {
public struct NimbleSwiftTestingHandler: AssertionHandler {
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
if !assertion {
recordTestingFailure("\(message.stringValue)\n", location: location)
Expand Down
29 changes: 21 additions & 8 deletions Sources/Nimble/Adapters/NimbleXCTestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation
import XCTest

/// Default handler for Nimble. This assertion handler passes on to Swift Testing or XCTest.
public class NimbleTestingHandler: AssertionHandler {
public struct NimbleTestingHandler: AssertionHandler {
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
if isRunningSwiftTest() {
NimbleSwiftTestingHandler().assert(assertion, message: message, location: location)
Expand All @@ -13,7 +13,7 @@ public class NimbleTestingHandler: AssertionHandler {
}

/// This assertion handler passes failures along to XCTest.
public class NimbleXCTestHandler: AssertionHandler {
public struct NimbleXCTestHandler: AssertionHandler {
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
if !assertion {
recordFailure("\(message.stringValue)\n", location: location)
Expand All @@ -23,7 +23,7 @@ public class NimbleXCTestHandler: AssertionHandler {

/// Alternative handler for Nimble. This assertion handler passes failures along
/// to XCTest by attempting to reduce the failure message size.
public class NimbleShortXCTestHandler: AssertionHandler {
public struct NimbleShortXCTestHandler: AssertionHandler {
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
if !assertion {
let msg: String
Expand All @@ -39,32 +39,45 @@ public class NimbleShortXCTestHandler: AssertionHandler {

/// Fallback handler in case XCTest/Swift Testing is unavailable. This assertion handler will abort
/// the program if it is invoked.
class NimbleTestingUnavailableHandler: AssertionHandler {
struct NimbleTestingUnavailableHandler: AssertionHandler {
func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
fatalError("XCTest and Swift Testing are not available and no custom assertion handler was configured. Aborting.")
}
}

#if canImport(Darwin)
/// Helper class providing access to the currently executing XCTestCase instance, if any
@objc final public class CurrentTestCaseTracker: NSObject, XCTestObservation {
@objc final public class CurrentTestCaseTracker: NSObject, XCTestObservation, @unchecked Sendable {
@objc public static let sharedInstance = CurrentTestCaseTracker()

private(set) var currentTestCase: XCTestCase?
private let lock = NSRecursiveLock()

private var _currentTestCase: XCTestCase?
var currentTestCase: XCTestCase? {
lock.lock()
defer { lock.unlock() }
return _currentTestCase
}

private var stashed_swift_reportFatalErrorsToDebugger: Bool = false

@objc public func testCaseWillStart(_ testCase: XCTestCase) {
lock.lock()
defer { lock.unlock() }

#if (os(macOS) || os(iOS) || os(visionOS)) && !SWIFT_PACKAGE
stashed_swift_reportFatalErrorsToDebugger = _swift_reportFatalErrorsToDebugger
_swift_reportFatalErrorsToDebugger = false
#endif

currentTestCase = testCase
_currentTestCase = testCase
}

@objc public func testCaseDidFinish(_ testCase: XCTestCase) {
currentTestCase = nil
lock.lock()
defer { lock.unlock() }

_currentTestCase = nil

#if (os(macOS) || os(iOS) || os(visionOS)) && !SWIFT_PACKAGE
_swift_reportFatalErrorsToDebugger = stashed_swift_reportFatalErrorsToDebugger
Expand Down
Loading
Loading