Skip to content

Commit

Permalink
Merge branch 'main' into feature/sendable-done
Browse files Browse the repository at this point in the history
  • Loading branch information
younata authored Dec 12, 2024
2 parents 6254a6d + 9aaef11 commit b5ffcc7
Show file tree
Hide file tree
Showing 29 changed files with 495 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/carthage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
ruby-version: 3.2
bundler-cache: true
- run: ./test carthage
2 changes: 1 addition & 1 deletion .github/workflows/cocoapods.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
ruby-version: 3.2
bundler-cache: true
- run: ./test podspec
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A sample Gemfile
source "https://rubygems.org"

gem 'cocoapods', '~> 1.15'
gem 'jazzy', '~> 0.14'
gem 'cocoapods', '~> 1.16'
gem 'jazzy', '~> 0.15'
73 changes: 38 additions & 35 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
CFPropertyList (3.0.7)
base64
nkf
rexml
activesupport (7.1.3)
activesupport (7.2.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
base64 (0.2.0)
bigdecimal (3.1.6)
benchmark (0.4.0)
bigdecimal (3.1.8)
claide (1.1.0)
cocoapods (1.15.2)
cocoapods (1.16.2)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.15.2)
cocoapods-core (= 1.16.2)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 2.1, < 3.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
Expand All @@ -39,8 +44,8 @@ GEM
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.23.0, < 2.0)
cocoapods-core (1.15.2)
xcodeproj (>= 1.27.0, < 2.0)
cocoapods-core (1.16.2)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
Expand All @@ -60,73 +65,71 @@ GEM
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.2.3)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
drb (2.2.0)
ruby2_keywords
drb (2.2.1)
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
ffi (1.16.3)
ffi (1.17.0)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.8.3)
i18n (1.14.1)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
jazzy (0.14.4)
jazzy (0.15.3)
cocoapods (~> 1.5)
mustache (~> 1.1)
open4 (~> 1.3)
redcarpet (~> 3.4)
rexml (~> 3.2)
rexml (>= 3.2.7, < 4.0)
rouge (>= 2.0.6, < 5.0)
sassc (~> 2.1)
sqlite3 (~> 1.3)
xcinvoke (~> 0.3.0)
json (2.7.1)
json (2.8.1)
liferaft (0.0.6)
mini_portile2 (2.8.5)
minitest (5.22.2)
logger (1.6.1)
mini_portile2 (2.8.7)
minitest (5.25.1)
molinillo (0.8.0)
mustache (1.1.1)
mutex_m (0.2.0)
nanaimo (0.3.0)
nanaimo (0.4.0)
nap (1.1.0)
netrc (0.11.0)
nkf (0.2.0)
open4 (1.3.4)
public_suffix (4.0.7)
redcarpet (3.6.0)
rexml (3.3.6)
strscan
rouge (4.2.0)
rexml (3.3.9)
rouge (4.4.0)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
sassc (2.4.0)
ffi (~> 1.9)
sqlite3 (1.6.9)
securerandom (0.3.1)
sqlite3 (1.7.3)
mini_portile2 (~> 2.8.0)
strscan (3.1.0)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
xcinvoke (0.3.0)
liferaft (~> 0.0.6)
xcodeproj (1.25.0)
xcodeproj (1.27.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (>= 3.3.2, < 4.0)
nanaimo (~> 0.4.0)
rexml (>= 3.3.6, < 4.0)

PLATFORMS
ruby

DEPENDENCIES
cocoapods (~> 1.15)
jazzy (~> 0.14)
cocoapods (~> 1.16)
jazzy (~> 0.15)

BUNDLED WITH
2.1.4
2 changes: 1 addition & 1 deletion Nimble.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Nimble"
s.version = "13.5.0"
s.version = "13.7.0"
s.summary = "A Matcher Framework for Swift and Objective-C"
s.description = <<-DESC
Use Nimble to express the expected outcomes of Swift or Objective-C expressions. Inspired by Cedar.
Expand Down
6 changes: 4 additions & 2 deletions Sources/Nimble/Adapters/AssertionRecorder+Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
closure: () async throws -> Void) async {
let environment = NimbleEnvironment.activeInstance
let oldRecorder = environment.assertionHandler
_ = NMBExceptionCapture(handler: nil, finally: ({
defer {
environment.assertionHandler = oldRecorder
}))
}
environment.assertionHandler = tempAssertionHandler

do {
try await closure()
} catch is RequireError {
// ignore this
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
Expand Down
2 changes: 2 additions & 0 deletions Sources/Nimble/Adapters/AssertionRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public func withAssertionHandler(_ tempAssertionHandler: AssertionHandler,
try capturer.tryBlockThrows {
try closure()
}
} catch is RequireError {
// specifically ignore RequireError, will be caught by the assertion handler.
} catch {
let failureMessage = FailureMessage()
failureMessage.stringValue = "unexpected error thrown: <\(error)>"
Expand Down
6 changes: 5 additions & 1 deletion Sources/Nimble/Adapters/NimbleSwiftTestingHandler.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation
#if canImport(Testing)
import Testing
// See https://github.com/pointfreeco/swift-snapshot-testing/discussions/901#discussioncomment-10605497
// tl;dr: Swift Testing is not available when using UI tests.
// And apparently `private import` - the preferred way to do this - doesn't work.
// So we use a deprecated approach that does work with this.
@_implementationOnly import Testing
#endif

public class NimbleSwiftTestingHandler: AssertionHandler {
Expand Down
46 changes: 30 additions & 16 deletions Sources/Nimble/DSL+Require.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ public func requirea<T>(fileID: String = #fileID, file: FileString = #filePath,
/// `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>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
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 {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -226,8 +226,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `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>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
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 {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -236,8 +236,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `unwraps` 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 unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure @escaping () throws -> T?) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
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 {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -246,8 +246,8 @@ public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, l
/// `unwraps` 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 unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
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 {
try requires(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -256,8 +256,8 @@ public func unwraps<T>(fileID: String = #fileID, file: FileString = #filePath, l
/// `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>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @escaping () async throws -> T?) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil())
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 {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -266,8 +266,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `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>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: () -> (() async throws -> T?)) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
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 {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -276,8 +276,8 @@ public func unwrap<T>(fileID: String = #fileID, file: FileString = #filePath, li
/// `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>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil())
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 {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, try await expression()).toNot(beNil(), description: description)
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -286,6 +286,20 @@ public func unwrapa<T>(fileID: String = #fileID, file: FileString = #filePath, l
/// `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>(fileID: String = #fileID, file: FileString = #filePath, line: UInt = #line, column: UInt = #column, customError: Error? = nil, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil())
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 {
try await requirea(fileID: fileID, file: file, line: line, column: column, customError: customError, expression()).toNot(beNil(), description: description)
}

/// Always fails the test and throw an error to prevent further test execution.
///
/// - Parameter message: A custom message to use in place of the default one.
/// - Parameter customError: A custom error to throw in place of a ``RequireError``.
public func requireFail(_ message: String? = nil, customError: Error? = nil, fileID: String = #fileID, filePath: FileString = #filePath, line: UInt = #line, column: UInt = #column) throws {
let location = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
let handler = NimbleEnvironment.activeInstance.assertionHandler

let msg = message ?? "requireFail() always fails"
handler.assert(false, message: FailureMessage(stringValue: msg), location: location)

throw customError ?? RequireError(message: msg, location: location)
}
2 changes: 1 addition & 1 deletion Sources/Nimble/DSL+Wait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public class NMBWait: NSObject {

internal func blockedRunLoopErrorMessageFor(_ fnName: String, leeway: NimbleTimeInterval) -> String {
// swiftlint:disable:next line_length
return "\(fnName) timed out but was unable to run the timeout handler because the main thread is unresponsive (\(leeway.description) 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."
return "\(fnName) timed out but was unable to run the timeout handler because the main thread is unresponsive. (\(leeway.description) is allowed 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 the run loop which may cause future failures in test runs."
}

/// Wait asynchronously until the done closure is called or the timeout has been reached.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Nimble/Matchers/BeAKindOf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ private func matcherMessage(forClass expectedClass: AnyClass) -> String {
}

/// A Nimble matcher that succeeds when the actual value is an instance of the given class.
public func beAKindOf<T>(_ expectedType: T.Type) -> Matcher<Any> {
public func beAKindOf<T, U>(_ expectedType: T.Type) -> Matcher<U> {
return Matcher.define { actualExpression in
let message: ExpectationMessage

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

/// A Nimble matcher that succeeds when the actual value is an _exact_ instance of the given class.
public func beAnInstanceOf<T>(_ expectedType: T.Type) -> Matcher<Any> {
public func beAnInstanceOf<T, U>(_ expectedType: T.Type) -> Matcher<U> {
let errorMessage = "be an instance of \(String(describing: expectedType))"
return Matcher.define { actualExpression in
let instance = try actualExpression.evaluate()
guard let validInstance = instance else {
guard let validInstance: Any = instance else {
return MatcherResult(
status: .doesNotMatch,
message: .expectedActualValueTo(errorMessage)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Nimble/Matchers/BeIdenticalTo.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// A Nimble matcher that succeeds when the actual value is the same instance
/// as the expected instance.
public func beIdenticalTo(_ expected: AnyObject?) -> Matcher<AnyObject> {
public func beIdenticalTo<T: AnyObject>(_ expected: T?) -> Matcher<T> {
return Matcher.define { actualExpression in
let actual = try actualExpression.evaluate()

Expand Down Expand Up @@ -35,7 +35,7 @@ public func !== (lhs: AsyncExpectation<AnyObject>, rhs: AnyObject?) async {
/// as the expected instance.
///
/// Alias for "beIdenticalTo".
public func be(_ expected: AnyObject?) -> Matcher<AnyObject> {
public func be<T: AnyObject>(_ expected: T?) -> Matcher<T> {
return beIdenticalTo(expected)
}

Expand Down
Loading

0 comments on commit b5ffcc7

Please sign in to comment.