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

Make AssertionHandler Sendable #1141

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions Nimble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,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 @@ -331,6 +332,7 @@
899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DSL+AsyncAwait.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 @@ -621,6 +623,7 @@
1FD8CD281968AB07008ED995 /* Stringers.swift */,
AE4BA9AC1C88DDB500B73906 /* Errors.swift */,
0477153423B740AD00402D4E /* NimbleTimeInterval.swift */,
89E5E1672BC78724002D54ED /* LockedContainer.swift */,
);
path = Utils;
sourceTree = "<group>";
Expand Down Expand Up @@ -855,6 +858,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
21 changes: 17 additions & 4 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,7 +10,20 @@ 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
return isXCTestAvailable() ? NimbleXCTestHandler() : NimbleXCTestUnavailableHandler()
}()
get {
_NimbleAssertionHandler.value
}
set {
_NimbleAssertionHandler.set(newValue)
}
}
private let _NimbleAssertionHandler = LockedContainer<AssertionHandler> {
// swiftlint:disable:previous identifier_name
if isXCTestAvailable() {
return NimbleXCTestHandler() as AssertionHandler
} else {
return NimbleXCTestUnavailableHandler() as AssertionHandler
}
}
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
27 changes: 20 additions & 7 deletions Sources/Nimble/Adapters/NimbleXCTestHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest

/// Default handler for Nimble. This assertion handler passes failures along to
/// XCTest.
public class NimbleXCTestHandler: AssertionHandler {
public final class NimbleXCTestHandler: AssertionHandler {
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
if !assertion {
recordFailure("\(message.stringValue)\n", location: location)
Expand All @@ -13,7 +13,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 final class NimbleShortXCTestHandler: AssertionHandler {
public func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
if !assertion {
let msg: String
Expand All @@ -29,32 +29,45 @@ public class NimbleShortXCTestHandler: AssertionHandler {

/// Fallback handler in case XCTest is unavailable. This assertion handler will abort
/// the program if it is invoked.
class NimbleXCTestUnavailableHandler: AssertionHandler {
final class NimbleXCTestUnavailableHandler: AssertionHandler {
func assert(_ assertion: Bool, message: FailureMessage, location: SourceLocation) {
fatalError("XCTest is 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
32 changes: 32 additions & 0 deletions Sources/Nimble/Utils/LockedContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

final class LockedContainer<T: Sendable>: @unchecked Sendable {
private let lock = NSRecursiveLock()
private var _value: T

var value: T {
lock.lock()
defer { lock.unlock() }
return _value
}

init(_ value: T) {
_value = value
}

init(_ closure: () -> T) {
_value = closure()
}

func operate(_ closure: (T) -> T) {
lock.lock()
defer { lock.unlock() }
_value = closure(_value)
}

func set(_ newValue: T) {
lock.lock()
defer { lock.unlock() }
_value = newValue
}
}
Loading