480 lines
19 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import XCTest
#if FOUNDATION_FRAMEWORK
import Foundation
#else
import FoundationEssentials
#endif
extension Optional {
@available(*, unavailable, message: "Use XCTUnwrap() instead")
func unwrapped(_ fn: String = #function, file: StaticString = #filePath, line: UInt = #line) throws -> Wrapped {
return try XCTUnwrap(self, file: file, line: line)
}
}
func expectThrows<Error: Swift.Error & Equatable>(_ expectedError: Error, _ test: () throws -> Void, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
var caught = false
do {
try test()
} catch let error as Error {
caught = true
XCTAssertEqual(error, expectedError, message(), file: file, line: line)
} catch {
caught = true
XCTFail("Incorrect error thrown: \(error) -- \(message())", file: file, line: line)
}
XCTAssert(caught, "No error thrown -- \(message())", file: file, line: line)
}
func expectDoesNotThrow(_ test: () throws -> Void, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertNoThrow(try test(), message(), file: file, line: line)
}
func expectTrue(_ actual: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertTrue(actual, message(), file: file, line: line)
}
func expectFalse(_ actual: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertFalse(actual, message(), file: file, line: line)
}
public func expectEqual<T: Equatable>(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertEqual(expected, actual, message(), file: file, line: line)
}
public func expectNotEqual<T: Equatable>(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertNotEqual(expected, actual, message(), file: file, line: line)
}
public func expectEqual<T: FloatingPoint>(_ expected: T, _ actual: T, within: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertEqual(expected, actual, accuracy: within, message(), file: file, line: line)
}
public func expectEqual<T: FloatingPoint>(_ expected: T?, _ actual: T, within: T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertNotNil(expected, message(), file: file, line: line)
if let expected = expected {
XCTAssertEqual(expected, actual, accuracy: within, message(), file: file, line: line)
}
}
public func expectEqual(
_ expected: Any.Type,
_ actual: Any.Type,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) {
XCTAssertTrue(expected == actual, message(), file: file, line: line)
}
public func expectEqualSequence< Expected: Sequence, Actual: Sequence>(
_ expected: Expected, _ actual: Actual,
_ message: @autoclosure () -> String = "",
file: String = #file, line: UInt = #line,
sameValue: (Expected.Element, Expected.Element) -> Bool
) where Expected.Element == Actual.Element {
if !expected.elementsEqual(actual, by: sameValue) {
XCTFail("expected elements: \"\(expected)\"\n"
+ "actual: \"\(actual)\" (of type \(String(reflecting: type(of: actual)))), \(message())")
}
}
public func expectEqualSequence< Expected: Sequence, Actual: Sequence>(
_ expected: Expected, _ actual: Actual,
_ message: @autoclosure () -> String = "",
file: String = #file, line: UInt = #line
) where Expected.Element == Actual.Element, Expected.Element: Equatable {
expectEqualSequence(expected, actual, message()) {
$0 == $1
}
}
func expectEqual(_ actual: Date, _ expected: Date , within: Double = 0.001, file: StaticString = #filePath, line: UInt = #line) {
let debugDescription = "\nactual: \(actual.formatted(.iso8601));\nexpected: \(expected.formatted(.iso8601))"
XCTAssertEqual(actual.timeIntervalSinceReferenceDate, expected.timeIntervalSinceReferenceDate, accuracy: within, debugDescription, file: file, line: line)
}
// Compare two date components like the original equality, but compares nanosecond within a reasonable epsilon, and optionally ignores quarter and calendar equality since they were often not supported in the original implementation
public func expectEqual(_ first: DateComponents, _ second: DateComponents, within nanosecondAccuracy: Int = 5000, expectQuarter: Bool = true, expectCalendar: Bool = true, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) {
XCTAssertEqual(first.era, second.era, message(), file: file, line: line)
XCTAssertEqual(first.year, second.year, message(), file: file, line: line)
XCTAssertEqual(first.month, second.month, message(), file: file, line: line)
XCTAssertEqual(first.day, second.day, message(), file: file, line: line)
XCTAssertEqual(first.dayOfYear, second.dayOfYear, message(), file: file, line: line)
XCTAssertEqual(first.hour, second.hour, message(), file: file, line: line)
XCTAssertEqual(first.minute, second.minute, message(), file: file, line: line)
XCTAssertEqual(first.second, second.second, message(), file: file, line: line)
XCTAssertEqual(first.weekday, second.weekday, message(), file: file, line: line)
XCTAssertEqual(first.weekdayOrdinal, second.weekdayOrdinal, message(), file: file, line: line)
XCTAssertEqual(first.weekOfMonth, second.weekOfMonth, message(), file: file, line: line)
XCTAssertEqual(first.weekOfYear, second.weekOfYear, message(), file: file, line: line)
XCTAssertEqual(first.yearForWeekOfYear, second.yearForWeekOfYear, message(), file: file, line: line)
if expectQuarter {
XCTAssertEqual(first.quarter, second.quarter, message(), file: file, line: line)
}
if let ns = first.nanosecond, let otherNS = second.nanosecond {
XCTAssertLessThanOrEqual(abs(ns - otherNS), nanosecondAccuracy, message(), file: file, line: line)
} else {
XCTAssertEqual(first.nanosecond, second.nanosecond, message(), file: file, line: line)
}
XCTAssertEqual(first.isLeapMonth, second.isLeapMonth, message(), file: file, line: line)
if expectCalendar {
XCTAssertEqual(first.calendar, second.calendar, message(), file: file, line: line)
}
XCTAssertEqual(first.timeZone, second.timeZone, message(), file: file, line: line)
}
func expectChanges<T: BinaryInteger>(_ check: @autoclosure () -> T, by difference: T? = nil, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, _ expression: () throws -> ()) rethrows {
let valueBefore = check()
try expression()
let valueAfter = check()
if let difference = difference {
XCTAssertEqual(valueAfter, valueBefore + difference, message(), file: file, line: line)
} else {
XCTAssertNotEqual(valueAfter, valueBefore, message(), file: file, line: line)
}
}
func expectNoChanges<T: BinaryInteger>(_ check: @autoclosure () -> T, by difference: T? = nil, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line, _ expression: () throws -> ()) rethrows {
let valueBefore = check()
try expression()
let valueAfter = check()
if let difference = difference {
XCTAssertNotEqual(valueAfter, valueBefore + difference, message(), file: file, line: line)
} else {
XCTAssertEqual(valueAfter, valueBefore, message(), file: file, line: line)
}
}
/// Test that the elements of `instances` satisfy the semantic
/// requirements of `Equatable`, using `oracle` to generate equality
/// expectations from pairs of positions in `instances`.
///
/// - Note: `oracle` is also checked for conformance to the
/// laws.
public func checkEquatable<Instances: Collection>(
_ instances: Instances,
oracle: (Instances.Index, Instances.Index) -> Bool,
allowBrokenTransitivity: Bool = false,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) where Instances.Element: Equatable {
let indices = Array(instances.indices)
_checkEquatableImpl(
Array(instances),
oracle: { oracle(indices[$0], indices[$1]) },
allowBrokenTransitivity: allowBrokenTransitivity,
message(),
file: file,
line: line)
}
private class Box<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
internal func _checkEquatableImpl<Instance : Equatable>(
_ instances: [Instance],
oracle: (Int, Int) -> Bool,
allowBrokenTransitivity: Bool = false,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) {
// For each index (which corresponds to an instance being tested) track the
// set of equal instances.
var transitivityScoreboard: [Box<Set<Int>>] =
instances.indices.map { _ in Box([]) }
for i in instances.indices {
let x = instances[i]
expectTrue(oracle(i, i), "bad oracle: broken reflexivity at index \(i)")
for j in instances.indices {
let y = instances[j]
let predictedXY = oracle(i, j)
expectEqual(
predictedXY, oracle(j, i),
"bad oracle: broken symmetry between indices \(i), \(j)",
file: file,
line: line)
let isEqualXY = x == y
expectEqual(
predictedXY, isEqualXY,
"""
\((predictedXY
? "expected equal, found not equal"
: "expected not equal, found equal"))
lhs (at index \(i)): \(String(reflecting: x))
rhs (at index \(j)): \(String(reflecting: y))
""",
file: file,
line: line)
// Not-equal is an inverse of equal.
expectNotEqual(
isEqualXY, x != y,
"""
lhs (at index \(i)): \(String(reflecting: x))
rhs (at index \(j)): \(String(reflecting: y))
""",
file: file,
line: line)
if !allowBrokenTransitivity {
// Check transitivity of the predicate represented by the oracle.
// If we are adding the instance `j` into an equivalence set, check that
// it is equal to every other instance in the set.
if predictedXY && i < j && transitivityScoreboard[i].value.insert(j).inserted {
if transitivityScoreboard[i].value.count == 1 {
transitivityScoreboard[i].value.insert(i)
}
for k in transitivityScoreboard[i].value {
expectTrue(
oracle(j, k),
"bad oracle: broken transitivity at indices \(i), \(j), \(k)",
file: file,
line: line)
// No need to check equality between actual values, we will check
// them with the checks above.
}
precondition(transitivityScoreboard[j].value.isEmpty)
transitivityScoreboard[j] = transitivityScoreboard[i]
}
}
}
}
}
func hash<H: Hashable>(_ value: H, salt: Int? = nil) -> Int {
var hasher = Hasher()
if let salt = salt {
hasher.combine(salt)
}
hasher.combine(value)
return hasher.finalize()
}
public func checkHashable<Instances: Collection>(
_ instances: Instances,
equalityOracle: (Instances.Index, Instances.Index) -> Bool,
allowIncompleteHashing: Bool = false,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath, line: UInt = #line
) where Instances.Element: Hashable {
checkHashable(
instances,
equalityOracle: equalityOracle,
hashEqualityOracle: equalityOracle,
allowIncompleteHashing: allowIncompleteHashing,
message(),
file: file,
line: line)
}
public func checkHashable<Instances: Collection>(
_ instances: Instances,
equalityOracle: (Instances.Index, Instances.Index) -> Bool,
hashEqualityOracle: (Instances.Index, Instances.Index) -> Bool,
allowIncompleteHashing: Bool = false,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath, line: UInt = #line
) where Instances.Element: Hashable {
checkEquatable(
instances,
oracle: equalityOracle,
message(),
file: file,
line: line)
for i in instances.indices {
let x = instances[i]
for j in instances.indices {
let y = instances[j]
let predicted = hashEqualityOracle(i, j)
XCTAssertEqual(
predicted,
hashEqualityOracle(j, i),
"bad hash oracle: broken symmetry between indices \(i), \(j)",
file: file, line: line)
if x == y {
XCTAssertTrue(
predicted,
"""
bad hash oracle: equality must imply hash equality
lhs (at index \(i)): \(x)
rhs (at index \(j)): \(y)
""",
file: file, line: line)
}
if predicted {
XCTAssertEqual(
hash(x), hash(y),
"""
hash(into:) expected to match, found to differ
lhs (at index \(i)): \(x)
rhs (at index \(j)): \(y)
""",
file: file, line: line)
XCTAssertEqual(
x.hashValue, y.hashValue,
"""
hashValue expected to match, found to differ
lhs (at index \(i)): \(x)
rhs (at index \(j)): \(y)
""",
file: file, line: line)
XCTAssertEqual(
x._rawHashValue(seed: 0), y._rawHashValue(seed: 0),
"""
_rawHashValue(seed:) expected to match, found to differ
lhs (at index \(i)): \(x)
rhs (at index \(j)): \(y)
""",
file: file, line: line)
} else if !allowIncompleteHashing {
// Try a few different seeds; at least one of them should discriminate
// between the hashes. It is extremely unlikely this check will fail
// all ten attempts, unless the type's hash encoding is not unique,
// or unless the hash equality oracle is wrong.
XCTAssertTrue(
(0..<10).contains { hash(x, salt: $0) != hash(y, salt: $0) },
"""
hash(into:) expected to differ, found to match
lhs (at index \(i)): \(x)
rhs (at index \(j)): \(y)
""",
file: file, line: line)
XCTAssertTrue(
(0..<10).contains { i in
x._rawHashValue(seed: i) != y._rawHashValue(seed: i)
},
"""
_rawHashValue(seed:) expected to differ, found to match
lhs (at index \(i)): \(x)
rhs (at index \(j)): \(y)
""",
file: file, line: line)
}
}
}
}
/// Test that the elements of `groups` consist of instances that satisfy the
/// semantic requirements of `Hashable`, with each group defining a distinct
/// equivalence class under `==`.
public func checkHashableGroups<Groups: Collection>(
_ groups: Groups,
_ message: @autoclosure () -> String = "",
allowIncompleteHashing: Bool = false,
file: StaticString = #filePath,
line: UInt = #line
) where Groups.Element: Collection, Groups.Element.Element: Hashable {
let instances = groups.flatMap { $0 }
// groupIndices[i] is the index of the element in groups that contains
// instances[i].
let groupIndices =
zip(0..., groups).flatMap { i, group in group.map { _ in i } }
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
return groupIndices[lhs] == groupIndices[rhs]
}
checkHashable(
instances,
equalityOracle: equalityOracle,
hashEqualityOracle: equalityOracle,
allowIncompleteHashing: allowIncompleteHashing,
file: file,
line: line)
}
private var shouldRunXFailTests: Bool {
// FIXME: Reenable after ProcessInfo is migrated
// return ProcessInfo.processInfo.environment["NS_FOUNDATION_ATTEMPT_XFAIL_TESTS"] == "YES"
return false
}
func shouldAttemptXFailTests(_ reason: String) -> Bool {
if shouldRunXFailTests {
return true
} else {
print("warning: Skipping test expected to fail with reason '\(reason)'\n")
return false
}
}
func shouldAttemptWindowsXFailTests(_ reason: String) -> Bool {
#if os(Windows)
return shouldAttemptXFailTests(reason)
#else
return true
#endif
}
func shouldAttemptAndroidXFailTests(_ reason: String) -> Bool {
#if os(Android)
return shouldAttemptXFailTests(reason)
#else
return true
#endif
}
func shouldAttemptOpenBSDXFailTests(_ reason: String) -> Bool {
#if os(OpenBSD)
return shouldAttemptXFailTests(reason)
#else
return true
#endif
}
func testExpectedToFail<T>(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void {
testExpectedToFailWithCheck(check: shouldAttemptXFailTests(_:), test, reason)
}
func testExpectedToFailOnWindows<T>(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void {
testExpectedToFailWithCheck(check: shouldAttemptWindowsXFailTests(_:), test, reason)
}
func testExpectedToFailOnAndroid<T>(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void {
testExpectedToFailWithCheck(check: shouldAttemptAndroidXFailTests(_:), test, reason)
}
func testExpectedToFailOnOpenBSD<T>(_ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void {
testExpectedToFailWithCheck(check: shouldAttemptOpenBSDXFailTests(_:), test, reason)
}
func testExpectedToFailWithCheck<T>(check: (String) -> Bool, _ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void {
if check(reason) {
return test
} else {
return { _ in return { } }
}
}