Skip to content

Rebase release/6.0.1 branch off release/6.0 instead of release/6.0.0. #668

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

Closed
wants to merge 9 commits into from
Closed
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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ extension Array where Element == PackageDescription.SwiftSetting {
.enableExperimentalFeature("AvailabilityMacro=_clockAPI:macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0"),
.enableExperimentalFeature("AvailabilityMacro=_regexAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"),
.enableExperimentalFeature("AvailabilityMacro=_swiftVersionAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"),
.enableExperimentalFeature("AvailabilityMacro=_synchronizationAPI:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"),

.enableExperimentalFeature("AvailabilityMacro=_distantFuture:macOS 99.0, iOS 99.0, watchOS 99.0, tvOS 99.0, visionOS 99.0"),
]
Expand Down
21 changes: 15 additions & 6 deletions Sources/Testing/Expectations/ExpectationChecking+Macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ public func __checkValue(
// Post an event for the expectation regardless of whether or not it passed.
// If the current event handler is not configured to handle events of this
// kind, this event is discarded.
var expectation = Expectation(evaluatedExpression: expression, isPassing: condition, isRequired: isRequired, sourceLocation: sourceLocation)
Event.post(.expectationChecked(expectation))
lazy var expectation = Expectation(evaluatedExpression: expression, isPassing: condition, isRequired: isRequired, sourceLocation: sourceLocation)
if Configuration.deliverExpectationCheckedEvents {
Event.post(.expectationChecked(expectation))
}

// Early exit if the expectation passed.
if condition {
Expand Down Expand Up @@ -835,10 +837,11 @@ public func __checkClosureCall<E>(
/// `#require()` macros. Do not call it directly.
public func __checkClosureCall<E>(
throws errorType: E.Type,
performing body: () async throws -> some Any,
performing body: () async throws -> sending some Any,
expression: __Expression,
comments: @autoclosure () -> [Comment],
isRequired: Bool,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation
) async -> Result<Void, any Error> where E: Error {
if errorType == Never.self {
Expand All @@ -848,6 +851,7 @@ public func __checkClosureCall<E>(
expression: expression,
comments: comments(),
isRequired: isRequired,
isolation: isolation,
sourceLocation: sourceLocation
)
} else {
Expand All @@ -858,6 +862,7 @@ public func __checkClosureCall<E>(
expression: expression,
comments: comments(),
isRequired: isRequired,
isolation: isolation,
sourceLocation: sourceLocation
)
}
Expand Down Expand Up @@ -911,10 +916,11 @@ public func __checkClosureCall(
/// `#require()` macros. Do not call it directly.
public func __checkClosureCall(
throws _: Never.Type,
performing body: () async throws -> some Any,
performing body: () async throws -> sending some Any,
expression: __Expression,
comments: @autoclosure () -> [Comment],
isRequired: Bool,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation
) async -> Result<Void, any Error> {
var success = true
Expand Down Expand Up @@ -973,10 +979,11 @@ public func __checkClosureCall<E>(
/// `#require()` macros. Do not call it directly.
public func __checkClosureCall<E>(
throws error: E,
performing body: () async throws -> some Any,
performing body: () async throws -> sending some Any,
expression: __Expression,
comments: @autoclosure () -> [Comment],
isRequired: Bool,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation
) async -> Result<Void, any Error> where E: Error & Equatable {
await __checkClosureCall(
Expand All @@ -986,6 +993,7 @@ public func __checkClosureCall<E>(
expression: expression,
comments: comments(),
isRequired: isRequired,
isolation: isolation,
sourceLocation: sourceLocation
)
}
Expand Down Expand Up @@ -1047,12 +1055,13 @@ public func __checkClosureCall<R>(
/// - Warning: This function is used to implement the `#expect()` and
/// `#require()` macros. Do not call it directly.
public func __checkClosureCall<R>(
performing body: () async throws -> R,
performing body: () async throws -> sending R,
throws errorMatcher: (any Error) async throws -> Bool,
mismatchExplanation: ((any Error) -> String)? = nil,
expression: __Expression,
comments: @autoclosure () -> [Comment],
isRequired: Bool,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation
) async -> Result<Void, any Error> {
var errorMatches = false
Expand Down
13 changes: 9 additions & 4 deletions Sources/Testing/Issues/Confirmation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extension Confirmation {
/// `body` is invoked. The default value of this argument is `1`, indicating
/// that the event should occur exactly once. Pass `0` if the event should
/// _never_ occur when `body` is invoked.
/// - isolation: The actor to which `body` is isolated, if any.
/// - sourceLocation: The source location to which any recorded issues should
/// be attributed.
/// - body: The function to invoke.
Expand Down Expand Up @@ -94,12 +95,14 @@ extension Confirmation {
public func confirmation<R>(
_ comment: Comment? = nil,
expectedCount: Int = 1,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation = #_sourceLocation,
_ body: (Confirmation) async throws -> R
_ body: (Confirmation) async throws -> sending R
) async rethrows -> R {
try await confirmation(
comment,
expectedCount: expectedCount ... expectedCount,
isolation: isolation,
sourceLocation: sourceLocation,
body
)
Expand All @@ -114,6 +117,7 @@ public func confirmation<R>(
/// function.
/// - expectedCount: A range of integers indicating the number of times the
/// expected event should occur when `body` is invoked.
/// - isolation: The actor to which `body` is isolated, if any.
/// - sourceLocation: The source location to which any recorded issues should
/// be attributed.
/// - body: The function to invoke.
Expand Down Expand Up @@ -156,13 +160,14 @@ public func confirmation<R>(
/// preconditions have been met, and records an issue if they have not.
///
/// If an exact count is expected, use
/// ``confirmation(_:expectedCount:sourceLocation:_:)-7kfko`` instead.
/// ``confirmation(_:expectedCount:isolation:sourceLocation:_:)`` instead.
@_spi(Experimental)
public func confirmation<R>(
_ comment: Comment? = nil,
expectedCount: some Confirmation.ExpectedCount,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation = #_sourceLocation,
_ body: (Confirmation) async throws -> R
_ body: (Confirmation) async throws -> sending R
) async rethrows -> R {
let confirmation = Confirmation()
defer {
Expand All @@ -182,7 +187,7 @@ public func confirmation<R>(
@_spi(Experimental)
extension Confirmation {
/// A protocol that describes a range expression that can be used with
/// ``confirmation(_:expectedCount:sourceLocation:_:)-41gmd``.
/// ``confirmation(_:expectedCount:isolation:sourceLocation:_:)-9rt6m``.
///
/// This protocol represents any expression that describes a range of
/// confirmation counts. For example, the expression `1 ..< 10` automatically
Expand Down
2 changes: 2 additions & 0 deletions Sources/Testing/Issues/Issue+Recording.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ extension Issue {
/// - sourceLocation: The source location to attribute any caught error to.
/// - configuration: The test configuration to use when recording an issue.
/// The default value is ``Configuration/current``.
/// - isolation: The actor to which `body` is isolated, if any.
/// - body: An asynchronous closure that might throw an error.
///
/// - Returns: The issue representing the caught error, if any error was
Expand All @@ -204,6 +205,7 @@ extension Issue {
static func withErrorRecording(
at sourceLocation: SourceLocation,
configuration: Configuration? = nil,
isolation: isolated (any Actor)? = #isolation,
_ body: () async throws -> Void
) async -> (any Error)? {
// Ensure that we are capturing backtraces for errors before we start
Expand Down
8 changes: 4 additions & 4 deletions Sources/Testing/Issues/Issue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public struct Issue: Sendable {
/// ``Confirmation/confirm(count:)`` should have been called.
///
/// This issue can occur when calling
/// ``confirmation(_:expectedCount:sourceLocation:_:)`` when the
/// ``confirmation(_:expectedCount:isolation:sourceLocation:_:)`` when the
/// confirmation passed to these functions' `body` closures is confirmed too
/// few or too many times.
indirect case confirmationMiscounted(actual: Int, expected: Int)
Expand All @@ -48,9 +48,9 @@ public struct Issue: Sendable {
/// ``Confirmation/confirm(count:)`` should have been called.
///
/// This issue can occur when calling
/// ``confirmation(_:expectedCount:sourceLocation:_:)-41gmd`` when the
/// confirmation passed to these functions' `body` closures is confirmed too
/// few or too many times.
/// ``confirmation(_:expectedCount:isolation:sourceLocation:_:)-9rt6m`` when
/// the confirmation passed to these functions' `body` closures is confirmed
/// too few or too many times.
@_spi(Experimental)
indirect case confirmationOutOfRange(actual: Int, expected: any Confirmation.ExpectedCount)

Expand Down
14 changes: 9 additions & 5 deletions Sources/Testing/Issues/KnownIssue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public typealias KnownIssueMatcher = @Sendable (_ issue: Issue) -> Bool
/// Because all errors thrown by `body` are caught as known issues, this
/// function is not throwing. If only some errors or issues are known to occur
/// while others should continue to cause test failures, use
/// ``withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)-5vi5n``
/// ``withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)``
/// instead.
public func withKnownIssue(
_ comment: Comment? = nil,
Expand Down Expand Up @@ -161,7 +161,7 @@ public func withKnownIssue(
///
/// It is not necessary to specify both `precondition` and `issueMatcher` if
/// only one is relevant. If all errors and issues should be considered known
/// issues, use ``withKnownIssue(_:isIntermittent:sourceLocation:_:)-95r6o``
/// issues, use ``withKnownIssue(_:isIntermittent:sourceLocation:_:)``
/// instead.
///
/// - Note: `issueMatcher` may be invoked more than once for the same issue.
Expand Down Expand Up @@ -200,6 +200,7 @@ public func withKnownIssue(
/// - isIntermittent: Whether or not the known issue occurs intermittently. If
/// this argument is `true` and the known issue does not occur, no secondary
/// issue is recorded.
/// - isolation: The actor to which `body` is isolated, if any.
/// - sourceLocation: The source location to which any recorded issues should
/// be attributed.
/// - body: The function to invoke.
Expand All @@ -218,15 +219,16 @@ public func withKnownIssue(
/// Because all errors thrown by `body` are caught as known issues, this
/// function is not throwing. If only some errors or issues are known to occur
/// while others should continue to cause test failures, use
/// ``withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)-47y3z``
/// ``withKnownIssue(_:isIntermittent:isolation:sourceLocation:_:when:matching:)``
/// instead.
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation = #_sourceLocation,
_ body: () async throws -> Void
) async {
try? await withKnownIssue(comment, isIntermittent: isIntermittent, sourceLocation: sourceLocation, body, matching: { _ in true })
try? await withKnownIssue(comment, isIntermittent: isIntermittent, isolation: isolation, sourceLocation: sourceLocation, body, matching: { _ in true })
}

/// Invoke a function that has a known issue that is expected to occur during
Expand All @@ -237,6 +239,7 @@ public func withKnownIssue(
/// - isIntermittent: Whether or not the known issue occurs intermittently. If
/// this argument is `true` and the known issue does not occur, no secondary
/// issue is recorded.
/// - isolation: The actor to which `body` is isolated, if any.
/// - sourceLocation: The source location to which any recorded issues should
/// be attributed.
/// - body: The function to invoke.
Expand Down Expand Up @@ -269,13 +272,14 @@ public func withKnownIssue(
///
/// It is not necessary to specify both `precondition` and `issueMatcher` if
/// only one is relevant. If all errors and issues should be considered known
/// issues, use ``withKnownIssue(_:isIntermittent:sourceLocation:_:)-3g6b7``
/// issues, use ``withKnownIssue(_:isIntermittent:isolation:sourceLocation:_:when:matching:)``
/// instead.
///
/// - Note: `issueMatcher` may be invoked more than once for the same issue.
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
isolation: isolated (any Actor)? = #isolation,
sourceLocation: SourceLocation = #_sourceLocation,
_ body: () async throws -> Void,
when precondition: () async -> Bool = { true },
Expand Down
11 changes: 11 additions & 0 deletions Sources/Testing/Parameterization/TypeInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public struct TypeInfo: Sendable {
// MARK: - Name

extension TypeInfo {
/// An in-memory cache of fully-qualified type name components.
private static let _fullyQualifiedNameComponentsCache = Locked<[ObjectIdentifier: [String]]>()

/// The complete name of this type, with the names of all referenced types
/// fully-qualified by their module names when possible.
///
Expand All @@ -92,6 +95,10 @@ extension TypeInfo {
public var fullyQualifiedNameComponents: [String] {
switch _kind {
case let .type(type):
if let cachedResult = Self._fullyQualifiedNameComponentsCache.rawValue[ObjectIdentifier(type)] {
return cachedResult
}

var result = String(reflecting: type)
.split(separator: ".")
.map(String.init)
Expand All @@ -109,6 +116,10 @@ extension TypeInfo {
// those out as they're uninteresting to us.
result = result.filter { !$0.starts(with: "(unknown context at") }

Self._fullyQualifiedNameComponentsCache.withLock { fullyQualifiedNameComponentsCache in
fullyQualifiedNameComponentsCache[ObjectIdentifier(type)] = result
}

return result
case let .nameOnly(fullyQualifiedNameComponents, _, _):
return fullyQualifiedNameComponents
Expand Down
44 changes: 37 additions & 7 deletions Sources/Testing/Running/Runner.RuntimeState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

private import Synchronization

extension Runner {
/// A type which collects the task-scoped runtime state for a running
/// ``Runner`` instance, the tests it runs, and other objects it interacts
Expand Down Expand Up @@ -111,7 +113,10 @@ extension Configuration {
/// - Returns: A unique number identifying `self` that can be
/// passed to `_removeFromAll(identifiedBy:)`` to unregister it.
private func _addToAll() -> UInt64 {
Self._all.withLock { all in
if deliverExpectationCheckedEvents, #available(_synchronizationAPI, *) {
Self._deliverExpectationCheckedEventsCount.add(1, ordering: .sequentiallyConsistent)
}
return Self._all.withLock { all in
let id = all.nextID
all.nextID += 1
all.instances[id] = self
Expand All @@ -123,12 +128,37 @@ extension Configuration {
///
/// - Parameters:
/// - id: The unique identifier of this instance, as previously returned by
/// `_addToAll()`. If `nil`, this function has no effect.
private func _removeFromAll(identifiedBy id: UInt64?) {
if let id {
Self._all.withLock { all in
_ = all.instances.removeValue(forKey: id)
}
/// `_addToAll()`.
private func _removeFromAll(identifiedBy id: UInt64) {
let configuration = Self._all.withLock { all in
all.instances.removeValue(forKey: id)
}
if let configuration, configuration.deliverExpectationCheckedEvents, #available(_synchronizationAPI, *) {
Self._deliverExpectationCheckedEventsCount.subtract(1, ordering: .sequentiallyConsistent)
}
}

/// An atomic counter that tracks the number of "current" configurations that
/// have set ``deliverExpectationCheckedEvents`` to `true`.
///
/// On older Apple platforms, this property is not available and ``all`` is
/// directly consulted instead (which is less efficient.)
@available(_synchronizationAPI, *)
private static let _deliverExpectationCheckedEventsCount = Atomic(0)

/// Whether or not events of the kind
/// ``Event/Kind-swift.enum/expectationChecked(_:)`` should be delivered to
/// the event handler of _any_ configuration set as current for a task in the
/// current process.
///
/// To determine if an individual instance of ``Configuration`` is listening
/// for these events, consult the per-instance
/// ``Configuration/deliverExpectationCheckedEvents`` property.
static var deliverExpectationCheckedEvents: Bool {
if #available(_synchronizationAPI, *) {
_deliverExpectationCheckedEventsCount.load(ordering: .sequentiallyConsistent) > 0
} else {
all.contains(where: \.deliverExpectationCheckedEvents)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/Support/Versions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ let simulatorVersion: String = {
///
/// This value is not part of the public interface of the testing library.
var testingLibraryVersion: String {
SWT_TESTING_LIBRARY_VERSION
swt_getTestingLibraryVersion().flatMap(String.init(validatingCString:)) ?? "unknown"
}

/// A human-readable string describing the Swift Standard Library's version.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Testing/Testing.docc/Expectations.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ the test when the code doesn't satisfy a requirement, use
### Confirming that asynchronous events occur

- <doc:testing-asynchronous-code>
- ``confirmation(_:expectedCount:sourceLocation:_:)``
- ``confirmation(_:expectedCount:isolation:sourceLocation:_:)``
- ``Confirmation``

### Retrieving information about checked expectations
Expand Down
Loading