Skip to content

Commit

Permalink
Cannot conform to Equatable either
Browse files Browse the repository at this point in the history
  • Loading branch information
grynspan committed Aug 19, 2024
1 parent 50b0b25 commit 7f67fb6
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 30 deletions.
118 changes: 89 additions & 29 deletions Sources/Testing/ExitTests/ExitCondition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,6 @@ private import _TestingInternals
/// ``expect(exitsWith:_:sourceLocation:performing:)`` or
/// ``require(exitsWith:_:sourceLocation:performing:)`` to configure which exit
/// statuses should be considered successful.
///
/// Two instances of this type can be compared; if either instance is equal to
/// ``failure``, it will compare equal to any instance except ``success``. To
/// check if two instances are exactly equal, use the `===` operator:
///
/// ```swift
/// let lhs: ExitCondition = .failure
/// let rhs: ExitCondition = .signal(SIGINT)
/// print(lhs == rhs) // prints "true"
/// print(lhs === rhs) // prints "false"
/// ```
@_spi(Experimental)
#if SWT_NO_EXIT_TESTS
@available(*, unavailable, message: "Exit tests are not available on this platform.")
Expand Down Expand Up @@ -89,7 +78,32 @@ public enum ExitCondition: Sendable {
#if SWT_NO_EXIT_TESTS
@available(*, unavailable, message: "Exit tests are not available on this platform.")
#endif
extension ExitCondition: Equatable {
extension ExitCondition {
/// Check whether or not two values of this type are equal.
///
/// - Parameters:
/// - lhs: One value to compare.
/// - rhs: Another value to compare.
///
/// - Returns: Whether or not `lhs` and `rhs` are equal.
///
/// Two instances of this type can be compared; if either instance is equal to
/// ``failure``, it will compare equal to any instance except ``success``. To
/// check if two instances are exactly equal, use the ``===(_:_:)`` operator:
///
/// ```swift
/// let lhs: ExitCondition = .failure
/// let rhs: ExitCondition = .signal(SIGINT)
/// print(lhs == rhs) // prints "true"
/// print(lhs === rhs) // prints "false"
/// ```
///
/// This special behavior means that the ``==(_:_:)`` operator is not
/// transitive, and does not satisfy the requirements of
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
///
/// For any values `a` and `b`, `a == b` implies that `a != b` is `false`.
public static func ==(lhs: Self, rhs: Self) -> Bool {
return switch (lhs, rhs) {
case let (.failure, .exitCode(exitCode)), let (.exitCode(exitCode), .failure):
Expand All @@ -104,6 +118,36 @@ extension ExitCondition: Equatable {
}
}

/// Check whether or not two values of this type are _not_ equal.
///
/// - Parameters:
/// - lhs: One value to compare.
/// - rhs: Another value to compare.
///
/// - Returns: Whether or not `lhs` and `rhs` are _not_ equal.
///
/// Two instances of this type can be compared; if either instance is equal to
/// ``failure``, it will compare equal to any instance except ``success``. To
/// check if two instances are not exactly equal, use the ``!==(_:_:)``
/// operator:
///
/// ```swift
/// let lhs: ExitCondition = .failure
/// let rhs: ExitCondition = .signal(SIGINT)
/// print(lhs != rhs) // prints "false"
/// print(lhs !== rhs) // prints "true"
/// ```
///
/// This special behavior means that the ``!=(_:_:)`` operator is not
/// transitive, and does not satisfy the requirements of
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
///
/// For any values `a` and `b`, `a == b` implies that `a != b` is `false`.
public static func !=(lhs: Self, rhs: Self) -> Bool {
!(lhs == rhs)
}

/// Check whether or not two values of this type are identical.
///
/// - Parameters:
Expand All @@ -112,12 +156,23 @@ extension ExitCondition: Equatable {
///
/// - Returns: Whether or not `lhs` and `rhs` are identical.
///
/// This operator differs from [`==(lhs:rhs:)`](https://developer.apple.com/documentation/swift/equatable/==(_:_:)-3axv1)
/// in that ``failure`` will only compare equal to itself using this operator,
/// but will compare equal to any value except ``success`` when using
/// [`==(lhs:rhs:)`](https://developer.apple.com/documentation/swift/equatable/==(_:_:)-3axv1).
/// Two instances of this type can be compared; if either instance is equal to
/// ``failure``, it will compare equal to any instance except ``success``. To
/// check if two instances are exactly equal, use the ``===(_:_:)`` operator:
///
/// ```swift
/// let lhs: ExitCondition = .failure
/// let rhs: ExitCondition = .signal(SIGINT)
/// print(lhs == rhs) // prints "true"
/// print(lhs === rhs) // prints "false"
/// ```
///
/// For any values `a` and `b`, `a === b` implies that `a !== b` is false.
/// This special behavior means that the ``==(_:_:)`` operator is not
/// transitive, and does not satisfy the requirements of
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
///
/// For any values `a` and `b`, `a === b` implies that `a !== b` is `false`.
public static func ===(lhs: Self, rhs: Self) -> Bool {
return switch (lhs, rhs) {
case (.failure, .failure):
Expand All @@ -141,20 +196,25 @@ extension ExitCondition: Equatable {
///
/// - Returns: Whether or not `lhs` and `rhs` are _not_ identical.
///
/// This operator differs from [`!=(lhs:rhs:)`](https://developer.apple.com/documentation/swift/equatable/!=(_:_:))
/// in that ``failure`` will only compare equal to itself using this operator,
/// but will compare equal to any value except ``success`` when using
/// [`!=(lhs:rhs:)`](https://developer.apple.com/documentation/swift/equatable/!=(_:_:)).
/// Two instances of this type can be compared; if either instance is equal to
/// ``failure``, it will compare equal to any instance except ``success``. To
/// check if two instances are not exactly equal, use the ``!==(_:_:)``
/// operator:
///
/// ```swift
/// let lhs: ExitCondition = .failure
/// let rhs: ExitCondition = .signal(SIGINT)
/// print(lhs != rhs) // prints "false"
/// print(lhs !== rhs) // prints "true"
/// ```
///
/// For any values `a` and `b`, `a === b` implies that `a !== b` is false.
/// This special behavior means that the ``!=(_:_:)`` operator is not
/// transitive, and does not satisfy the requirements of
/// [`Equatable`](https://developer.apple.com/documentation/swift/equatable)
/// or [`Hashable`](https://developer.apple.com/documentation/swift/hashable).
///
/// For any values `a` and `b`, `a === b` implies that `a !== b` is `false`.
public static func !==(lhs: Self, rhs: Self) -> Bool {
!(lhs === rhs)
}
}

// MARK: - Hashable

// Because .failure is fuzzy-matched, the hash of an exit condition cannot
// distinguish failure cases without violating Hashable's contract. Hence, the
// only thing we can hash is whether or not it's a failure. That's a terrible
// hash function, so we have intentionally omitted Hashable conformance.
11 changes: 10 additions & 1 deletion Tests/TestingTests/ExitTestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,29 @@ private import _TestingInternals
}
#endif

@Test("Exit condition exact matching (===)")
@Test("Exit condition matching operators (==, !=, ===, !==)")
func exitConditionMatching() {
#expect(ExitCondition.success == .success)
#expect(ExitCondition.success === .success)
#expect(ExitCondition.success == .exitCode(EXIT_SUCCESS))
#expect(ExitCondition.success === .exitCode(EXIT_SUCCESS))
#expect(ExitCondition.success != .exitCode(EXIT_FAILURE))
#expect(ExitCondition.success !== .exitCode(EXIT_FAILURE))

#expect(ExitCondition.failure == .failure)
#expect(ExitCondition.failure === .failure)

#expect(ExitCondition.exitCode(EXIT_FAILURE &+ 1) != .exitCode(EXIT_FAILURE))
#expect(ExitCondition.exitCode(EXIT_FAILURE &+ 1) !== .exitCode(EXIT_FAILURE))

#if !os(Windows)
#expect(ExitCondition.success != .exitCode(EXIT_FAILURE))
#expect(ExitCondition.success !== .exitCode(EXIT_FAILURE))
#expect(ExitCondition.success != .signal(SIGINT))
#expect(ExitCondition.success !== .signal(SIGINT))
#expect(ExitCondition.signal(SIGINT) == .signal(SIGINT))
#expect(ExitCondition.signal(SIGINT) === .signal(SIGINT))
#expect(ExitCondition.signal(SIGTERM) != .signal(SIGINT))
#expect(ExitCondition.signal(SIGTERM) !== .signal(SIGINT))
#endif
}
Expand Down

0 comments on commit 7f67fb6

Please sign in to comment.