//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2021 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 // //===----------------------------------------------------------------------===// // Loosely adapted from https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest public func checkEquatable( equivalenceClasses: [[Instance]], file: StaticString = #file, line: UInt = #line ) { let instances = equivalenceClasses.flatMap { $0 } let oracle = equivalenceClasses.indices.flatMap { i in repeatElement(i, count: equivalenceClasses[i].count) } checkEquatable(instances, oracle: { oracle[$0] == oracle[$1] }, file: file, line: line) } public func checkEquatable( _ instances: C, oracle: (C.Index, C.Index) -> Bool, file: StaticString = #file, line: UInt = #line ) where C.Element: Equatable { let indices = Array(instances.indices) checkEquatable( Array(instances), oracle: { oracle(indices[$0], indices[$1]) }, file: file, line: line) } public func checkEquatable( expectedEqual: Bool, _ lhs: T, _ rhs: T, file: StaticString = #file, line: UInt = #line ) { checkEquatable( [lhs, rhs], oracle: { expectedEqual || $0 == $1 }, file: file, line: line) } public func checkEquatable( _ instances: [Instance], oracle: (Int, Int) -> Bool, file: StaticString = #file, line: UInt = #line ) { let entry = TestContext.current.push("checkEquatable", file: file, line: line) defer { TestContext.current.pop(entry) } // For each index (which corresponds to an instance being tested) track the // set of equal instances. var transitivityScoreboard: [Box>] = instances.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 expectedXY = oracle(i, j) expectEqual(oracle(j, i), expectedXY, "bad oracle: broken symmetry between indices \(i), \(j)") let actualXY = (x == y) expectEqual( actualXY, expectedXY, """ Elements \((expectedXY ? "expected equal, found not equal" : "expected not equal, found equal")): lhs (at index \(i)): \(String(reflecting: x)) rhs (at index \(j)): \(String(reflecting: y)) """) // Not-equal is an inverse of equal. expectNotEqual( x != y, actualXY, """ `!=` returns the same result as `==`: lhs (at index \(i)): \(String(reflecting: x)) rhs (at index \(j)): \(String(reflecting: y)) """) // 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 expectedXY && 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: transitivity violation x (at index \(i)): \(String(reflecting: x)) y (at index \(j)): \(String(reflecting: y)) z (at index \(k)): \(String(reflecting: instances[k])) """) // 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] } } } }