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 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c167df3
Make AsyncExpression conform to Sendable (#1067)
younata Jul 29, 2023
fe9e3ff
Make expect for async closures take in Sendable closures (#1070)
younata Jul 29, 2023
de9028b
The async variants of expect now require Sendable values (#1071)
younata Jul 29, 2023
fc988c0
Make AsyncPredicate Sendable and operate only on Sendable types (#1072)
younata Aug 13, 2023
4876c2a
Update Require DSL to be (mostly) Sendable. (#1130)
younata Mar 19, 2024
b579af3
Make FailureMessage sendable. (#1131)
younata Mar 19, 2024
a48a917
Make MatcherResult conform to Sendable (#1132)
younata Mar 19, 2024
89cfb34
Make the Polling Helpers Sendable (#1133)
younata Mar 19, 2024
286aa52
Make AssertionHandler Sendable (#1141)
younata May 13, 2024
44643f3
Mark the protocol conformances in BeLogical as retroactive
younata Jun 18, 2024
5333774
Mark the synchronous DSL funcs as sending
younata Jun 18, 2024
c9fa2bf
Mark values in AsyncExpression as sending
younata Jun 18, 2024
25978ba
Reimplement MemoizedClosure to avoid the actor reentrant problem
younata Jun 18, 2024
aad73c7
Mark the closures in AsyncExpression as sending
younata Jun 18, 2024
5bdd76b
Update the PostNotification matcher to be sendable
younata Jun 18, 2024
b96603a
Update the BeResult matchers to take in sendable closures
younata Jun 18, 2024
414ee29
Update the Map matchers to take in sendable closures
younata Jun 18, 2024
0c9ff36
Update the waitUntil DSL to be sendable... ish
younata Jun 18, 2024
60962fb
Make NMBMatcher unchecked sendable
younata Jun 18, 2024
12c29d2
Mork work on getting the async-version of polling expectations to be …
younata Jun 18, 2024
231b16c
Eliminate concurrency warnings in polling expectations
younata Jul 18, 2024
24fd17a
Fix concurrency warnings for more objective-c/foundation types (#1165)
younata Oct 14, 2024
fc7bb4c
Fix the current set of concurrency warnings in tests (#1166)
younata Oct 14, 2024
12c1c0c
All parts of the dsl must take in sending (#1167)
younata Oct 14, 2024
def883e
Remove usage of the swift 6 only sending keyword (#1168)
younata Oct 14, 2024
c370ca3
Replace usage of all the FileID/FilePath/line/column macros with Sour…
younata Dec 20, 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
6 changes: 1 addition & 5 deletions Sources/Nimble/Adapters/AssertionRecorder+Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
///
/// @see AssertionHandler
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
fileID: String = #fileID,
file: FileString = #filePath,
line: UInt = #line,
column: UInt = #column,
location: SourceLocation = SourceLocation(),
closure: () async throws -> Void) async {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
Expand All @@ -27,7 +24,6 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
let location = SourceLocation(fileID: fileID, filePath: file, line: line, column: column)
tempAssertionHandler.assert(false, message: failureMessage, location: location)
}
}
Expand Down
24 changes: 12 additions & 12 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 Expand Up @@ -63,10 +71,7 @@ extension NMBExceptionCapture {
///
/// @see AssertionHandler
public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
fileID: String = #fileID,
file: FileString = #filePath,
line: UInt = #line,
column: UInt = #column,
location: SourceLocation = SourceLocation(),
closure: () throws -> Void) {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
Expand All @@ -84,11 +89,6 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
let location = SourceLocation(
fileID: fileID,
filePath: file,
line: line, column: column
)
tempAssertionHandler.assert(false, message: failureMessage, location: location)
}
}
Expand Down
31 changes: 23 additions & 8 deletions Sources/Nimble/Adapters/NMBExpectation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,42 @@
}

// 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?)
return expect(location: SourceLocation(fileID: "unknown/\(_file)", filePath: _file, line: _line, column: 0), 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 Expand Up @@ -173,7 +188,7 @@
return toAlwaysWithDescription
}

@objc public class func failWithMessage(_ message: String, file: FileString, line: UInt) {

Check warning on line 191 in Sources/Nimble/Adapters/NMBExpectation.swift

View workflow job for this annotation

GitHub Actions / lint

Static Over Final Class Violation: Prefer `static` over `class` in a final class (static_over_final_class)
fail(message, location: SourceLocation(fileID: "Unknown/\(file)", filePath: file, line: line, column: 0))
}
}
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