| //===--- StdlibUnittest.swift.gyb -----------------------------*- swift -*-===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| %{ |
| from gyb_stdlib_unittest_support import TRACE, stackTrace, trace |
| }% |
| |
| import SwiftPrivate |
| import SwiftPrivatePthreadExtras |
| import SwiftPrivateLibcExtras |
| |
| #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) |
| import Darwin |
| #elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku) |
| import Glibc |
| #endif |
| |
| #if _runtime(_ObjC) |
| import ObjectiveC |
| #endif |
| |
| extension String { |
| /// Returns the lines in `self`. |
| public var _lines : [String] { |
| return _split(separator: "\n") |
| } |
| |
| /// Splits `self` at occurrences of `separator`. |
| public func _split(separator: Unicode.Scalar) -> [String] { |
| let scalarSlices = unicodeScalars.split { $0 == separator } |
| return scalarSlices.map { String(String.UnicodeScalarView($0)) } |
| } |
| } |
| |
| public struct SourceLoc { |
| public let file: String |
| public let line: UInt |
| public let comment: String? |
| |
| public init(_ file: String, _ line: UInt, comment: String? = nil) { |
| self.file = file |
| self.line = line |
| self.comment = comment |
| } |
| |
| public func withCurrentLoc( |
| _ file: String = #file, line: UInt = #line |
| ) -> SourceLocStack { |
| return SourceLocStack(self).with(SourceLoc(file, line)) |
| } |
| } |
| |
| public struct SourceLocStack { |
| let locs: [SourceLoc] |
| |
| public init() { |
| locs = [] |
| } |
| |
| public init(_ loc: SourceLoc) { |
| locs = [loc] |
| } |
| |
| init(_locs: [SourceLoc]) { |
| locs = _locs |
| } |
| |
| var isEmpty: Bool { |
| return locs.isEmpty |
| } |
| |
| public func with(_ loc: SourceLoc) -> SourceLocStack { |
| var locs = self.locs |
| locs.append(loc) |
| return SourceLocStack(_locs: locs) |
| } |
| |
| public func pushIf( |
| _ showFrame: Bool, file: String, line: UInt |
| ) -> SourceLocStack { |
| return showFrame ? self.with(SourceLoc(file, line)) : self |
| } |
| |
| public func withCurrentLoc( |
| file: String = #file, line: UInt = #line |
| ) -> SourceLocStack { |
| return with(SourceLoc(file, line)) |
| } |
| |
| public func print() { |
| let top = locs.first! |
| Swift.print("check failed at \(top.file), line \(top.line)") |
| _printStackTrace(SourceLocStack(_locs: Array(locs.dropFirst()))) |
| } |
| } |
| |
| func _printStackTrace(_ stackTrace: SourceLocStack?) { |
| guard let s = stackTrace, !s.locs.isEmpty else { return } |
| print("stacktrace:") |
| for (i, loc) in s.locs.reversed().enumerated() { |
| let comment = (loc.comment != nil) ? " ; \(loc.comment!)" : "" |
| print(" #\(i): \(loc.file):\(loc.line)\(comment)") |
| } |
| } |
| |
| // FIXME: these variables should be atomic, since multiple threads can call |
| // `expect*()` functions. |
| var _anyExpectFailed = false |
| var _seenExpectCrash = false |
| |
| /// Run `body` and expect a failure to happen. |
| /// |
| /// The check passes iff `body` triggers one or more failures. |
| public func expectFailure(${TRACE}, invoking body: () -> Void) { |
| let startAnyExpectFailed = _anyExpectFailed |
| _anyExpectFailed = false |
| body() |
| let endAnyExpectFailed = _anyExpectFailed |
| _anyExpectFailed = false |
| expectTrue( |
| endAnyExpectFailed, "running `body` should produce an expected failure", |
| stackTrace: ${stackTrace} |
| ) |
| _anyExpectFailed = _anyExpectFailed || startAnyExpectFailed |
| } |
| |
| public func identity(_ element: OpaqueValue<Int>) -> OpaqueValue<Int> { |
| return element |
| } |
| |
| public func identityEq(_ element: MinimalEquatableValue) -> MinimalEquatableValue { |
| return element |
| } |
| |
| public func identityComp(_ element: MinimalComparableValue) |
| -> MinimalComparableValue { |
| return element |
| } |
| |
| public func expectEqual<T : Equatable>(_ expected: T, _ actual: T, ${TRACE}) { |
| expectEqualTest(expected, actual, ${trace}, showFrame: false) {$0 == $1} |
| } |
| |
| public func expectEqual<T : Equatable, U : Equatable>( |
| _ expected: (T, U), _ actual: (T, U), ${TRACE}) { |
| expectEqualTest(expected.0, actual.0, ${trace}, showFrame: false) {$0 == $1} |
| expectEqualTest(expected.1, actual.1, ${trace}, showFrame: false) {$0 == $1} |
| } |
| |
| public func expectEqual<T : Equatable, U : Equatable, V : Equatable>( |
| _ expected: (T, U, V), _ actual: (T, U, V), ${TRACE}) { |
| expectEqualTest(expected.0, actual.0, ${trace}, showFrame: false) {$0 == $1} |
| expectEqualTest(expected.1, actual.1, ${trace}, showFrame: false) {$0 == $1} |
| expectEqualTest(expected.2, actual.2, ${trace}, showFrame: false) {$0 == $1} |
| } |
| |
| public func expectEqual<T : Equatable, U : Equatable, V : Equatable, W : Equatable>( |
| _ expected: (T, U, V, W), _ actual: (T, U, V, W), ${TRACE}) { |
| expectEqualTest(expected.0, actual.0, ${trace}, showFrame: false) {$0 == $1} |
| expectEqualTest(expected.1, actual.1, ${trace}, showFrame: false) {$0 == $1} |
| expectEqualTest(expected.2, actual.2, ${trace}, showFrame: false) {$0 == $1} |
| expectEqualTest(expected.3, actual.3, ${trace}, showFrame: false) {$0 == $1} |
| } |
| |
| % for (Lhs, Rhs) in [ |
| % ('String', 'Substring'), |
| % ('Substring', 'String'), |
| % ('String', 'String'), # this one is to break ambiguity |
| % ]: |
| public func expectEqual(_ expected: ${Lhs}, _ actual: ${Rhs}, ${TRACE}) { |
| if !(expected == actual) { |
| expectationFailure( |
| "expected: \(String(reflecting: expected)) (of type \(String(reflecting: type(of: expected))))\n" |
| + "actual: \(String(reflecting: actual)) (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace} |
| ) |
| } |
| } |
| % end |
| |
| public func expectEqualReference(_ expected: AnyObject?, _ actual: AnyObject?, ${TRACE}) { |
| expectEqualTest(expected, actual, ${trace}, showFrame: false) {$0 === $1} |
| } |
| |
| public func expectationFailure( |
| _ reason: String, |
| trace message: String, |
| stackTrace: SourceLocStack) { |
| _anyExpectFailed = true |
| stackTrace.print() |
| print(reason, terminator: reason == "" ? "" : "\n") |
| print(message, terminator: message == "" ? "" : "\n") |
| } |
| |
| // Renamed to avoid collision with expectEqual(_, _, TRACE). |
| // See <rdar://26058520> Generic type constraints incorrectly applied to |
| // functions with the same name |
| public func expectEqualTest<T>( |
| _ expected: T, _ actual: T, ${TRACE}, sameValue equal: (T, T) -> Bool |
| ) { |
| if !equal(expected, actual) { |
| expectationFailure( |
| "expected: \(String(reflecting: expected)) (of type \(String(reflecting: type(of: expected))))\n" |
| + "actual: \(String(reflecting: actual)) (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace} |
| ) |
| } |
| } |
| |
| public func expectNotEqual<T : Equatable>(_ expected: T, _ actual: T, ${TRACE}) { |
| if expected == actual { |
| expectationFailure( |
| "unexpected value: \"\(actual)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace} |
| ) |
| } |
| } |
| |
| // Cannot write a sane set of overloads using generics because of: |
| // <rdar://problem/17015923> Array -> NSArray implicit conversion insanity |
| public func expectOptionalEqual<T : Equatable>( |
| _ expected: T, _ actual: T?, ${TRACE} |
| ) { |
| expectOptionalEqual(expected, actual, ${trace}, showFrame: false) {$0 == $1} |
| } |
| |
| public func expectOptionalEqual<T>( |
| _ expected: T, _ actual: T?, ${TRACE}, sameValue equal: (T, T) -> Bool |
| ) { |
| if (actual == nil) || !equal(expected, actual!) { |
| expectationFailure( |
| "expected: \"\(expected)\" (of type \(String(reflecting: type(of: expected))))\n" |
| + "actual: \"\(actual.debugDescription)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| } |
| |
| public func expectEqual<T : Equatable>(_ expected: T?, _ actual: T?, ${TRACE}) { |
| if expected != actual { |
| expectationFailure( |
| "expected: \"\(expected.debugDescription)\" (of type \(String(reflecting: type(of: expected))))\n" |
| + "actual: \"\(actual.debugDescription)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| } |
| |
| public func expectNotEqual<T : Equatable>( |
| _ expected: T?, _ actual: T?, ${TRACE} |
| ) { |
| if expected == actual { |
| expectationFailure( |
| "unexpected value: \"\(actual.debugDescription)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| } |
| |
| // Array<T> is not Equatable if T is. Provide additional overloads. |
| // Same for Dictionary. |
| %for (Generic, EquatableType) in [ |
| % ('<T : Equatable>', 'ContiguousArray<T>'), |
| % ('<T : Equatable>', 'ArraySlice<T>'), |
| % ('<T : Equatable>', 'Array<T>'), |
| % ('<T, U : Equatable>', 'Dictionary<T, U>')]: |
| |
| public func expectEqual${Generic}( |
| _ expected: ${EquatableType}, _ actual: ${EquatableType}, ${TRACE} |
| ) { |
| expectEqualTest(expected, actual, ${trace}, showFrame: false) { $0 == $1 } |
| } |
| |
| public func expectOptionalEqual${Generic}( |
| _ expected: ${EquatableType}, _ actual: ${EquatableType}?, ${TRACE}) { |
| if (actual == nil) || expected != actual! { |
| expectationFailure( |
| "expected: \"\(expected)\" (of type \(String(reflecting: type(of: expected))))" |
| + "actual: \"\(actual.debugDescription)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| } |
| |
| %end |
| |
| public func expectEqual( |
| _ expected: Any.Type, _ actual: Any.Type, ${TRACE} |
| ) { |
| expectEqualTest(expected, actual, ${trace}, showFrame: false) { $0 == $1 } |
| } |
| |
| public func expectLT<T : Comparable>(_ lhs: T, _ rhs: T, ${TRACE}) { |
| if !(lhs < rhs) { |
| expectationFailure("\(lhs) < \(rhs)", trace: ${trace}) |
| } |
| } |
| |
| public func expectLE<T : Comparable>(_ lhs: T, _ rhs: T, ${TRACE}) { |
| if !(lhs <= rhs) { |
| expectationFailure("\(lhs) <= \(rhs)", trace: ${trace}) |
| } |
| } |
| |
| public func expectGT<T : Comparable>(_ lhs: T, _ rhs: T, ${TRACE}) { |
| if !(lhs > rhs) { |
| expectationFailure("\(lhs) > \(rhs)", trace: ${trace}) |
| } |
| } |
| |
| public func expectGE<T : Comparable>(_ lhs: T, _ rhs: T, ${TRACE}) { |
| if !(lhs >= rhs) { |
| expectationFailure("\(lhs) >= \(rhs)", trace: ${trace}) |
| } |
| } |
| |
| % for OtherRange in ['Range', 'CountableRange', 'ClosedRange', 'CountableClosedRange']: |
| extension Range |
| % if 'Countable' in OtherRange: |
| where |
| Bound : Strideable, Bound.Stride : SignedInteger |
| % end |
| { |
| internal func _contains(_ other: ${OtherRange}<Bound>) -> Bool { |
| if other.lowerBound < lowerBound { return false } |
| % if 'Closed' in OtherRange: |
| if upperBound <= other.upperBound { return false } |
| % else: |
| if upperBound < other.upperBound { return false } |
| % end |
| return true |
| } |
| } |
| % end |
| |
| % for Range in ['Range', 'CountableRange', 'ClosedRange', 'CountableClosedRange']: |
| public func expectTrapping<Bound>( |
| _ point: Bound, in range: ${Range}<Bound>, ${TRACE} |
| ) { |
| if !range.contains(point) { |
| expectationFailure("\(point) in \(range)", trace: ${trace}) |
| _trappingExpectationFailedCallback() |
| } |
| } |
| |
| public func expectTrapping<Bound>( |
| _ subRange: ${Range}<Bound>, in range: Range<Bound>, ${TRACE} |
| ) { |
| if !range._contains(subRange) { |
| expectationFailure("\(subRange) in \(range)", trace: ${trace}) |
| _trappingExpectationFailedCallback() |
| } |
| } |
| % end |
| |
| extension ClosedRange { |
| internal func _contains(_ other: ClosedRange<Bound>) -> Bool { |
| if other.lowerBound < lowerBound { return false } |
| if upperBound < other.upperBound { return false } |
| return true |
| } |
| } |
| |
| public func expectTrapping<Bound>( |
| _ subRange: ClosedRange<Bound>, in range: ClosedRange<Bound>, ${TRACE} |
| ) { |
| if !range._contains(subRange) { |
| expectationFailure("\(subRange) in \(range)", trace: ${trace}) |
| _trappingExpectationFailedCallback() |
| } |
| } |
| |
| public func expectType<T>(_: T.Type, _ x: inout T) {} |
| public func expectEqualType<T>(_: T.Type, _: T.Type) {} |
| |
| public func expectSequenceType<X : Sequence>(_ x: X) -> X { |
| return x |
| } |
| |
| % for Mutable in ['', 'Mutable']: |
| public func expect${Mutable}CollectionType<X : ${Mutable}Collection>( |
| _ x: X.Type |
| ) {} |
| % end |
| |
| /// A slice is a `Collection` that when sliced returns an instance of |
| /// itself. |
| public func expectSliceType<X : Collection>( |
| _ sliceType: X.Type |
| ) where X.SubSequence == X {} |
| |
| /// A mutable slice is a `MutableCollection` that when sliced returns an |
| /// instance of itself. |
| public func expectMutableSliceType<X : MutableCollection>( |
| _ mutableSliceType: X.Type |
| ) where X.SubSequence == X {} |
| |
| /// Check that all associated types of a `Sequence` are what we expect them |
| /// to be. |
| public func expectSequenceAssociatedTypes<X : Sequence>( |
| sequenceType: X.Type, |
| iteratorType: X.Iterator.Type, |
| subSequenceType: X.SubSequence.Type |
| ) {} |
| |
| /// Check that all associated types of a `Collection` are what we expect them |
| /// to be. |
| public func expectCollectionAssociatedTypes<X : Collection>( |
| collectionType: X.Type, |
| iteratorType: X.Iterator.Type, |
| subSequenceType: X.SubSequence.Type, |
| indexType: X.Index.Type, |
| indexDistanceType: X.IndexDistance.Type, |
| indicesType: X.Indices.Type |
| ) {} |
| |
| /// Check that all associated types of a `BidirectionalCollection` are what we |
| /// expect them to be. |
| public func expectBidirectionalCollectionAssociatedTypes<X : BidirectionalCollection>( |
| collectionType: X.Type, |
| iteratorType: X.Iterator.Type, |
| subSequenceType: X.SubSequence.Type, |
| indexType: X.Index.Type, |
| indexDistanceType: X.IndexDistance.Type, |
| indicesType: X.Indices.Type |
| ) {} |
| |
| /// Check that all associated types of a `RandomAccessCollection` are what we |
| /// expect them to be. |
| public func expectRandomAccessCollectionAssociatedTypes<X : RandomAccessCollection>( |
| collectionType: X.Type, |
| iteratorType: X.Iterator.Type, |
| subSequenceType: X.SubSequence.Type, |
| indexType: X.Index.Type, |
| indexDistanceType: X.IndexDistance.Type, |
| indicesType: X.Indices.Type |
| ) {} |
| |
| public struct AssertionResult : CustomStringConvertible { |
| init(isPass: Bool) { |
| self._isPass = isPass |
| } |
| |
| public func withDescription(_ description: String) -> AssertionResult { |
| var result = self |
| result.description += description |
| return result |
| } |
| |
| let _isPass: Bool |
| |
| public var description: String = "" |
| } |
| |
| public func assertionSuccess() -> AssertionResult { |
| return AssertionResult(isPass: true) |
| } |
| |
| public func assertionFailure() -> AssertionResult { |
| return AssertionResult(isPass: false) |
| } |
| |
| public func expectUnreachable(${TRACE}) { |
| expectationFailure("this code should not be executed", trace: ${trace}) |
| } |
| |
| public func expectUnreachableCatch(_ error: Error, ${TRACE}) { |
| expectationFailure( |
| "error should not be thrown: \"\(error)\"", trace: ${trace}) |
| } |
| |
| public func expectTrue(_ actual: AssertionResult, ${TRACE}) { |
| if !actual._isPass { |
| expectationFailure( |
| "expected: passed assertion\n\(actual.description)", trace: ${trace}) |
| } |
| } |
| |
| public func expectFalse(_ actual: AssertionResult, ${TRACE}) { |
| if actual._isPass { |
| expectationFailure( |
| "expected: failed assertion\n\(actual.description)", trace: ${trace}) |
| } |
| } |
| |
| public func expectTrue(_ actual: Bool, ${TRACE}) { |
| if !actual { |
| expectationFailure("expected: true", trace: ${trace}) |
| } |
| } |
| |
| public func expectFalse(_ actual: Bool, ${TRACE}) { |
| if actual { |
| expectationFailure("expected: false", trace: ${trace}) |
| } |
| } |
| |
| public func expectThrows<ErrorType: Error & Equatable>( |
| _ expectedError: ErrorType? = nil, _ test: () throws -> Void, ${TRACE}) { |
| do { |
| try test() |
| } catch let error as ErrorType { |
| if let expectedError = expectedError { |
| expectEqual(expectedError, error) |
| } |
| } catch { |
| expectationFailure("unexpected error thrown: \"\(error)\"", trace: ${trace}) |
| } |
| } |
| |
| public func expectDoesNotThrow(_ test: () throws -> Void, ${TRACE}) { |
| do { |
| try test() |
| } catch { |
| expectationFailure("unexpected error thrown: \"\(error)\"", trace: ${trace}) |
| } |
| } |
| |
| public func expectNil<T>(_ value: T?, ${TRACE}) { |
| if value != nil { |
| expectationFailure( |
| "expected optional to be nil\nactual: \"\(value!)\"", trace: ${trace}) |
| } |
| } |
| |
| @discardableResult |
| public func expectNotNil<T>(_ value: T?, ${TRACE}) -> T? { |
| if value == nil { |
| expectationFailure("expected optional to be non-nil", trace: ${trace}) |
| } |
| return value |
| } |
| |
| public func expectCrashLater() { |
| print("\(_stdlibUnittestStreamPrefix);expectCrash;\(_anyExpectFailed)") |
| |
| var stderr = _Stderr() |
| print("\(_stdlibUnittestStreamPrefix);expectCrash", to: &stderr) |
| |
| _seenExpectCrash = true |
| } |
| |
| func _defaultTestSuiteFailedCallback() { |
| abort() |
| } |
| |
| var _testSuiteFailedCallback: () -> Void = _defaultTestSuiteFailedCallback |
| |
| public func _setTestSuiteFailedCallback(_ callback: @escaping () -> Void) { |
| _testSuiteFailedCallback = callback |
| } |
| |
| func _defaultTrappingExpectationFailedCallback() { |
| abort() |
| } |
| |
| var _trappingExpectationFailedCallback: () -> Void |
| = _defaultTrappingExpectationFailedCallback |
| |
| public func _setTrappingExpectationFailedCallback(callback: @escaping () -> Void) { |
| _trappingExpectationFailedCallback = callback |
| } |
| |
| extension ProcessTerminationStatus { |
| var isSwiftTrap: Bool { |
| switch self { |
| case .signal(let signal): |
| return CInt(signal) == SIGILL || CInt(signal) == SIGTRAP |
| default: |
| // This default case is needed for standard library builds where |
| // resilience is enabled. |
| // FIXME: Add the .exit case when there is a way to suppress when not. |
| // case .exit(_): return false |
| return false |
| } |
| } |
| } |
| |
| func _stdlib_getline() -> String? { |
| var result: [UInt8] = [] |
| while true { |
| let c = getchar() |
| if c == EOF { |
| if result.isEmpty { |
| return nil |
| } |
| return String._fromWellFormedCodeUnitSequence(UTF8.self, input: result) |
| } |
| if c == CInt(Unicode.Scalar("\n").value) { |
| return String._fromWellFormedCodeUnitSequence(UTF8.self, input: result) |
| } |
| result.append(UInt8(c)) |
| } |
| } |
| |
| func _printDebuggingAdvice(_ fullTestName: String) { |
| print("To debug, run:") |
| var invocation = [CommandLine.arguments[0]] |
| let interpreter = getenv("SWIFT_INTERPRETER") |
| if interpreter != nil { |
| if let interpreterCmd = String(validatingUTF8: interpreter!) { |
| invocation.insert(interpreterCmd, at: 0) |
| } |
| } |
| print("$ \(invocation.joined(separator: " ")) " + |
| "--stdlib-unittest-in-process --stdlib-unittest-filter \"\(fullTestName)\"") |
| } |
| |
| var _allTestSuites: [TestSuite] = [] |
| var _testSuiteNameToIndex: [String : Int] = [:] |
| |
| let _stdlibUnittestStreamPrefix = "__STDLIB_UNITTEST__" |
| let _crashedPrefix = "CRASHED:" |
| |
| @_silgen_name("installTrapInterceptor") |
| func _installTrapInterceptor() |
| |
| #if _runtime(_ObjC) |
| @objc protocol _StdlibUnittestNSException { |
| @objc optional var name: AnyObject { get } |
| } |
| #endif |
| |
| // Avoid serializing references to objc_setUncaughtExceptionHandler in SIL. |
| @inline(never) |
| func _childProcess() { |
| _installTrapInterceptor() |
| |
| #if _runtime(_ObjC) |
| objc_setUncaughtExceptionHandler { |
| let exception = ($0 as Optional)! as AnyObject |
| var stderr = _Stderr() |
| let maybeNSException = |
| unsafeBitCast(exception, to: _StdlibUnittestNSException.self) |
| if let name = maybeNSException.name { |
| print("*** [StdlibUnittest] Terminating due to uncaught exception " + |
| "\(name): \(exception)", |
| to: &stderr) |
| } else { |
| print("*** [StdlibUnittest] Terminating due to uncaught exception: " + |
| "\(exception)", |
| to: &stderr) |
| } |
| } |
| #endif |
| |
| while let line = _stdlib_getline() { |
| let parts = line._split(separator: ";") |
| |
| if parts[0] == _stdlibUnittestStreamPrefix { |
| precondition(parts[1] == "shutdown") |
| return |
| } |
| |
| let testSuiteName = parts[0] |
| let testName = parts[1] |
| var testParameter: Int? |
| if parts.count > 2 { |
| testParameter = Int(parts[2])! |
| } else { |
| testParameter = nil |
| } |
| |
| let testSuite = _allTestSuites[_testSuiteNameToIndex[testSuiteName]!] |
| _anyExpectFailed = false |
| testSuite._runTest(name: testName, parameter: testParameter) |
| |
| print("\(_stdlibUnittestStreamPrefix);end;\(_anyExpectFailed)") |
| |
| var stderr = _Stderr() |
| print("\(_stdlibUnittestStreamPrefix);end", to: &stderr) |
| |
| if !testSuite._testByName(testName).canReuseChildProcessAfterTest { |
| return |
| } |
| } |
| } |
| |
| struct _ParentProcess { |
| internal var _pid: pid_t? |
| internal var _childStdin: _FDOutputStream = _FDOutputStream(fd: -1) |
| internal var _childStdout: _FDInputStream = _FDInputStream(fd: -1) |
| internal var _childStderr: _FDInputStream = _FDInputStream(fd: -1) |
| |
| internal var _runTestsInProcess: Bool |
| internal var _filter: String? |
| internal var _args: [String] |
| |
| init(runTestsInProcess: Bool, args: [String], filter: String?) { |
| self._runTestsInProcess = runTestsInProcess |
| self._filter = filter |
| self._args = args |
| } |
| |
| mutating func _spawnChild() { |
| let params = ["--stdlib-unittest-run-child"] + _args |
| let (pid, childStdinFD, childStdoutFD, childStderrFD) = spawnChild(params) |
| _pid = pid |
| _childStdin = _FDOutputStream(fd: childStdinFD) |
| _childStdout = _FDInputStream(fd: childStdoutFD) |
| _childStderr = _FDInputStream(fd: childStderrFD) |
| } |
| |
| mutating func _waitForChild() -> ProcessTerminationStatus { |
| let status = posixWaitpid(_pid!) |
| _pid = nil |
| _childStdin.close() |
| _childStdout.close() |
| _childStderr.close() |
| _childStdin = _FDOutputStream(fd: -1) |
| _childStdout = _FDInputStream(fd: -1) |
| _childStderr = _FDInputStream(fd: -1) |
| return status |
| } |
| |
| internal mutating func _readFromChild( |
| onStdoutLine: (String) -> (done: Bool, Void), |
| onStderrLine: (String) -> (done: Bool, Void) |
| ) { |
| var readfds = _stdlib_fd_set() |
| var writefds = _stdlib_fd_set() |
| var errorfds = _stdlib_fd_set() |
| var done = false |
| while !((_childStdout.isEOF && _childStderr.isEOF) || done) { |
| readfds.zero() |
| errorfds.zero() |
| if !_childStdout.isEOF { |
| readfds.set(_childStdout.fd) |
| errorfds.set(_childStdout.fd) |
| } |
| if !_childStderr.isEOF { |
| readfds.set(_childStderr.fd) |
| errorfds.set(_childStderr.fd) |
| } |
| var ret: CInt |
| repeat { |
| ret = _stdlib_select(&readfds, &writefds, &errorfds, nil) |
| } while ret == -1 && errno == EINTR |
| if ret <= 0 { |
| fatalError("select() returned an error") |
| } |
| if readfds.isset(_childStdout.fd) || errorfds.isset(_childStdout.fd) { |
| _childStdout.read() |
| while let line = _childStdout.getline() { |
| (done: done, ()) = onStdoutLine(line) |
| } |
| continue |
| } |
| if readfds.isset(_childStderr.fd) || errorfds.isset(_childStderr.fd) { |
| _childStderr.read() |
| while let line = _childStderr.getline() { |
| (done: done, ()) = onStderrLine(line) |
| } |
| continue |
| } |
| } |
| } |
| |
| /// Returns the values of the corresponding variables in the child process. |
| internal mutating func _runTestInChild( |
| _ testSuite: TestSuite, |
| _ testName: String, |
| parameter: Int? |
| ) -> (anyExpectFailed: Bool, seenExpectCrash: Bool, |
| status: ProcessTerminationStatus?, |
| crashStdout: [Substring], crashStderr: [Substring]) { |
| if _pid == nil { |
| _spawnChild() |
| } |
| |
| print("\(testSuite.name);\(testName)", terminator: "", to: &_childStdin) |
| if let parameter = parameter { |
| print(";", terminator: "", to: &_childStdin) |
| print(parameter, terminator: "", to: &_childStdin) |
| } |
| print("", to: &_childStdin) |
| |
| let currentTest = testSuite._testByName(testName) |
| if let stdinText = currentTest.stdinText { |
| print(stdinText, terminator: "", to: &_childStdin) |
| } |
| if currentTest.stdinEndsWithEOF { |
| _childStdin.close() |
| } |
| |
| var stdoutSeenCrashDelimiter = false |
| var stderrSeenCrashDelimiter = false |
| var stdoutEnd = false |
| var stderrEnd = false |
| var capturedCrashStdout: [Substring] = [] |
| var capturedCrashStderr: [Substring] = [] |
| var anyExpectFailedInChild = false |
| |
| func processLine(_ line: String, isStdout: Bool) -> (done: Bool, Void) { |
| var line = line[...] |
| if let index = findSubstring(line, _stdlibUnittestStreamPrefix) { |
| let controlMessage = |
| line[index..<line.endIndex].split(separator: ";") |
| switch controlMessage[1] { |
| case "expectCrash": |
| if isStdout { |
| stdoutSeenCrashDelimiter = true |
| anyExpectFailedInChild = controlMessage[2] == "true" |
| } else { |
| stderrSeenCrashDelimiter = true |
| } |
| case "end": |
| if isStdout { |
| stdoutEnd = true |
| anyExpectFailedInChild = controlMessage[2] == "true" |
| } else { |
| stderrEnd = true |
| } |
| default: |
| fatalError("unexpected message") |
| } |
| line = line[line.startIndex..<index] |
| if line.isEmpty { |
| return (done: stdoutEnd && stderrEnd, ()) |
| } |
| } |
| if isStdout { |
| if stdoutSeenCrashDelimiter { |
| capturedCrashStdout.append(line) |
| } |
| } else { |
| if stderrSeenCrashDelimiter { |
| capturedCrashStderr.append(line) |
| if findSubstring(line, _crashedPrefix) != nil { |
| line = "OK: saw expected \"\(line.lowercased())\""[...] |
| } |
| } |
| } |
| if isStdout { |
| print("stdout>>> \(line)") |
| } else { |
| print("stderr>>> \(line)") |
| } |
| return (done: stdoutEnd && stderrEnd, ()) |
| } |
| |
| _readFromChild( |
| onStdoutLine: { processLine($0, isStdout: true) }, |
| onStderrLine: { processLine($0, isStdout: false) }) |
| |
| // Check if the child has sent us "end" markers for the current test. |
| if stdoutEnd && stderrEnd { |
| var status: ProcessTerminationStatus? |
| if !testSuite._testByName(testName).canReuseChildProcessAfterTest { |
| status = _waitForChild() |
| switch status! { |
| case .exit(0): |
| status = nil |
| default: |
| () |
| } |
| } |
| return ( |
| anyExpectFailedInChild, |
| stdoutSeenCrashDelimiter || stderrSeenCrashDelimiter, status, |
| capturedCrashStdout, capturedCrashStderr) |
| } |
| |
| // We reached EOF on stdout and stderr and we did not see "end" markers, so |
| // it looks like child crashed (of course it could have closed the file |
| // descriptors, but we assume it did not, since it prevent further |
| // communication with the parent). |
| let status = _waitForChild() |
| return ( |
| anyExpectFailedInChild, |
| stdoutSeenCrashDelimiter || stderrSeenCrashDelimiter, status, |
| capturedCrashStdout, capturedCrashStderr) |
| } |
| |
| internal mutating func _shutdownChild() -> (failed: Bool, Void) { |
| if _pid == nil { |
| // The child process is not running. Report that it didn't fail during |
| // shutdown. |
| return (failed: false, ()) |
| } |
| print("\(_stdlibUnittestStreamPrefix);shutdown", to: &_childStdin) |
| |
| var childCrashed = false |
| |
| func processLine(_ line: String, isStdout: Bool) -> (done: Bool, Void) { |
| if isStdout { |
| print("stdout>>> \(line)") |
| } else { |
| if findSubstring(line, _crashedPrefix) != nil { |
| childCrashed = true |
| } |
| print("stderr>>> \(line)") |
| } |
| return (done: false, ()) |
| } |
| |
| _readFromChild( |
| onStdoutLine: { processLine($0, isStdout: true) }, |
| onStderrLine: { processLine($0, isStdout: false) }) |
| |
| let status = _waitForChild() |
| switch status { |
| case .exit(0): |
| return (failed: childCrashed, ()) |
| default: |
| print("Abnormal child process termination: \(status).") |
| return (failed: true, ()) |
| } |
| } |
| |
| internal enum _TestStatus { |
| case skip([TestRunPredicate]) |
| case pass |
| case fail |
| case uxPass |
| case xFail |
| } |
| |
| internal mutating func runOneTest( |
| fullTestName: String, |
| testSuite: TestSuite, |
| test t: TestSuite._Test, |
| testParameter: Int? |
| ) -> _TestStatus { |
| let activeSkips = t.getActiveSkipPredicates() |
| if !activeSkips.isEmpty { |
| return .skip(activeSkips) |
| } |
| |
| let activeXFails = t.getActiveXFailPredicates() |
| let expectXFail = !activeXFails.isEmpty |
| let activeXFailsText = expectXFail ? " (XFAIL: \(activeXFails))" : "" |
| print("[ RUN ] \(fullTestName)\(activeXFailsText)") |
| |
| var expectCrash = false |
| var childTerminationStatus: ProcessTerminationStatus? |
| var crashStdout: [Substring] = [] |
| var crashStderr: [Substring] = [] |
| if _runTestsInProcess { |
| if t.stdinText != nil { |
| print("The test \(fullTestName) requires stdin input and can't be run in-process, marking as failed") |
| _anyExpectFailed = true |
| } else { |
| _anyExpectFailed = false |
| testSuite._runTest(name: t.name, parameter: testParameter) |
| } |
| } else { |
| (_anyExpectFailed, expectCrash, childTerminationStatus, crashStdout, |
| crashStderr) = |
| _runTestInChild(testSuite, t.name, parameter: testParameter) |
| } |
| |
| // Determine if the test passed, not taking XFAILs into account. |
| var testPassed = false |
| switch (childTerminationStatus, expectCrash) { |
| case (.none, false): |
| testPassed = !_anyExpectFailed |
| |
| case (.none, true): |
| testPassed = false |
| print("expecting a crash, but the test did not crash") |
| |
| case (.some(_), false): |
| testPassed = false |
| print("the test crashed unexpectedly") |
| |
| case (.some(_), true): |
| testPassed = !_anyExpectFailed |
| } |
| if testPassed && t.crashOutputMatches.count > 0 { |
| // If we still think that the test passed, check if the crash |
| // output matches our expectations. |
| let crashOutput = crashStdout + crashStderr |
| for expectedSubstring in t.crashOutputMatches { |
| var found = false |
| if crashOutput.isEmpty && expectedSubstring.isEmpty { |
| found = true |
| } |
| for s in crashOutput { |
| if findSubstring(s, expectedSubstring) != nil { |
| found = true |
| break |
| } |
| } |
| if !found { |
| print("did not find expected string after crash: \(expectedSubstring.debugDescription)") |
| testPassed = false |
| } |
| } |
| } |
| |
| // Apply XFAILs. |
| switch (testPassed, expectXFail) { |
| case (true, false): |
| return .pass |
| |
| case (true, true): |
| return .uxPass |
| |
| case (false, false): |
| return .fail |
| |
| case (false, true): |
| return .xFail |
| } |
| } |
| |
| mutating func run() { |
| if let filter = _filter { |
| print("StdlibUnittest: using filter: \(filter)") |
| } |
| for testSuite in _allTestSuites { |
| var uxpassedTests: [String] = [] |
| var failedTests: [String] = [] |
| var skippedTests: [String] = [] |
| for t in testSuite._tests { |
| for testParameter in t.parameterValues { |
| var testName = t.name |
| if let testParameter = testParameter { |
| testName += "/" |
| testName += String(testParameter) |
| } |
| let fullTestName = "\(testSuite.name).\(testName)" |
| if let filter = _filter, |
| findSubstring(fullTestName, filter) == nil { |
| |
| continue |
| } |
| |
| switch runOneTest( |
| fullTestName: fullTestName, |
| testSuite: testSuite, |
| test: t, |
| testParameter: testParameter |
| ) { |
| case .skip(let activeSkips): |
| skippedTests.append(testName) |
| print("[ SKIP ] \(fullTestName) (skip: \(activeSkips))") |
| |
| case .pass: |
| print("[ OK ] \(fullTestName)") |
| |
| case .uxPass: |
| uxpassedTests.append(testName) |
| print("[ UXPASS ] \(fullTestName)") |
| |
| case .fail: |
| failedTests.append(testName) |
| print("[ FAIL ] \(fullTestName)") |
| |
| case .xFail: |
| print("[ XFAIL ] \(fullTestName)") |
| } |
| } |
| } |
| |
| if !uxpassedTests.isEmpty || !failedTests.isEmpty { |
| print("\(testSuite.name): Some tests failed, aborting") |
| print("UXPASS: \(uxpassedTests)") |
| print("FAIL: \(failedTests)") |
| print("SKIP: \(skippedTests)") |
| if !uxpassedTests.isEmpty { |
| _printDebuggingAdvice(uxpassedTests[0]) |
| } |
| if !failedTests.isEmpty { |
| _printDebuggingAdvice(failedTests[0]) |
| } |
| _testSuiteFailedCallback() |
| } else { |
| print("\(testSuite.name): All tests passed") |
| } |
| } |
| let (failed: failedOnShutdown, ()) = _shutdownChild() |
| if failedOnShutdown { |
| print("The child process failed during shutdown, aborting.") |
| _testSuiteFailedCallback() |
| } |
| } |
| } |
| |
| // Track repeated calls to runAllTests() and/or runNoTests(). |
| // Complain if a file runs no tests without calling runNoTests(). |
| struct PersistentState { |
| static var runAllTestsWasCalled: Bool = false |
| static var runNoTestsWasCalled: Bool = false |
| static var ranSomething: Bool = false |
| static var complaintInstalled = false |
| |
| static func complainIfNothingRuns() { |
| if !complaintInstalled { |
| complaintInstalled = true |
| atexit { |
| if !PersistentState.ranSomething { |
| print("Ran no tests and runNoTests() was not called. Aborting. ") |
| print("Did you forget to call runAllTests()?") |
| _testSuiteFailedCallback() |
| } |
| } |
| } |
| } |
| } |
| |
| |
| // Call runNoTests() if you want to deliberately run no tests. |
| public func runNoTests() { |
| if PersistentState.runAllTestsWasCalled { |
| print("runNoTests() called after runAllTests(). Aborting.") |
| _testSuiteFailedCallback() |
| return |
| } |
| if PersistentState.runNoTestsWasCalled { |
| print("runNoTests() called twice. Aborting.") |
| _testSuiteFailedCallback() |
| return |
| } |
| PersistentState.runNoTestsWasCalled = true |
| PersistentState.ranSomething = true |
| } |
| |
| public func runAllTests() { |
| if PersistentState.runNoTestsWasCalled { |
| print("runAllTests() called after runNoTests(). Aborting.") |
| _testSuiteFailedCallback() |
| return |
| } |
| if PersistentState.runAllTestsWasCalled { |
| print("runAllTests() called twice. Aborting.") |
| _testSuiteFailedCallback() |
| return |
| } |
| PersistentState.runAllTestsWasCalled = true |
| PersistentState.ranSomething = true |
| |
| #if _runtime(_ObjC) |
| autoreleasepool { |
| _stdlib_initializeReturnAutoreleased() |
| } |
| #endif |
| |
| let _isChildProcess: Bool = |
| CommandLine.arguments.contains("--stdlib-unittest-run-child") |
| |
| if _isChildProcess { |
| _childProcess() |
| } else { |
| var runTestsInProcess: Bool = false |
| var filter: String? |
| var args = [String]() |
| var i = 0 |
| i += 1 // Skip the name of the executable. |
| while i < CommandLine.arguments.count { |
| let arg = CommandLine.arguments[i] |
| if arg == "--stdlib-unittest-in-process" { |
| runTestsInProcess = true |
| i += 1 |
| continue |
| } |
| if arg == "--stdlib-unittest-filter" { |
| filter = CommandLine.arguments[i + 1] |
| i += 2 |
| continue |
| } |
| if arg == "--help" { |
| let message = |
| "optional arguments:\n" + |
| "--stdlib-unittest-in-process\n" + |
| " run tests in-process without intercepting crashes.\n" + |
| " Useful for running under a debugger.\n" + |
| "--stdlib-unittest-filter FILTER-STRING\n" + |
| " only run tests whose names contain FILTER-STRING as\n" + |
| " a substring." |
| print(message) |
| return |
| } |
| |
| // Pass through unparsed arguments to the child process. |
| args.append(CommandLine.arguments[i]) |
| |
| i += 1 |
| } |
| |
| var parent = _ParentProcess( |
| runTestsInProcess: runTestsInProcess, args: args, filter: filter) |
| parent.run() |
| } |
| } |
| |
| #if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER |
| |
| @_silgen_name("_swift_leaks_startTrackingObjects") |
| func startTrackingObjects(_: UnsafePointer<CChar>) |
| @_silgen_name("_swift_leaks_stopTrackingObjects") |
| func stopTrackingObjects(_: UnsafePointer<CChar>) -> Int |
| |
| #endif |
| |
| public final class TestSuite { |
| public init(_ name: String) { |
| self.name = name |
| _precondition( |
| _testNameToIndex[name] == nil, |
| "test suite with the same name already exists") |
| _allTestSuites.append(self) |
| _testSuiteNameToIndex[name] = _allTestSuites.count - 1 |
| PersistentState.complainIfNothingRuns() |
| } |
| |
| // This method is prohibited from inlining because inlining the test harness |
| // into the test is not interesting from the runtime performance perspective. |
| // And it does not really make the test cases more effectively at testing the |
| // optimizer from a correctness prospective. On the contrary, it sometimes |
| // severely affects the compile time of the test code. |
| @inline(never) |
| public func test( |
| _ name: String, |
| file: String = #file, line: UInt = #line, |
| _ testFunction: @escaping () -> Void |
| ) { |
| _TestBuilder(testSuite: self, name: name, loc: SourceLoc(file, line)) |
| .code(testFunction) |
| } |
| |
| // This method is prohibited from inlining because inlining the test harness |
| // into the test is not interesting from the runtime performance perspective. |
| // And it does not really make the test cases more effectively at testing the |
| // optimizer from a correctness prospective. On the contrary, it sometimes |
| // severely affects the compile time of the test code. |
| @inline(never) |
| public func test( |
| _ name: String, file: String = #file, line: UInt = #line |
| ) -> _TestBuilder { |
| return _TestBuilder(testSuite: self, name: name, loc: SourceLoc(file, line)) |
| } |
| |
| public func setUp(_ code: @escaping () -> Void) { |
| _precondition(_testSetUpCode == nil, "set-up code already set") |
| _testSetUpCode = code |
| } |
| |
| public func tearDown(_ code: @escaping () -> Void) { |
| _precondition(_testTearDownCode == nil, "tear-down code already set") |
| _testTearDownCode = code |
| } |
| |
| func _runTest(name testName: String, parameter: Int?) { |
| PersistentState.ranSomething = true |
| for r in _allResettables { |
| r.reset() |
| } |
| LifetimeTracked.instances = 0 |
| if let f = _testSetUpCode { |
| f() |
| } |
| let test = _testByName(testName) |
| |
| #if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER |
| startTrackingObjects(name) |
| #endif |
| |
| switch test.code { |
| case .single(let code): |
| precondition( |
| parameter == nil, |
| "can't pass parameters to non-parameterized tests") |
| code() |
| case .parameterized(code: let code, _): |
| code(parameter!) |
| } |
| |
| #if SWIFT_RUNTIME_ENABLE_LEAK_CHECKER |
| _ = stopTrackingObjects(name) |
| #endif |
| |
| if let f = _testTearDownCode { |
| f() |
| } |
| expectEqual( |
| 0, LifetimeTracked.instances, "Found leaked LifetimeTracked instances.", |
| file: test.testLoc.file, line: test.testLoc.line) |
| } |
| |
| func _testByName(_ testName: String) -> _Test { |
| return _tests[_testNameToIndex[testName]!] |
| } |
| |
| internal enum _TestCode { |
| case single(code: () -> Void) |
| case parameterized(code: (Int) -> Void, count: Int) |
| } |
| |
| internal struct _Test { |
| let name: String |
| let testLoc: SourceLoc |
| let xfail: [TestRunPredicate] |
| let skip: [TestRunPredicate] |
| let stdinText: String? |
| let stdinEndsWithEOF: Bool |
| let crashOutputMatches: [String] |
| let code: _TestCode |
| |
| /// Whether the test harness should stop reusing the child process after |
| /// running this test. |
| var canReuseChildProcessAfterTest: Bool { |
| return stdinText == nil |
| } |
| |
| func getActiveXFailPredicates() -> [TestRunPredicate] { |
| return xfail.filter { $0.evaluate() } |
| } |
| |
| func getActiveSkipPredicates() -> [TestRunPredicate] { |
| return skip.filter { $0.evaluate() } |
| } |
| |
| var parameterValues: [Int?] { |
| switch code { |
| case .single: |
| return [nil] |
| case .parameterized(code: _, count: let count): |
| return Array(0..<count) |
| } |
| } |
| } |
| |
| public struct _TestBuilder { |
| let _testSuite: TestSuite |
| var _name: String |
| var _data: _Data = _Data() |
| |
| internal final class _Data { |
| var _xfail: [TestRunPredicate] = [] |
| var _skip: [TestRunPredicate] = [] |
| var _stdinText: String? |
| var _stdinEndsWithEOF: Bool = false |
| var _crashOutputMatches: [String] = [] |
| var _testLoc: SourceLoc? |
| } |
| |
| init(testSuite: TestSuite, name: String, loc: SourceLoc) { |
| _testSuite = testSuite |
| _name = name |
| _data._testLoc = loc |
| } |
| |
| public func xfail(_ predicate: TestRunPredicate) -> _TestBuilder { |
| _data._xfail.append(predicate) |
| return self |
| } |
| |
| public func skip(_ predicate: TestRunPredicate) -> _TestBuilder { |
| _data._skip.append(predicate) |
| return self |
| } |
| |
| public func stdin(_ stdinText: String, eof: Bool = false) -> _TestBuilder { |
| _data._stdinText = stdinText |
| _data._stdinEndsWithEOF = eof |
| return self |
| } |
| |
| public func crashOutputMatches(_ string: String) -> _TestBuilder { |
| _data._crashOutputMatches.append(string) |
| return self |
| } |
| |
| internal func _build(_ testCode: _TestCode) { |
| _testSuite._tests.append( |
| _Test( |
| name: _name, testLoc: _data._testLoc!, xfail: _data._xfail, |
| skip: _data._skip, |
| stdinText: _data._stdinText, |
| stdinEndsWithEOF: _data._stdinEndsWithEOF, |
| crashOutputMatches: _data._crashOutputMatches, |
| code: testCode)) |
| _testSuite._testNameToIndex[_name] = _testSuite._tests.count - 1 |
| } |
| |
| public func code(_ testFunction: @escaping () -> Void) { |
| _build(.single(code: testFunction)) |
| } |
| |
| public func forEach<Data>( |
| in parameterSets: [Data], |
| testFunction: @escaping (Data) -> Void |
| ) { |
| _build(.parameterized( |
| code: { (i: Int) in testFunction(parameterSets[i]) }, |
| count: parameterSets.count)) |
| } |
| } |
| |
| var name: String |
| var _tests: [_Test] = [] |
| |
| /// Code that is run before every test. |
| var _testSetUpCode: (() -> Void)? |
| |
| /// Code that is run after every test. |
| var _testTearDownCode: (() -> Void)? |
| |
| /// Maps test name to index in `_tests`. |
| var _testNameToIndex: [String : Int] = [:] |
| } |
| |
| #if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) |
| @_silgen_name("getSystemVersionPlistProperty") |
| func _getSystemVersionPlistPropertyImpl( |
| _ propertyName: UnsafePointer<CChar>) -> UnsafePointer<CChar>? |
| |
| func _getSystemVersionPlistProperty(_ propertyName: String) -> String? { |
| let cs = _getSystemVersionPlistPropertyImpl(propertyName) |
| return cs.map(String.init(cString:)) |
| } |
| #endif |
| |
| public enum OSVersion : CustomStringConvertible { |
| case osx(major: Int, minor: Int, bugFix: Int) |
| case iOS(major: Int, minor: Int, bugFix: Int) |
| case tvOS(major: Int, minor: Int, bugFix: Int) |
| case watchOS(major: Int, minor: Int, bugFix: Int) |
| case iOSSimulator |
| case tvOSSimulator |
| case watchOSSimulator |
| case linux |
| case freeBSD |
| case android |
| case ps4 |
| case windowsCygnus |
| case windows |
| case haiku |
| |
| public var description: String { |
| switch self { |
| case .osx(let major, let minor, let bugFix): |
| return "OS X \(major).\(minor).\(bugFix)" |
| case .iOS(let major, let minor, let bugFix): |
| return "iOS \(major).\(minor).\(bugFix)" |
| case .tvOS(let major, let minor, let bugFix): |
| return "TVOS \(major).\(minor).\(bugFix)" |
| case .watchOS(let major, let minor, let bugFix): |
| return "watchOS \(major).\(minor).\(bugFix)" |
| case .iOSSimulator: |
| return "iOSSimulator" |
| case .tvOSSimulator: |
| return "TVOSSimulator" |
| case .watchOSSimulator: |
| return "watchOSSimulator" |
| case .linux: |
| return "Linux" |
| case .freeBSD: |
| return "FreeBSD" |
| case .ps4: |
| return "PS4" |
| case .android: |
| return "Android" |
| case .windowsCygnus: |
| return "Cygwin" |
| case .windows: |
| return "Windows" |
| case .haiku: |
| return "Haiku" |
| } |
| } |
| } |
| |
| func _parseDottedVersion(_ s: String) -> [Int] { |
| return Array(s._split(separator: ".").lazy.map { Int($0)! }) |
| } |
| |
| public func _parseDottedVersionTriple(_ s: String) -> (Int, Int, Int) { |
| var array = _parseDottedVersion(s) |
| if array.count >= 4 { |
| fatalError("unexpected version") |
| } |
| return ( |
| array.count >= 1 ? array[0] : 0, |
| array.count >= 2 ? array[1] : 0, |
| array.count >= 3 ? array[2] : 0) |
| } |
| |
| func _getOSVersion() -> OSVersion { |
| #if os(iOS) && (arch(i386) || arch(x86_64)) |
| // On simulator, the plist file that we try to read turns out to be host's |
| // plist file, which indicates OS X. |
| // |
| // FIXME: how to get the simulator version *without* UIKit? |
| return .iOSSimulator |
| #elseif os(tvOS) && (arch(i386) || arch(x86_64)) |
| return .tvOSSimulator |
| #elseif os(watchOS) && (arch(i386) || arch(x86_64)) |
| return .watchOSSimulator |
| #elseif os(Linux) |
| return .linux |
| #elseif os(FreeBSD) |
| return .freeBSD |
| #elseif os(PS4) |
| return .ps4 |
| #elseif os(Android) |
| return .android |
| #elseif os(Cygwin) |
| return .windowsCygnus |
| #elseif os(Windows) |
| return .windows |
| #elseif os(Haiku) |
| return .haiku |
| #else |
| let productVersion = _getSystemVersionPlistProperty("ProductVersion")! |
| let (major, minor, bugFix) = _parseDottedVersionTriple(productVersion) |
| #if os(OSX) |
| return .osx(major: major, minor: minor, bugFix: bugFix) |
| #elseif os(iOS) |
| return .iOS(major: major, minor: minor, bugFix: bugFix) |
| #elseif os(tvOS) |
| return .tvOS(major: major, minor: minor, bugFix: bugFix) |
| #elseif os(watchOS) |
| return .watchOS(major: major, minor: minor, bugFix: bugFix) |
| #else |
| fatalError("could not determine OS version") |
| #endif |
| #endif |
| } |
| |
| var _runningOSVersion: OSVersion = _getOSVersion() |
| var _overrideOSVersion: OSVersion? |
| |
| /// Override the OS version for testing. |
| public func _setOverrideOSVersion(_ v: OSVersion) { |
| _overrideOSVersion = v |
| } |
| |
| func _getRunningOSVersion() -> OSVersion { |
| // Allow overriding the OS version for testing. |
| return _overrideOSVersion ?? _runningOSVersion |
| } |
| |
| public enum TestRunPredicate : CustomStringConvertible { |
| case custom(() -> Bool, reason: String) |
| |
| case always(/*reason:*/ String) |
| case never |
| |
| case osxAny(/*reason:*/ String) |
| case osxMajor(Int, reason: String) |
| case osxMinor(Int, Int, reason: String) |
| case osxMinorRange(Int, ClosedRange<Int>, reason: String) |
| case osxBugFix(Int, Int, Int, reason: String) |
| case osxBugFixRange(Int, Int, ClosedRange<Int>, reason: String) |
| |
| case iOSAny(/*reason:*/ String) |
| case iOSMajor(Int, reason: String) |
| case iOSMinor(Int, Int, reason: String) |
| case iOSMinorRange(Int, ClosedRange<Int>, reason: String) |
| case iOSBugFix(Int, Int, Int, reason: String) |
| case iOSBugFixRange(Int, Int, ClosedRange<Int>, reason: String) |
| |
| case iOSSimulatorAny(/*reason:*/ String) |
| |
| case tvOSAny(/*reason:*/ String) |
| case tvOSMajor(Int, reason: String) |
| case tvOSMinor(Int, Int, reason: String) |
| case tvOSMinorRange(Int, ClosedRange<Int>, reason: String) |
| case tvOSBugFix(Int, Int, Int, reason: String) |
| case tvOSBugFixRange(Int, Int, ClosedRange<Int>, reason: String) |
| |
| case tvOSSimulatorAny(/*reason:*/ String) |
| |
| case watchOSAny(/*reason:*/ String) |
| case watchOSMajor(Int, reason: String) |
| case watchOSMinor(Int, Int, reason: String) |
| case watchOSMinorRange(Int, ClosedRange<Int>, reason: String) |
| case watchOSBugFix(Int, Int, Int, reason: String) |
| case watchOSBugFixRange(Int, Int, ClosedRange<Int>, reason: String) |
| |
| case watchOSSimulatorAny(/*reason:*/ String) |
| |
| case linuxAny(reason: String) |
| |
| case freeBSDAny(reason: String) |
| |
| case ps4Any(reason: String) |
| |
| case androidAny(reason: String) |
| |
| case windowsAny(reason: String) |
| |
| case windowsCygnusAny(reason: String) |
| |
| case haikuAny(reason: String) |
| |
| case objCRuntime(/*reason:*/ String) |
| case nativeRuntime(/*reason:*/ String) |
| |
| public var description: String { |
| switch self { |
| case .custom(_, let reason): |
| return "Custom(reason: \(reason))" |
| |
| case .always(let reason): |
| return "Always(reason: \(reason))" |
| case .never: |
| return "" |
| |
| case .osxAny(let reason): |
| return "osx(*, reason: \(reason))" |
| case .osxMajor(let major, let reason): |
| return "osx(\(major).*, reason: \(reason))" |
| case .osxMinor(let major, let minor, let reason): |
| return "osx(\(major).\(minor), reason: \(reason))" |
| case .osxMinorRange(let major, let minorRange, let reason): |
| return "osx(\(major).[\(minorRange)], reason: \(reason))" |
| case .osxBugFix(let major, let minor, let bugFix, let reason): |
| return "osx(\(major).\(minor).\(bugFix), reason: \(reason))" |
| case .osxBugFixRange(let major, let minor, let bugFixRange, let reason): |
| return "osx(\(major).\(minor).[\(bugFixRange)], reason: \(reason))" |
| |
| case .iOSAny(let reason): |
| return "iOS(*, reason: \(reason))" |
| case .iOSMajor(let major, let reason): |
| return "iOS(\(major).*, reason: \(reason))" |
| case .iOSMinor(let major, let minor, let reason): |
| return "iOS(\(major).\(minor), reason: \(reason))" |
| case .iOSMinorRange(let major, let minorRange, let reason): |
| return "iOS(\(major).[\(minorRange)], reason: \(reason))" |
| case .iOSBugFix(let major, let minor, let bugFix, let reason): |
| return "iOS(\(major).\(minor).\(bugFix), reason: \(reason))" |
| case .iOSBugFixRange(let major, let minor, let bugFixRange, let reason): |
| return "iOS(\(major).\(minor).[\(bugFixRange)], reason: \(reason))" |
| |
| case .iOSSimulatorAny(let reason): |
| return "iOSSimulatorAny(*, reason: \(reason))" |
| |
| case .tvOSAny(let reason): |
| return "tvOS(*, reason: \(reason))" |
| case .tvOSMajor(let major, let reason): |
| return "tvOS(\(major).*, reason: \(reason))" |
| case .tvOSMinor(let major, let minor, let reason): |
| return "tvOS(\(major).\(minor), reason: \(reason))" |
| case .tvOSMinorRange(let major, let minorRange, let reason): |
| return "tvOS(\(major).[\(minorRange)], reason: \(reason))" |
| case .tvOSBugFix(let major, let minor, let bugFix, let reason): |
| return "tvOS(\(major).\(minor).\(bugFix), reason: \(reason))" |
| case .tvOSBugFixRange(let major, let minor, let bugFixRange, let reason): |
| return "tvOS(\(major).\(minor).[\(bugFixRange)], reason: \(reason))" |
| |
| case .tvOSSimulatorAny(let reason): |
| return "tvOSSimulatorAny(*, reason: \(reason))" |
| |
| case .watchOSAny(let reason): |
| return "watchOS(*, reason: \(reason))" |
| case .watchOSMajor(let major, let reason): |
| return "watchOS(\(major).*, reason: \(reason))" |
| case .watchOSMinor(let major, let minor, let reason): |
| return "watchOS(\(major).\(minor), reason: \(reason))" |
| case .watchOSMinorRange(let major, let minorRange, let reason): |
| return "watchOS(\(major).[\(minorRange)], reason: \(reason))" |
| case .watchOSBugFix(let major, let minor, let bugFix, let reason): |
| return "watchOS(\(major).\(minor).\(bugFix), reason: \(reason))" |
| case .watchOSBugFixRange(let major, let minor, let bugFixRange, let reason): |
| return "watchOS(\(major).\(minor).[\(bugFixRange)], reason: \(reason))" |
| |
| case .watchOSSimulatorAny(let reason): |
| return "watchOSSimulatorAny(*, reason: \(reason))" |
| |
| case .linuxAny(reason: let reason): |
| return "linuxAny(*, reason: \(reason))" |
| |
| case .androidAny(reason: let reason): |
| return "androidAny(*, reason: \(reason))" |
| |
| case .freeBSDAny(reason: let reason): |
| return "freeBSDAny(*, reason: \(reason))" |
| |
| case .ps4Any(reason: let reason): |
| return "ps4Any(*, reason: \(reason))" |
| |
| case .windowsAny(reason: let reason): |
| return "windowsAny(*, reason: \(reason))" |
| |
| case .windowsCygnusAny(reason: let reason): |
| return "windowsCygnusAny(*, reason: \(reason))" |
| |
| case .haikuAny(reason: let reason): |
| return "haikuAny(*, reason: \(reason))" |
| |
| case .objCRuntime(let reason): |
| return "Objective-C runtime, reason: \(reason))" |
| case .nativeRuntime(let reason): |
| return "Native runtime (no ObjC), reason: \(reason))" |
| } |
| } |
| |
| public func evaluate() -> Bool { |
| switch self { |
| case .custom(let predicate, _): |
| return predicate() |
| |
| case .always: |
| return true |
| case .never: |
| return false |
| |
| case .osxAny: |
| switch _getRunningOSVersion() { |
| case .osx: |
| return true |
| default: |
| return false |
| } |
| |
| case .osxMajor(let major, _): |
| switch _getRunningOSVersion() { |
| case .osx(major, _, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .osxMinor(let major, let minor, _): |
| switch _getRunningOSVersion() { |
| case .osx(major, minor, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .osxMinorRange(let major, let minorRange, _): |
| switch _getRunningOSVersion() { |
| case .osx(major, let runningMinor, _): |
| return minorRange.contains(runningMinor) |
| default: |
| return false |
| } |
| |
| case .osxBugFix(let major, let minor, let bugFix, _): |
| switch _getRunningOSVersion() { |
| case .osx(major, minor, bugFix): |
| return true |
| default: |
| return false |
| } |
| |
| case .osxBugFixRange(let major, let minor, let bugFixRange, _): |
| switch _getRunningOSVersion() { |
| case .osx(major, minor, let runningBugFix): |
| return bugFixRange.contains(runningBugFix) |
| default: |
| return false |
| } |
| |
| case .iOSAny: |
| switch _getRunningOSVersion() { |
| case .iOS: |
| return true |
| default: |
| return false |
| } |
| |
| case .iOSMajor(let major, _): |
| switch _getRunningOSVersion() { |
| case .iOS(major, _, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .iOSMinor(let major, let minor, _): |
| switch _getRunningOSVersion() { |
| case .iOS(major, minor, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .iOSMinorRange(let major, let minorRange, _): |
| switch _getRunningOSVersion() { |
| case .iOS(major, let runningMinor, _): |
| return minorRange.contains(runningMinor) |
| default: |
| return false |
| } |
| |
| case .iOSBugFix(let major, let minor, let bugFix, _): |
| switch _getRunningOSVersion() { |
| case .iOS(major, minor, bugFix): |
| return true |
| default: |
| return false |
| } |
| |
| case .iOSBugFixRange(let major, let minor, let bugFixRange, _): |
| switch _getRunningOSVersion() { |
| case .iOS(major, minor, let runningBugFix): |
| return bugFixRange.contains(runningBugFix) |
| default: |
| return false |
| } |
| |
| case .iOSSimulatorAny: |
| switch _getRunningOSVersion() { |
| case .iOSSimulator: |
| return true |
| default: |
| return false |
| } |
| |
| case .tvOSAny: |
| switch _getRunningOSVersion() { |
| case .tvOS: |
| return true |
| default: |
| return false |
| } |
| |
| case .tvOSMajor(let major, _): |
| switch _getRunningOSVersion() { |
| case .tvOS(major, _, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .tvOSMinor(let major, let minor, _): |
| switch _getRunningOSVersion() { |
| case .tvOS(major, minor, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .tvOSMinorRange(let major, let minorRange, _): |
| switch _getRunningOSVersion() { |
| case .tvOS(major, let runningMinor, _): |
| return minorRange.contains(runningMinor) |
| default: |
| return false |
| } |
| |
| case .tvOSBugFix(let major, let minor, let bugFix, _): |
| switch _getRunningOSVersion() { |
| case .tvOS(major, minor, bugFix): |
| return true |
| default: |
| return false |
| } |
| |
| case .tvOSBugFixRange(let major, let minor, let bugFixRange, _): |
| switch _getRunningOSVersion() { |
| case .tvOS(major, minor, let runningBugFix): |
| return bugFixRange.contains(runningBugFix) |
| default: |
| return false |
| } |
| |
| case .tvOSSimulatorAny: |
| switch _getRunningOSVersion() { |
| case .tvOSSimulator: |
| return true |
| default: |
| return false |
| } |
| |
| case .watchOSAny: |
| switch _getRunningOSVersion() { |
| case .watchOS: |
| return true |
| default: |
| return false |
| } |
| |
| case .watchOSMajor(let major, _): |
| switch _getRunningOSVersion() { |
| case .watchOS(major, _, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .watchOSMinor(let major, let minor, _): |
| switch _getRunningOSVersion() { |
| case .watchOS(major, minor, _): |
| return true |
| default: |
| return false |
| } |
| |
| case .watchOSMinorRange(let major, let minorRange, _): |
| switch _getRunningOSVersion() { |
| case .watchOS(major, let runningMinor, _): |
| return minorRange.contains(runningMinor) |
| default: |
| return false |
| } |
| |
| case .watchOSBugFix(let major, let minor, let bugFix, _): |
| switch _getRunningOSVersion() { |
| case .watchOS(major, minor, bugFix): |
| return true |
| default: |
| return false |
| } |
| |
| case .watchOSBugFixRange(let major, let minor, let bugFixRange, _): |
| switch _getRunningOSVersion() { |
| case .watchOS(major, minor, let runningBugFix): |
| return bugFixRange.contains(runningBugFix) |
| default: |
| return false |
| } |
| |
| case .watchOSSimulatorAny: |
| switch _getRunningOSVersion() { |
| case .watchOSSimulator: |
| return true |
| default: |
| return false |
| } |
| |
| case .linuxAny: |
| switch _getRunningOSVersion() { |
| case .linux: |
| return true |
| default: |
| return false |
| } |
| |
| case .androidAny: |
| switch _getRunningOSVersion() { |
| case .android: |
| return true |
| default: |
| return false |
| } |
| |
| case .freeBSDAny: |
| switch _getRunningOSVersion() { |
| case .freeBSD: |
| return true |
| default: |
| return false |
| } |
| |
| case .ps4Any: |
| switch _getRunningOSVersion() { |
| case .ps4: |
| return true |
| default: |
| return false |
| } |
| |
| case .windowsAny: |
| switch _getRunningOSVersion() { |
| case .windowsCygnus: |
| return true |
| case .windows: |
| return true |
| default: |
| return false |
| } |
| |
| case .windowsCygnusAny: |
| switch _getRunningOSVersion() { |
| case .windowsCygnus: |
| return true |
| default: |
| return false |
| } |
| |
| case .haikuAny: |
| switch _getRunningOSVersion() { |
| case .haiku: |
| return true |
| default: |
| return false |
| } |
| |
| case .objCRuntime: |
| #if _runtime(_ObjC) |
| return true |
| #else |
| return false |
| #endif |
| |
| case .nativeRuntime: |
| #if _runtime(_ObjC) |
| return false |
| #else |
| return true |
| #endif |
| } |
| } |
| } |
| |
| // |
| // Semantic tests for protocol conformance |
| // |
| |
| /// 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, |
| ${TRACE} |
| ) where |
| Instances.Iterator.Element : Equatable |
| { |
| let indices = Array(instances.indices) |
| _checkEquatableImpl( |
| Array(instances), |
| oracle: { oracle(indices[$0], indices[$1]) }, |
| allowBrokenTransitivity: allowBrokenTransitivity, |
| ${trace}) |
| } |
| |
| internal func _checkEquatableImpl<Instance : Equatable>( |
| _ instances: [Instance], |
| oracle: (Int, Int) -> Bool, |
| allowBrokenTransitivity: Bool = false, |
| ${TRACE} |
| ) { |
| // 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(Set()) } |
| |
| // TODO: swift-3-indexing-model: add tests for this function. |
| 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)", |
| stackTrace: ${stackTrace}) |
| |
| let isEqualXY = x == y |
| expectEqual( |
| predictedXY, isEqualXY, |
| (predictedXY |
| ? "expected equal, found not equal\n" |
| : "expected not equal, found equal\n") + |
| "lhs (at index \(i)): \(String(reflecting: x))\n" + |
| "rhs (at index \(j)): \(String(reflecting: y))", |
| stackTrace: ${stackTrace}) |
| |
| // Not-equal is an inverse of equal. |
| expectNotEqual( |
| isEqualXY, x != y, |
| "lhs (at index \(i)): \(String(reflecting: x))\nrhs (at index \(j)): \(String(reflecting: y))", |
| stackTrace: ${stackTrace}) |
| |
| 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)", |
| stackTrace: ${stackTrace}) |
| // 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] |
| } |
| } |
| } |
| } |
| } |
| |
| |
| public func checkEquatable<T : Equatable>( |
| _ expectedEqual: Bool, _ lhs: T, _ rhs: T, ${TRACE} |
| ) { |
| checkEquatable( |
| [lhs, rhs], |
| oracle: { expectedEqual || $0 == $1 }, ${trace}, showFrame: false) |
| } |
| |
| /// Test that the elements of `instances` satisfy the semantic |
| /// requirements of `Hashable`, using `equalityOracle` to generate |
| /// equality expectations from pairs of positions in `instances`. |
| public func checkHashable<Instances : Collection>( |
| _ instances: Instances, |
| equalityOracle: (Instances.Index, Instances.Index) -> Bool, |
| allowBrokenTransitivity: Bool = false, |
| ${TRACE} |
| ) where |
| Instances.Iterator.Element : Hashable { |
| |
| checkEquatable( |
| instances, |
| oracle: equalityOracle, |
| allowBrokenTransitivity: allowBrokenTransitivity, |
| ${trace}) |
| |
| for i in instances.indices { |
| let x = instances[i] |
| for j in instances.indices { |
| let y = instances[j] |
| if x == y { |
| expectEqual( |
| x.hashValue, y.hashValue, |
| "lhs (at index \(i)): \(x)\nrhs (at index \(j)): \(y)", |
| stackTrace: ${stackTrace}) |
| } |
| } |
| } |
| } |
| |
| public func checkHashable<T : Hashable>( |
| expectedEqual: Bool, _ lhs: T, _ rhs: T, ${TRACE} |
| ) { |
| checkHashable( |
| [lhs, rhs], equalityOracle: { expectedEqual || $0 == $1 }, ${trace}) |
| } |
| |
| public enum ExpectedComparisonResult { |
| case lt, eq, gt |
| |
| public func isLT() -> Bool { |
| return self == .lt |
| } |
| |
| public func isEQ() -> Bool { |
| return self == .eq |
| } |
| |
| public func isGT() -> Bool { |
| return self == .gt |
| } |
| |
| public func isLE() -> Bool { |
| return isLT() || isEQ() |
| } |
| |
| public func isGE() -> Bool { |
| return isGT() || isEQ() |
| } |
| |
| public func isNE() -> Bool { |
| return !isEQ() |
| } |
| |
| public func flip() -> ExpectedComparisonResult { |
| switch self { |
| case .lt: |
| return .gt |
| case .eq: |
| return .eq |
| case .gt: |
| return .lt |
| } |
| } |
| } |
| |
| extension ExpectedComparisonResult : CustomStringConvertible { |
| public var description: String { |
| switch self { |
| case .lt: |
| return "<" |
| case .eq: |
| return "==" |
| case .gt: |
| return ">" |
| } |
| } |
| } |
| |
| /// Test that the elements of `instances` satisfy the semantic |
| /// requirements of `Comparable`, using `oracle` to generate comparison |
| /// expectations from pairs of positions in `instances`. |
| /// |
| /// - Note: `oracle` is also checked for conformance to the |
| /// laws. |
| public func checkComparable<Instances : Collection>( |
| _ instances: Instances, |
| oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, |
| ${TRACE} |
| ) where |
| Instances.Iterator.Element : Comparable { |
| |
| // Also checks that equality is consistent with comparison and that |
| // the oracle obeys the equality laws |
| checkEquatable(instances, oracle: { oracle($0, $1).isEQ() }, ${trace}) |
| |
| for i in instances.indices { |
| let x = instances[i] |
| |
| expectFalse( |
| x < x, |
| "found 'x < x'\n" + |
| "at index \(i): \(String(reflecting: x))", |
| stackTrace: ${stackTrace}) |
| |
| expectFalse( |
| x > x, |
| "found 'x > x'\n" + |
| "at index \(i): \(String(reflecting: x))", |
| stackTrace: ${stackTrace}) |
| |
| expectTrue(x <= x, |
| "found 'x <= x' to be false\n" + |
| "at index \(i): \(String(reflecting: x))", |
| stackTrace: ${stackTrace}) |
| |
| expectTrue(x >= x, |
| "found 'x >= x' to be false\n" + |
| "at index \(i): \(String(reflecting: x))", |
| stackTrace: ${stackTrace}) |
| |
| for j in instances.indices where i != j { |
| let y = instances[j] |
| |
| let expected = oracle(i, j) |
| |
| expectEqual( |
| expected.flip(), oracle(j, i), |
| "bad oracle: missing antisymmetry: " |
| + "(\(String(reflecting: i)), \(String(reflecting: j)))", |
| stackTrace: ${stackTrace}) |
| |
| expectEqual(expected.isLT(), x < y, |
| "x < y\n" + |
| "lhs (at index \(i)): \(String(reflecting: x))\n" + |
| "rhs (at index \(j)): \(String(reflecting: y))", |
| stackTrace: ${stackTrace}) |
| |
| expectEqual(expected.isLE(), x <= y, |
| "x <= y\n" + |
| "lhs (at index \(i)): \(String(reflecting: x))\n" + |
| "rhs (at index \(j)): \(String(reflecting: y))", |
| stackTrace: ${stackTrace}) |
| |
| expectEqual(expected.isGE(), x >= y, |
| "x >= y\n" + |
| "lhs (at index \(i)): \(String(reflecting: x))\n" + |
| "rhs (at index \(j)): \(String(reflecting: y))", |
| stackTrace: ${stackTrace}) |
| |
| expectEqual(expected.isGT(), x > y, |
| "x > y\n" + |
| "lhs (at index \(i)): \(String(reflecting: x))\n" + |
| "rhs (at index \(j)): \(String(reflecting: y))", |
| stackTrace: ${stackTrace}) |
| |
| for k in instances.indices { |
| let expected2 = oracle(j, k) |
| if expected == expected2 { |
| expectEqual( |
| expected, oracle(i, k), |
| "bad oracle: missing transitivity " |
| + "(\(String(reflecting: i)), \(String(reflecting: j)), " |
| + "\(String(reflecting: k)))", stackTrace: ${stackTrace}) |
| } |
| } |
| } |
| } |
| } |
| |
| public func checkComparable<T : Comparable>( |
| _ expected: ExpectedComparisonResult, _ lhs: T, _ rhs: T, ${TRACE} |
| ) { |
| checkComparable( |
| [lhs, rhs], |
| oracle: { [[ .eq, expected], [ expected.flip(), .eq]][$0][$1] }, |
| ${trace}) |
| } |
| |
| |
| /// Test that the elements of `instances` satisfy the semantic |
| /// requirements of `Strideable`, using `advanceOracle` and |
| /// 'distanceOracle' to generate expectations about the results of |
| /// `advanced(by:)` and `distance(to:)` from pairs of positions in |
| /// `instances` and `strides`. |
| /// |
| /// - Note: `oracle` is also checked for conformance to the |
| /// laws. |
| public func checkStrideable<Instances : Collection, Strides : Collection>( |
| _ instances: Instances, strides: Strides, |
| distanceOracle: |
| (Instances.Index, Instances.Index) -> Strides.Iterator.Element, |
| advanceOracle: |
| (Instances.Index, Strides.Index) -> Instances.Iterator.Element, |
| ${TRACE} |
| ) where |
| Instances.Iterator.Element : Strideable, |
| Instances.Iterator.Element.Stride == Strides.Iterator.Element { |
| |
| checkComparable( |
| instances, |
| oracle: { |
| let d = distanceOracle($1, $0); |
| return d < 0 ? .lt : d == 0 ? .eq : .gt |
| }, |
| ${trace}) |
| |
| for i in instances.indices { |
| let x = instances[i] |
| expectEqual(x, x.advanced(by: 0)) |
| |
| for j in strides.indices { |
| let y = strides[j] |
| expectEqual(advanceOracle(i, j), x.advanced(by: y)) |
| } |
| |
| for j in instances.indices { |
| let y = instances[j] |
| expectEqual(distanceOracle(i, j), x.distance(to: y)) |
| } |
| } |
| } |
| |
| public func checkLosslessStringConvertible<Instance>( |
| _ instances: [Instance] |
| ) where Instance : LosslessStringConvertible & Equatable { |
| expectEqualFunctionsForDomain(instances, { $0 }, { Instance(String($0))! }) |
| } |
| |
| public func nthIndex<C: Collection>(_ x: C, _ n: Int) -> C.Index { |
| return x.index(x.startIndex, offsetBy: numericCast(n)) |
| } |
| |
| public func nth<C: Collection>(_ x: C, _ n: Int) -> C.Iterator.Element { |
| return x[nthIndex(x, n)] |
| } |
| |
| public func expectEqualSequence< |
| Expected: Sequence, |
| Actual: Sequence |
| >( |
| _ expected: Expected, _ actual: Actual, ${TRACE} |
| ) where |
| Expected.Iterator.Element == Actual.Iterator.Element, |
| Expected.Iterator.Element : Equatable { |
| |
| expectEqualSequence(expected, actual, ${trace}) { $0 == $1 } |
| } |
| |
| public func expectEqualSequence< |
| Expected : Sequence, |
| Actual : Sequence, |
| T : Equatable, |
| U : Equatable |
| >( |
| _ expected: Expected, _ actual: Actual, ${TRACE} |
| ) where |
| Expected.Iterator.Element == Actual.Iterator.Element, |
| Expected.Iterator.Element == (T, U) { |
| |
| expectEqualSequence( |
| expected, actual, ${trace}) { |
| (lhs: (T, U), rhs: (T, U)) -> Bool in |
| lhs.0 == rhs.0 && lhs.1 == rhs.1 |
| } |
| } |
| |
| public func expectEqualSequence< |
| Expected: Sequence, |
| Actual: Sequence |
| >( |
| _ expected: Expected, _ actual: Actual, ${TRACE}, |
| sameValue: (Expected.Iterator.Element, Expected.Iterator.Element) -> Bool |
| ) where |
| Expected.Iterator.Element == Actual.Iterator.Element { |
| |
| if !expected.elementsEqual(actual, by: sameValue) { |
| expectationFailure("expected elements: \"\(expected)\"\n" |
| + "actual: \"\(actual)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| } |
| |
| public func expectEqualsUnordered< |
| Expected : Sequence, |
| Actual : Sequence |
| >( |
| _ expected: Expected, _ actual: Actual, ${TRACE}, |
| compare: @escaping (Expected.Iterator.Element, Expected.Iterator.Element) |
| -> ExpectedComparisonResult |
| ) where |
| Expected.Iterator.Element == Actual.Iterator.Element { |
| |
| let x: [Expected.Iterator.Element] = |
| expected.sorted { compare($0, $1).isLT() } |
| let y: [Actual.Iterator.Element] = |
| actual.sorted { compare($0, $1).isLT() } |
| expectEqualSequence( |
| x, y, ${trace}, sameValue: { compare($0, $1).isEQ() }) |
| } |
| |
| public func expectEqualsUnordered< |
| Expected : Sequence, |
| Actual : Sequence |
| >( |
| _ expected: Expected, _ actual: Actual, ${TRACE} |
| ) where |
| Expected.Iterator.Element == Actual.Iterator.Element, |
| Expected.Iterator.Element : Comparable { |
| |
| expectEqualsUnordered(expected, actual, ${trace}) { |
| $0 < $1 ? .lt : $0 == $1 ? .eq : .gt |
| } |
| } |
| |
| public func expectEqualsUnordered<T : Comparable>( |
| _ expected: [T], _ actual: [T], ${TRACE} |
| ) { |
| let x = expected.sorted() |
| let y = actual.sorted() |
| expectEqualSequence(x, y, ${trace}) |
| } |
| |
| public func expectEqualsUnordered< |
| T : Strideable |
| >( |
| _ expected: Range<T>, _ actual: [T], ${TRACE} |
| ) where T.Stride : SignedInteger { |
| expectEqualsUnordered( |
| CountableRange(uncheckedBounds: |
| (lower: expected.lowerBound, upper: expected.upperBound)), |
| actual, |
| ${trace}, |
| showFrame: false) |
| } |
| |
| public func expectEqualsUnordered<T : Strideable>( |
| _ expected: CountableRange<T>, _ actual: [T], ${TRACE} |
| ) { |
| if expected.count != actual.count { |
| expectationFailure("expected elements: \"\(expected)\"\n" |
| + "actual: \"\(actual)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| let r = Range(uncheckedBounds: |
| (lower: expected.lowerBound, upper: expected.upperBound)) |
| for e in actual { |
| if !r.contains(e) { |
| expectationFailure("expected elements: \"\(expected)\"\n" |
| + "actual: \"\(actual)\" (of type \(String(reflecting: type(of: actual))))", |
| trace: ${trace}) |
| } |
| } |
| } |
| |
| /// A nominal type that is equivalent to a tuple of two elements. |
| /// |
| /// We need a nominal type because we can't add protocol conformances to |
| /// tuples. |
| struct Pair<T : Comparable> : Comparable { |
| init(_ first: T, _ second: T) { |
| self.first = first |
| self.second = second |
| } |
| |
| var first: T |
| var second: T |
| } |
| |
| func == <T>(lhs: Pair<T>, rhs: Pair<T>) -> Bool { |
| return lhs.first == rhs.first && lhs.second == rhs.second |
| } |
| |
| func < <T>(lhs: Pair<T>, rhs: Pair<T>) -> Bool { |
| return [lhs.first, lhs.second].lexicographicallyPrecedes( |
| [rhs.first, rhs.second]) |
| } |
| |
| public func expectEqualsUnordered< |
| Expected : Sequence, |
| Actual : Sequence, |
| T : Comparable |
| >( |
| _ expected: Expected, _ actual: Actual, ${TRACE} |
| ) where |
| Actual.Iterator.Element == (key: T, value: T), |
| Expected.Iterator.Element == (T, T) { |
| |
| func comparePairLess(_ lhs: (T, T), rhs: (T, T)) -> Bool { |
| return [lhs.0, lhs.1].lexicographicallyPrecedes([rhs.0, rhs.1]) |
| } |
| |
| let x: [(T, T)] = |
| expected.sorted(by: comparePairLess) |
| let y: [(T, T)] = |
| actual.map { ($0.0, $0.1) } |
| .sorted(by: comparePairLess) |
| |
| func comparePairEquals(_ lhs: (T, T), rhs: (key: T, value: T)) -> Bool { |
| return lhs.0 == rhs.0 && lhs.1 == rhs.1 |
| } |
| |
| expectEqualSequence(x, y, ${trace}, sameValue: comparePairEquals) |
| } |
| |
| public func expectEqualFunctionsForDomain<ArgumentType, Result : Equatable>( |
| _ arguments: [ArgumentType], _ function1: (ArgumentType) -> Result, |
| _ function2: (ArgumentType) -> Result |
| ) { |
| for a in arguments { |
| let expected = function1(a) |
| let actual = function2(a) |
| expectEqual(expected, actual, "where the argument is: \(a)") |
| } |
| } |
| |
| public func expectEqualMethodsForDomain< |
| SelfType, ArgumentType, Result : Equatable |
| >( |
| _ selfs: [SelfType], _ arguments: [ArgumentType], |
| _ function1: (SelfType) -> (ArgumentType) -> Result, |
| _ function2: (SelfType) -> (ArgumentType) -> Result |
| ) { |
| for s in selfs { |
| for a in arguments { |
| let expected = function1(s)(a) |
| let actual = function2(s)(a) |
| expectEqual( |
| expected, actual, |
| "where the first argument is: \(s)\nand the second argument is: \(a)" |
| ) |
| } |
| } |
| } |
| |
| public func expectEqualUnicodeScalars( |
| _ expected: [UInt32], _ actual: String, ${TRACE}) { |
| let actualUnicodeScalars = Array( |
| actual.unicodeScalars.lazy.map { $0.value }) |
| |
| if !expected.elementsEqual(actualUnicodeScalars) { |
| expectationFailure( |
| "expected elements: \"\(asHex(expected))\"\n" |
| + "actual: \"\(asHex(actualUnicodeScalars))\"", |
| trace: ${trace}) |
| } |
| } |
| |
| // ${'Local Variables'}: |
| // eval: (read-only-mode 1) |
| // End: |