// RUN: %empty-directory(%t) // RUN: %target-build-swift %s -o %t/a.out -module-name main -swift-version 4 // RUN: %target-codesign %t/a.out // RUN: %target-run %t/a.out // REQUIRES: executable_test // REQUIRES: objc_interop import Foundation #if FOUNDATION_XCTEST import XCTest class TestSuper : XCTestCase { } #else import StdlibUnittest class TestSuper { } #endif struct SInt: Codable, Equatable, Hashable { var x: Int } extension SInt: Comparable { static func < (_ lhs: Self, _ rhs: Self) -> Bool { return lhs.x < rhs.x } } struct SFloat { var y: Float } extension SFloat: Equatable {} extension SFloat: Hashable {} extension SFloat: Codable {} struct SGeneric { var t: T var u: U } extension SGeneric: Equatable where T: Equatable, U: Equatable {} extension SGeneric: Hashable where T: Hashable, U: Hashable {} extension SGeneric: Codable where T: Codable, U: Codable {} enum EGeneric { case a(T), b(Int), c } extension EGeneric: Equatable where T: Equatable {} extension EGeneric: Hashable where T: Hashable {} extension EGeneric: Comparable where T: Comparable {} enum NoValues { case a, b, c } extension NoValues: CaseIterable {} // Cache some values, and make them all have the same width (within a type) for // formatting niceness. let SIOne = SInt(x: 1) let SITwo = SInt(x: 2) let SFOne = SFloat(y: 1.0) let SFTwo = SFloat(y: 2.0) let SFInf = SFloat(y: .infinity) let SFNan = SFloat(y: .nan) let SGOneOne = SGeneric(t: SIOne, u: SFOne) let SGTwoOne = SGeneric(t: SITwo, u: SFOne) let SGTwoTwo = SGeneric(t: SITwo, u: SFTwo) let SGOneInf = SGeneric(t: SIOne, u: SFInf) let SGOneNan = SGeneric(t: SIOne, u: SFNan) let EGaOne: EGeneric = .a(SIOne) let EGaTwo: EGeneric = .a(SITwo) let EGbOne: EGeneric = .b(1) let EGbTwo: EGeneric = .b(2) let EGc___: EGeneric = .c func debugDescription(_ value: T) -> String { if let debugDescribable = value as? CustomDebugStringConvertible { return debugDescribable.debugDescription } else if let describable = value as? CustomStringConvertible { return describable.description } else { return "\(value)" } } func testEquatableHashable(cases: [Int: (T, T, Bool, Bool)]) { for (testLine, (lhs, rhs, equal, hashEqual)) in cases { expectEqual(lhs == rhs, equal, "\(#file):\(testLine) LHS <\(debugDescription(lhs))> == RHS <\(debugDescription(rhs))> doesn't match <\(equal)>") let lhsHash = lhs.hashValue let rhsHash = rhs.hashValue expectEqual(lhsHash == rhsHash, hashEqual, "\(#file):\(testLine) LHS <\(debugDescription(lhs)).hashValue> (\(lhsHash)) == RHS <\(debugDescription(rhs)).hashValue> (\(rhsHash)) doesn't match <\(hashEqual)>") } } func testEquatableHashableComparable(cases: [Int: (T, T, Bool, Bool, Bool)]) { for (testLine, (lhs, rhs, equal, hashEqual, less)) in cases { expectEqual(lhs == rhs, equal, "\(#file):\(testLine) LHS <\(debugDescription(lhs))> == RHS <\(debugDescription(rhs))> doesn't match <\(equal)>") expectEqual(lhs < rhs, less, "\(#file):\(testLine) LHS <\(debugDescription(lhs))> < RHS <\(debugDescription(rhs))> doesn't match <\(less)>") let lhsHash = lhs.hashValue let rhsHash = rhs.hashValue expectEqual(lhsHash == rhsHash, hashEqual, "\(#file):\(testLine) LHS <\(debugDescription(lhs)).hashValue> (\(lhsHash)) == RHS <\(debugDescription(rhs)).hashValue> (\(rhsHash)) doesn't match <\(hashEqual)>") } } class TestEquatableHashable : TestSuper { lazy var int: [Int: (SInt, SInt, Bool, Bool)] = [ #line : (SIOne, SIOne, true, true), #line : (SIOne, SITwo, false, false), #line : (SITwo, SIOne, false, false), #line : (SITwo, SITwo, true, true), ] func test_SInt() { testEquatableHashable(cases: int) } lazy var float: [Int: (SFloat, SFloat, Bool, Bool)] = [ #line : (SFOne, SFOne, true, true), #line : (SFOne, SFTwo, false, false), #line : (SFTwo, SFOne, false, false), #line : (SFTwo, SFTwo, true, true), #line : (SFInf, SFInf, true, true), #line : (SFInf, SFOne, false, false), // A bit-based hasher is likely to hash these to the same thing. #line : (SFNan, SFNan, false, true), #line : (SFNan, SFOne, false, false), ] func test_SFloat() { testEquatableHashable(cases: float) } lazy var generic: [Int: (SGeneric, SGeneric, Bool, Bool)] = [ #line : (SGOneOne, SGOneOne, true, true), #line : (SGOneOne, SGTwoOne, false, false), #line : (SGOneOne, SGTwoTwo, false, false), #line : (SGTwoOne, SGTwoOne, true, true), #line : (SGTwoOne, SGTwoTwo, false, false), #line : (SGTwoTwo, SGTwoTwo, true, true), #line : (SGOneInf, SGOneInf, true, true), #line : (SGOneInf, SGOneOne, false, false), #line : (SGOneInf, SGTwoOne, false, false), #line : (SGOneInf, SGTwoTwo, false, false), // As above, a bit-based hasher is likely to hash these to the same thing #line : (SGOneNan, SGOneNan, false, true), #line : (SGOneNan, SGOneOne, false, false), #line : (SGOneNan, SGTwoOne, false, false), #line : (SGOneNan, SGTwoTwo, false, false), #line : (SGOneNan, SGOneInf, false, false) ] func test_SGeneric() { testEquatableHashable(cases: generic) } lazy var egeneric: [Int: (EGeneric, EGeneric, Bool, Bool, Bool)] = [ #line : (EGaOne, EGaOne, true, true, false), #line : (EGaOne, EGaTwo, false, false, true), #line : (EGaOne, EGbOne, false, false, true), #line : (EGaOne, EGbTwo, false, false, true), #line : (EGaOne, EGc___, false, false, true), #line : (EGbOne, EGaOne, false, false, false), #line : (EGbOne, EGaTwo, false, false, false), #line : (EGbOne, EGbOne, true, true, false), #line : (EGbOne, EGbTwo, false, false, true), #line : (EGbOne, EGc___, false, false, true), #line : (EGc___, EGaOne, false, false, false), #line : (EGc___, EGaTwo, false, false, false), #line : (EGc___, EGbOne, false, false, false), #line : (EGc___, EGbTwo, false, false, false), #line : (EGc___, EGc___, true, true, false), ] func test_EGeneric() { testEquatableHashableComparable(cases: egeneric) } } func expectRoundTripEquality(of value: T, lineNumber: Int) where T : Equatable { let inf = "INF", negInf = "-INF", nan = "NaN" let encoder = JSONEncoder() encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf, negativeInfinity: negInf, nan: nan) let data: Data do { data = try encoder.encode(value) } catch { fatalError("\(#file):\(lineNumber): Unable to encode \(T.self) <\(debugDescription(value))>: \(error)") } let decoder = JSONDecoder() decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: inf, negativeInfinity: negInf, nan: nan) let decoded: T do { decoded = try decoder.decode(T.self, from: data) } catch { fatalError("\(#file):\(lineNumber): Unable to decode \(T.self) <\(debugDescription(value))>: \(error)") } expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>") } class TestCodable : TestSuper { lazy var int: [Int: SInt] = [ #line : SIOne, #line : SITwo, ] func test_SInt() { for (testLine, value) in int { expectRoundTripEquality(of: value, lineNumber: testLine) } } lazy var float: [Int : SFloat] = [ #line : SFOne, #line : SFTwo, #line : SFInf, // This won't compare equal to itself // #line : SFNan ] func test_SFloat() { for (testLine, value) in float { expectRoundTripEquality(of: value, lineNumber: testLine) } } lazy var generic : [Int : SGeneric] = [ #line : SGOneOne, #line : SGTwoOne, #line : SGTwoTwo, #line : SGOneInf, // As above, this won't compare equal to itself // #line : SGOneNan, ] func test_SGeneric() { for (testLine, value) in generic { expectRoundTripEquality(of: value, lineNumber: testLine) } } } class TestCaseIterable : TestSuper { func test_allCases() { expectEqual(NoValues.allCases, [.a, .b, .c]) } } #if !FOUNDATION_XCTEST var equatableHashable = [ "TestEquatableHashable.test_SInt": TestEquatableHashable.test_SInt, "TestEquatableHashable.test_SFloat": TestEquatableHashable.test_SFloat, "TestEquatableHashable.test_SGeneric": TestEquatableHashable.test_SGeneric, "TestEquatableHashable.test_EGeneric": TestEquatableHashable.test_EGeneric, ] var EquatableHashableTests = TestSuite("TestEquatableHashable") for (name, test) in equatableHashable { EquatableHashableTests.test(name) { test(TestEquatableHashable())() } } var codable = [ "TestCodable.test_SInt": TestCodable.test_SInt, "TestCodable.test_SFloat": TestCodable.test_SFloat, "TestCodable.test_SGeneric": TestCodable.test_SGeneric, ] var CodableTests = TestSuite("TestCodable") for (name, test) in codable { CodableTests.test(name) { test(TestCodable())() } } var caseIterable = [ "TestCaseIterable.test_allCases": TestCaseIterable.test_allCases, ] var CaseIterableTests = TestSuite("TestCaseIterable") for (name, test) in caseIterable { CaseIterableTests.test(name) { test(TestCaseIterable())() } } runAllTests() #endif