blob: 3b9296066549cd869625cf687ae53557af8fadc8 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
#if DARWIN_COMPATIBILITY_TESTS
public typealias XCTestCaseEntry = (testCaseClass: XCTestCase.Type, allTests: [(String, XCTestCaseClosure)])
public typealias XCTestCaseClosure = (XCTestCase) throws -> Void
public func testCase<T: XCTestCase>(_ allTests: [(String, (T) -> () throws -> Void)]) -> XCTestCaseEntry {
let tests: [(String, XCTestCaseClosure)] = allTests.map { ($0.0, test($0.1)) }
return (T.self, tests)
}
private func test<T: XCTestCase>(_ testFunc: @escaping (T) -> () throws -> Void) -> XCTestCaseClosure {
return { testCaseType in
guard let testCase = testCaseType as? T else {
fatalError("Attempt to invoke test on class \(T.self) with incompatible instance type \(type(of: testCaseType))")
}
try testFunc(testCase)()
}
}
#endif
func checkHashing_ValueType<Item: Hashable, S: Sequence>(
initialValue item: Item,
byMutating keyPath: WritableKeyPath<Item, S.Element>,
throughValues values: S,
file: StaticString = #file,
line: UInt = #line
) {
_checkHashing(
ofType: Item.self,
withMutableCounterpart: Item.self,
initialValue: item,
mutableCopyBlock: { $0 },
byMutating: keyPath,
throughValues: values,
file: file,
line: line)
}
func checkHashing_NSCopying<Item: NSObject & NSCopying, S: Sequence>(
initialValue item: Item,
byMutating keyPath: ReferenceWritableKeyPath<Item, S.Element>,
throughValues values: S,
file: StaticString = #file,
line: UInt = #line
) {
_checkHashing(
ofType: Item.self,
withMutableCounterpart: Item.self,
initialValue: item,
mutableCopyBlock: { $0.copy() as! Item },
byMutating: keyPath,
throughValues: values,
file: file,
line: line)
}
func checkHashing_NSMutableCopying<
Source: NSObject & NSMutableCopying,
Target: NSObject & NSMutableCopying,
S: Sequence
>(
initialValue item: Source,
byMutating keyPath: ReferenceWritableKeyPath<Target, S.Element>,
throughValues values: S,
file: StaticString = #file,
line: UInt = #line
) {
_checkHashing(
ofType: Source.self,
withMutableCounterpart: Target.self,
initialValue: item,
mutableCopyBlock: { $0.mutableCopy() as! Target },
byMutating: keyPath,
throughValues: values,
file: file,
line: line)
}
// Check that mutating `object` via the specified key path affects its
// hash value.
func _checkHashing<Source: Hashable, Target: Hashable, S: Sequence>(
ofType source: Source.Type,
withMutableCounterpart target: Target.Type,
initialValue object: Source,
mutableCopyBlock copyBlock: (Source) -> Target,
byMutating keyPath: WritableKeyPath<Target, S.Element>,
throughValues values: S,
file: StaticString = #file,
line: UInt = #line
) {
let reference = copyBlock(object)
let referenceHash = reference.hashValue
XCTAssertEqual(
reference.hashValue, referenceHash,
"\(type(of: reference)).hashValue is nondeterministic",
file: file, line: line)
var found = false
for value in values {
var copy = copyBlock(object)
XCTAssertEqual(
copy, reference,
"Invalid copy operation",
file: file, line: line)
XCTAssertEqual(
copy.hashValue, referenceHash,
"Invalid copy operation",
file: file, line: line)
copy[keyPath: keyPath] = value
XCTAssertNotEqual(
reference, copy,
"\(keyPath) did not affect object equality",
file: file, line: line)
if referenceHash != copy.hashValue {
found = true
}
}
if !found {
XCTFail(
"\(keyPath) does not seem to contribute to the hash value",
file: file, line: line)
}
}
enum TestError: Error {
case unexpectedNil
}
extension Optional {
@available(*, unavailable, message: "Use XCTUnwrap() instead")
func unwrapped(_ fn: String = #function, file: StaticString = #file, line: UInt = #line) throws -> Wrapped {
return try XCTUnwrap(self, file: file, line: line)
}
}
// Shims for StdlibUnittest:
// These allow for test code to be written targeting the overlay and then ported to s-c-f, or vice versa.
// You can use the FOUNDATION_XCTEST compilation condition to distinguish between tests running in XCTest
// or in StdlibUnittest.
func expectThrows<Error: Swift.Error & Equatable>(_ expectedError: Error, _ test: () throws -> Void, _ message: @autoclosure () -> String = "", file: StaticString = #file, 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 = #file, line: UInt = #line) {
XCTAssertNoThrow(try test(), message(), file: file, line: line)
}
func expectTrue(_ actual: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertTrue(actual, message(), file: file, line: line)
}
func expectFalse(_ actual: Bool, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertFalse(actual, message(), file: file, line: line)
}
func expectEqual<T: Equatable>(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(expected, actual, message(), file: file, line: line)
}
func expectNotEqual<T: Equatable>(_ expected: T, _ actual: T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertNotEqual(expected, actual, message(), file: file, line: line)
}
func expectEqual<T: FloatingPoint>(_ expected: T, _ actual: T, within: T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(expected, actual, accuracy: within, message(), file: file, line: line)
}
func expectEqual<T: FloatingPoint>(_ expected: T?, _ actual: T, within: T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) {
XCTAssertNotNil(expected, message(), file: file, line: line)
if let expected = expected {
XCTAssertEqual(expected, actual, accuracy: within, message(), file: file, line: line)
}
}
func expectEqual(
_ expected: Any.Type,
_ actual: Any.Type,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) {
XCTAssertTrue(expected == actual, message(), file: file, line: line)
}
func expectChanges<T: BinaryInteger>(_ check: @autoclosure () -> T, by difference: T? = nil, _ message: @autoclosure () -> String = "", file: StaticString = #file, 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 = #file, 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)
}
}
extension Fixture where ValueType: NSObject & NSCoding {
func loadEach(handler: (ValueType, FixtureVariant) throws -> Void) throws {
try self.loadEach(fixtureRepository: try XCTUnwrap(testBundle().url(forResource: "Fixtures", withExtension: nil)), handler: handler)
}
func assertLoadedValuesMatch(_ matchHandler: (ValueType, ValueType) -> Bool = { $0 == $1 }) throws {
let reference = try make()
try loadEach(handler: { (value, variant) in
XCTAssertTrue(matchHandler(reference, value), "The fixture with identifier \(identifier) failed to match for on-disk variant \(variant)")
})
}
func assertValueRoundtripsInCoder(settingUpArchiverWith archiverSetup: (NSKeyedArchiver) -> Void = { _ in}, unarchiverWith unarchiverSetup: (NSKeyedUnarchiver) -> Void = { _ in}, matchingWith: (ValueType, ValueType) -> Bool = { $0 == $1 }) throws {
let original = try make()
let coder = NSKeyedArchiver(forWritingWith: NSMutableData())
archiverSetup(coder)
coder.encode(original, forKey: NSKeyedArchiveRootObjectKey)
coder.finishEncoding()
let data = coder.encodedData
let decoder = NSKeyedUnarchiver(forReadingWith: data)
decoder.decodingFailurePolicy = .setErrorAndReturn
unarchiverSetup(decoder)
let object = decoder.decodeObject(of: ValueType.self, forKey: NSKeyedArchiveRootObjectKey)
XCTAssertNil(decoder.error)
if let object = object {
XCTAssertTrue(matchingWith(object, original), "The fixture with identifier '\(identifier)' failed to match after an in-memory roundtrip.")
} else {
XCTFail("The fixture with identifier '\(identifier)' failed to decode after an in-memory roundtrip.")
}
}
func assertValueRoundtripsInCoder(secureCoding: Bool, matchingWith: (ValueType, ValueType) -> Bool = { $0 == $1 }) throws {
try assertValueRoundtripsInCoder(settingUpArchiverWith: { (archiver) in
archiver.requiresSecureCoding = secureCoding
}, unarchiverWith: { (unarchiver) in
unarchiver.requiresSecureCoding = secureCoding
}, matchingWith: matchingWith)
}
}
/// 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 = #file,
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 = #file,
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 = #file, 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 = #file, 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 = #file,
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 {
return ProcessInfo.processInfo.environment["NS_FOUNDATION_ATTEMPT_XFAIL_TESTS"] == "YES"
}
private func printStderr(_ msg: String) {
#if DARWIN_COMPATIBILITY_TESTS
FileHandle.standardError.write(Data(msg.utf8))
#else
try? FileHandle.standardError.write(contentsOf: Data(msg.utf8))
#endif
}
func shouldAttemptXFailTests(_ reason: String) -> Bool {
if shouldRunXFailTests {
return true
} else {
printStderr("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 testCaseExpectedToFail<T: XCTestCase>(_ allTests: [(String, (T) -> () throws -> Void)], _ reason: String) -> XCTestCaseEntry {
return testCase(allTests.map { ($0.0, testExpectedToFail($0.1, "This test suite is disabled: \(reason)")) })
}
func appendTestCaseExpectedToFail<T: XCTestCase>(_ reason: String, _ allTests: [(String, (T) -> () throws -> Void)], into array: inout [XCTestCaseEntry]) {
if shouldAttemptXFailTests(reason) {
array.append(testCase(allTests))
}
}
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 testExpectedToFailWithCheck<T>(check: (String) -> Bool, _ test: @escaping (T) -> () throws -> Void, _ reason: String) -> (T) -> () throws -> Void {
if check(reason) {
return test
} else {
return { _ in return { } }
}
}
extension XCTest {
func assertCrashes(within block: () throws -> Void) rethrows {
let childProcessEnvVariable = "NS_FOUNDATION_TEST_PERFORM_ASSERT_CRASHES_BLOCKS"
let childProcessEnvVariableOnValue = "YES"
let isChildProcess = ProcessInfo.processInfo.environment[childProcessEnvVariable] == childProcessEnvVariableOnValue
if isChildProcess {
try block()
} else {
var arguments = ProcessInfo.processInfo.arguments
let process = Process()
process.executableURL = URL(fileURLWithPath: arguments[0])
arguments.remove(at: 0)
arguments.removeAll(where: { $0.hasPrefix("TestFoundation.") })
arguments.append("TestFoundation." + self.name.replacingOccurrences(of: ".", with: "/"))
process.arguments = arguments
var environment = ProcessInfo.processInfo.environment
environment[childProcessEnvVariable] = childProcessEnvVariableOnValue
process.environment = environment
do {
try process.run()
process.waitUntilExit()
XCTAssertEqual(process.terminationReason, .uncaughtSignal, "Child process should have crashed: \(process)")
} catch {
XCTFail("Couldn't start child process for testing crash: \(process) - \(error)")
}
}
}
}
extension FileHandle: TextOutputStream {
public func write(_ string: String) {
write(Data(string.utf8))
}
struct EncodedOutputStream: TextOutputStream {
let fileHandle: FileHandle
let encoding: String.Encoding
init(_ fileHandle: FileHandle, encoding: String.Encoding) {
self.fileHandle = fileHandle
self.encoding = encoding
}
func write(_ string: String) {
fileHandle.write(string.data(using: encoding)!)
}
}
}