//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2017 - 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
//
//===----------------------------------------------------------------------===//
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// REQUIRES: objc_interop
// UNSUPPORTED: back_deployment_runtime

import Foundation
import CoreGraphics

#if FOUNDATION_XCTEST
import XCTest
class TestCodableSuper : XCTestCase { }
#else
import StdlibUnittest
class TestCodableSuper { }
#endif

// MARK: - Helper Functions
@available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
func makePersonNameComponents(namePrefix: String? = nil,
                              givenName: String? = nil,
                              middleName: String? = nil,
                              familyName: String? = nil,
                              nameSuffix: String? = nil,
                              nickname: String? = nil) -> PersonNameComponents {
    var result = PersonNameComponents()
    result.namePrefix = namePrefix
    result.givenName = givenName
    result.middleName = middleName
    result.familyName = familyName
    result.nameSuffix = nameSuffix
    result.nickname = nickname
    return result
}

func debugDescription<T>(_ 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 performEncodeAndDecode<T : Codable>(of value: T, encode: (T) throws -> Data, decode: (T.Type, Data) throws -> T, lineNumber: Int) -> T {

    let data: Data
    do {
        data = try encode(value)
    } catch {
        fatalError("\(#file):\(lineNumber): Unable to encode \(T.self) <\(debugDescription(value))>: \(error)")
    }

    do {
        return try decode(T.self, data)
    } catch {
        fatalError("\(#file):\(lineNumber): Unable to decode \(T.self) <\(debugDescription(value))>: \(error)")
    }
}

func expectRoundTripEquality<T : Codable>(of value: T, encode: (T) throws -> Data, decode: (T.Type, Data) throws -> T, lineNumber: Int) where T : Equatable {

    let decoded = performEncodeAndDecode(of: value, encode: encode, decode: decode, lineNumber: lineNumber)

    expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
}

func expectRoundTripEqualityThroughJSON<T : Codable>(for value: T, expectedJSON: String? = nil, lineNumber: Int) where T : Equatable {
    let inf = "INF", negInf = "-INF", nan = "NaN"
    let encode = { (_ value: T) throws -> Data in
        let encoder = JSONEncoder()
        encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf,
                                                                      negativeInfinity: negInf,
                                                                      nan: nan)
        if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
            encoder.outputFormatting = .sortedKeys
        }
        let encoded = try encoder.encode(value)

        if let expectedJSON = expectedJSON {
            let actualJSON = String(decoding: encoded, as: UTF8.self)
            expectEqual(expectedJSON, actualJSON, line: UInt(lineNumber))
        }
        return encoded
    }

    let decode = { (_ type: T.Type, _ data: Data) throws -> T in
        let decoder = JSONDecoder()
        decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: inf,
                                                                        negativeInfinity: negInf,
                                                                        nan: nan)
        return try decoder.decode(type, from: data)
    }

    expectRoundTripEquality(of: value, encode: encode, decode: decode, lineNumber: lineNumber)
}

func expectRoundTripEqualityThroughPlist<T : Codable>(for value: T, lineNumber: Int) where T : Equatable {
    let encode = { (_ value: T) throws -> Data in
        return try PropertyListEncoder().encode(value)
    }

    let decode = { (_ type: T.Type,_ data: Data) throws -> T in
        return try PropertyListDecoder().decode(type, from: data)
    }

    expectRoundTripEquality(of: value, encode: encode, decode: decode, lineNumber: lineNumber)
}

func expectDecodingErrorViaJSON<T : Codable>(
    type: T.Type,
    json: String,
    errorKind: DecodingErrorKind,
    lineNumber: Int = #line)
{
    let data = json.data(using: .utf8)!
    do {
        let value = try JSONDecoder().decode(T.self, from: data)
        expectUnreachable(":\(lineNumber): Successfully decoded invalid \(T.self) <\(debugDescription(value))>")
    } catch let error as DecodingError {
        expectEqual(error.errorKind, errorKind, "\(#file):\(lineNumber): Incorrect error kind <\(error.errorKind)> not equal to expected <\(errorKind)>")
    } catch {
        expectUnreachableCatch(error, ":\(lineNumber): Unexpected error type when decoding \(T.self)")
    }
}

// MARK: - Helper Types
// A wrapper around a UUID that will allow it to be encoded at the top level of an encoder.
struct UUIDCodingWrapper : Codable, Equatable, Hashable, CodingKeyRepresentable {
    let value: UUID

    init(_ value: UUID) {
        self.value = value
    }

    init?<T: CodingKey>(codingKey: T) {
        guard let uuid = UUID(uuidString: codingKey.stringValue) else { return nil }
        self.value = uuid
    }

    var codingKey: CodingKey {
        GenericCodingKey(stringValue: value.uuidString)
    }

    static func ==(_ lhs: UUIDCodingWrapper, _ rhs: UUIDCodingWrapper) -> Bool {
        return lhs.value == rhs.value
    }
}

enum DecodingErrorKind {
    case dataCorrupted
    case keyNotFound
    case typeMismatch
    case valueNotFound
}

extension DecodingError {
    var errorKind: DecodingErrorKind {
        switch self {
        case .dataCorrupted: .dataCorrupted
        case .keyNotFound:   .keyNotFound
        case .typeMismatch:  .typeMismatch
        case .valueNotFound: .valueNotFound
        }
    }
}

// MARK: - Tests
class TestCodable : TestCodableSuper {
    // MARK: - AffineTransform
#if os(macOS)
    lazy var affineTransformValues: [Int : AffineTransform] = [
        #line : AffineTransform.identity,
        #line : AffineTransform(),
        #line : AffineTransform(translationByX: 2.0, byY: 2.0),
        #line : AffineTransform(scale: 2.0),
        #line : AffineTransform(rotationByDegrees: .pi / 2),

        #line : AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7),
        #line : AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33),
        #line : AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2),
        #line : AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99),
        #line : AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44)
    ]

    func test_AffineTransform_JSON() {
        for (testLine, transform) in affineTransformValues {
            expectRoundTripEqualityThroughJSON(for: transform, lineNumber: testLine)
        }
    }

    func test_AffineTransform_Plist() {
        for (testLine, transform) in affineTransformValues {
            expectRoundTripEqualityThroughPlist(for: transform, lineNumber: testLine)
        }
    }
#endif

    // MARK: - Calendar
    lazy var calendarValues: [Int : Calendar] = [
        #line : Calendar(identifier: .gregorian),
        #line : Calendar(identifier: .buddhist),
        #line : Calendar(identifier: .chinese),
        #line : Calendar(identifier: .coptic),
        #line : Calendar(identifier: .ethiopicAmeteMihret),
        #line : Calendar(identifier: .ethiopicAmeteAlem),
        #line : Calendar(identifier: .hebrew),
        #line : Calendar(identifier: .iso8601),
        #line : Calendar(identifier: .indian),
        #line : Calendar(identifier: .islamic),
        #line : Calendar(identifier: .islamicCivil),
        #line : Calendar(identifier: .japanese),
        #line : Calendar(identifier: .persian),
        #line : Calendar(identifier: .republicOfChina),
    ]

    func test_Calendar_JSON() {
        for (testLine, calendar) in calendarValues {
            expectRoundTripEqualityThroughJSON(for: calendar, lineNumber: testLine)
        }
    }

    func test_Calendar_Plist() {
        for (testLine, calendar) in calendarValues {
            expectRoundTripEqualityThroughPlist(for: calendar, lineNumber: testLine)
        }
    }

    // MARK: - CharacterSet
    lazy var characterSetValues: [Int : CharacterSet] = [
        #line : CharacterSet.controlCharacters,
        #line : CharacterSet.whitespaces,
        #line : CharacterSet.whitespacesAndNewlines,
        #line : CharacterSet.decimalDigits,
        #line : CharacterSet.letters,
        #line : CharacterSet.lowercaseLetters,
        #line : CharacterSet.uppercaseLetters,
        #line : CharacterSet.nonBaseCharacters,
        #line : CharacterSet.alphanumerics,
        #line : CharacterSet.decomposables,
        #line : CharacterSet.illegalCharacters,
        #line : CharacterSet.punctuationCharacters,
        #line : CharacterSet.capitalizedLetters,
        #line : CharacterSet.symbols,
        #line : CharacterSet.newlines
    ]

    func test_CharacterSet_JSON() {
        for (testLine, characterSet) in characterSetValues {
            expectRoundTripEqualityThroughJSON(for: characterSet, lineNumber: testLine)
        }
    }

    func test_CharacterSet_Plist() {
        for (testLine, characterSet) in characterSetValues {
            expectRoundTripEqualityThroughPlist(for: characterSet, lineNumber: testLine)
        }
    }

    // MARK: - CGAffineTransform
    lazy var cg_affineTransformValues: [Int : CGAffineTransform] = {
        var values = [
            #line : CGAffineTransform.identity,
            #line : CGAffineTransform(),
            #line : CGAffineTransform(translationX: 2.0, y: 2.0),
            #line : CGAffineTransform(scaleX: 2.0, y: 2.0),
            #line : CGAffineTransform(a: 1.0, b: 2.5, c: 66.2, d: 40.2, tx: -5.5, ty: 3.7),
            #line : CGAffineTransform(a: -55.66, b: 22.7, c: 1.5, d: 0.0, tx: -22, ty: -33),
            #line : CGAffineTransform(a: 4.5, b: 1.1, c: 0.025, d: 0.077, tx: -0.55, ty: 33.2),
            #line : CGAffineTransform(a: 7.0, b: -2.3, c: 6.7, d: 0.25, tx: 0.556, ty: 0.99),
            #line : CGAffineTransform(a: 0.498, b: -0.284, c: -0.742, d: 0.3248, tx: 12, ty: 44)
        ]

        if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
            values[#line] = CGAffineTransform(rotationAngle: .pi / 2)
        }

        return values
    }()

    func test_CGAffineTransform_JSON() {
        for (testLine, transform) in cg_affineTransformValues {
            expectRoundTripEqualityThroughJSON(for: transform, lineNumber: testLine)
        }
    }

    func test_CGAffineTransform_Plist() {
        for (testLine, transform) in cg_affineTransformValues {
            expectRoundTripEqualityThroughPlist(for: transform, lineNumber: testLine)
        }
    }

    // MARK: - CGPoint
    lazy var cg_pointValues: [Int : CGPoint] = {
        var values = [
            #line : CGPoint.zero,
            #line : CGPoint(x: 10, y: 20)
        ]

        if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
            // Limit on magnitude in JSON. See rdar://problem/12717407
            values[#line] = CGPoint(x: CGFloat.greatestFiniteMagnitude,
                                    y: CGFloat.greatestFiniteMagnitude)
        }

        return values
    }()

    func test_CGPoint_JSON() {
        for (testLine, point) in cg_pointValues {
            expectRoundTripEqualityThroughJSON(for: point, lineNumber: testLine)
        }
    }

    func test_CGPoint_Plist() {
        for (testLine, point) in cg_pointValues {
            expectRoundTripEqualityThroughPlist(for: point, lineNumber: testLine)
        }
    }

    // MARK: - CGSize
    lazy var cg_sizeValues: [Int : CGSize] = {
        var values = [
            #line : CGSize.zero,
            #line : CGSize(width: 30, height: 40)
        ]

        if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
            // Limit on magnitude in JSON. See rdar://problem/12717407
            values[#line] = CGSize(width: CGFloat.greatestFiniteMagnitude,
                                   height: CGFloat.greatestFiniteMagnitude)
        }

        return values
    }()

    func test_CGSize_JSON() {
        for (testLine, size) in cg_sizeValues {
            expectRoundTripEqualityThroughJSON(for: size, lineNumber: testLine)
        }
    }

    func test_CGSize_Plist() {
        for (testLine, size) in cg_sizeValues {
            expectRoundTripEqualityThroughPlist(for: size, lineNumber: testLine)
        }
    }

    // MARK: - CGRect
    lazy var cg_rectValues: [Int : CGRect] = {
        var values = [
            #line : CGRect.zero,
            #line : CGRect.null,
            #line : CGRect(x: 10, y: 20, width: 30, height: 40)
        ]

        if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
            // Limit on magnitude in JSON. See rdar://problem/12717407
            values[#line] = CGRect.infinite
        }

        return values
    }()

    func test_CGRect_JSON() {
        for (testLine, rect) in cg_rectValues {
            expectRoundTripEqualityThroughJSON(for: rect, lineNumber: testLine)
        }
    }

    func test_CGRect_Plist() {
        for (testLine, rect) in cg_rectValues {
            expectRoundTripEqualityThroughPlist(for: rect, lineNumber: testLine)
        }
    }

    // MARK: - CGVector
    lazy var cg_vectorValues: [Int : CGVector] = {
        var values = [
            #line : CGVector.zero,
            #line : CGVector(dx: 0.0, dy: -9.81)
        ]

        if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
            // Limit on magnitude in JSON. See rdar://problem/12717407
            values[#line] = CGVector(dx: CGFloat.greatestFiniteMagnitude,
                                     dy: CGFloat.greatestFiniteMagnitude)
        }

        return values
    }()

    func test_CGVector_JSON() {
        for (testLine, vector) in cg_vectorValues {
            expectRoundTripEqualityThroughJSON(for: vector, lineNumber: testLine)
        }
    }

    func test_CGVector_Plist() {
        for (testLine, vector) in cg_vectorValues {
            expectRoundTripEqualityThroughPlist(for: vector, lineNumber: testLine)
        }
    }

    // MARK: - ClosedRange
    func test_ClosedRange_JSON() {
        let value = 0...Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try JSONDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded ClosedRange upperBound <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
        expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded ClosedRange lowerBound <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    func test_ClosedRange_Plist() {
        let value = 0...Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded ClosedRange upperBound <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
        expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded ClosedRange lowerBound <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }
    
    func test_ClosedRange_JSON_Errors() {
        expectDecodingErrorViaJSON(
            type: ClosedRange<Int>.self,
            json: "[5,0]",
            errorKind: .dataCorrupted)
        expectDecodingErrorViaJSON(
            type: ClosedRange<Int>.self,
            json: "[5,]",
            errorKind: .valueNotFound)
        expectDecodingErrorViaJSON(
            type: ClosedRange<Int>.self,
            json: "[0,Hello]",
            errorKind: .dataCorrupted)
    }

    // MARK: - CollectionDifference
    lazy var collectionDifferenceValues: [Int : CollectionDifference<Int>] = [
        #line : [1, 2, 3].difference(from: [1, 2, 3]),
        #line : [1, 2, 3].difference(from: [1, 2]),
        #line : [1, 2, 3].difference(from: [2, 3, 4]),
        #line : [1, 2, 3].difference(from: [6, 7, 8]),
    ]
    
    func test_CollectionDifference_JSON() {
        for (testLine, difference) in collectionDifferenceValues {
            expectRoundTripEqualityThroughJSON(for: difference, lineNumber: testLine)
        }
    }
    
    func test_CollectionDifference_Plist() {
        for (testLine, difference) in collectionDifferenceValues {
            expectRoundTripEqualityThroughPlist(for: difference, lineNumber: testLine)
        }
    }

    func test_CollectionDifference_JSON_Errors() {
        // Valid serialization:
        // {
        //   "insertions" : [ { "associatedOffset" : null, "element" : 1, "isRemove" : false, "offset" : 0 } ],
        //   "removals"   : [ { "associatedOffset" : null, "element" : 4, "isRemove" : true,  "offset" : 2 } ]
        // }
        
        // Removal in insertion
        expectDecodingErrorViaJSON(
            type: CollectionDifference<Int>.self,
            json: #"""
                {
                  "insertions" : [ { "associatedOffset" : null, "element" : 1, "isRemove" : true, "offset" : 0 } ],
                  "removals"   : [ { "associatedOffset" : null, "element" : 4, "isRemove" : true,  "offset" : 2 } ]
                }
                """#,
            errorKind: .dataCorrupted)
        // Repeated offset
        expectDecodingErrorViaJSON(
            type: CollectionDifference<Int>.self,
            json: #"""
                {
                  "insertions" : [ { "associatedOffset" : null, "element" : 1, "isRemove" : true, "offset" : 2 } ],
                  "removals"   : [ { "associatedOffset" : null, "element" : 4, "isRemove" : true,  "offset" : 2 } ]
                }
                """#,
            errorKind: .dataCorrupted)
        // Invalid offset
        expectDecodingErrorViaJSON(
            type: CollectionDifference<Int>.self,
            json: #"""
                {
                  "insertions" : [ { "associatedOffset" : null, "element" : 1, "isRemove" : true, "offset" : -2 } ],
                  "removals"   : [ { "associatedOffset" : null, "element" : 4, "isRemove" : true,  "offset" : 2 } ]
                }
                """#,
            errorKind: .dataCorrupted)
        // Invalid associated offset
        expectDecodingErrorViaJSON(
            type: CollectionDifference<Int>.self,
            json: #"""
                {
                  "insertions" : [ { "associatedOffset" : 2, "element" : 1, "isRemove" : true, "offset" : 0 } ],
                  "removals"   : [ { "associatedOffset" : null, "element" : 4, "isRemove" : true,  "offset" : 2 } ]
                }
                """#,
            errorKind: .dataCorrupted)
    }

    // MARK: - ContiguousArray
    lazy var contiguousArrayValues: [Int : ContiguousArray<String>] = [
        #line : [],
        #line : ["foo"],
        #line : ["foo", "bar"],
        #line : ["foo", "bar", "baz"],
    ]

    func test_ContiguousArray_JSON() {
        for (testLine, contiguousArray) in contiguousArrayValues {
            expectRoundTripEqualityThroughJSON(for: contiguousArray, lineNumber: testLine)
        }
    }

    func test_ContiguousArray_Plist() {
        for (testLine, contiguousArray) in contiguousArrayValues {
            expectRoundTripEqualityThroughPlist(for: contiguousArray, lineNumber: testLine)
        }
    }

    // MARK: - DateComponents
    lazy var dateComponents: Set<Calendar.Component> = [
        .era, .year, .month, .day, .hour, .minute, .second, .nanosecond,
        .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear,
        .yearForWeekOfYear, .timeZone, .calendar
    ]

    func test_DateComponents_JSON() {
        let calendar = Calendar(identifier: .gregorian)
        let components = calendar.dateComponents(dateComponents, from: Date())
        expectRoundTripEqualityThroughJSON(for: components, lineNumber: #line - 1)
    }

    func test_DateComponents_Plist() {
        let calendar = Calendar(identifier: .gregorian)
        let components = calendar.dateComponents(dateComponents, from: Date())
        expectRoundTripEqualityThroughPlist(for: components, lineNumber: #line - 1)
    }

    // MARK: - DateInterval
    @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
    static let dateIntervalValues: [Int : DateInterval] = [
        #line : DateInterval(),
        #line : DateInterval(start: Date.distantPast, end: Date()),
        #line : DateInterval(start: Date(), end: Date.distantFuture),
        #line : DateInterval(start: Date.distantPast, end: Date.distantFuture)
    ]

    @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
    func test_DateInterval_JSON() {
        for (testLine, interval) in Self.dateIntervalValues {
            expectRoundTripEqualityThroughJSON(for: interval, lineNumber: testLine)
        }
    }

    @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
    func test_DateInterval_Plist() {
        for (testLine, interval) in Self.dateIntervalValues {
            expectRoundTripEqualityThroughPlist(for: interval, lineNumber: testLine)
        }
    }

    // MARK: - Decimal
    lazy var decimalValues: [Int : Decimal] = [
        #line : Decimal.leastFiniteMagnitude,
        #line : Decimal.greatestFiniteMagnitude,
        #line : Decimal.leastNormalMagnitude,
        #line : Decimal.leastNonzeroMagnitude,
        #line : Decimal(),

        // See 33996620 for re-enabling this test.
        // #line : Decimal.pi,
    ]

    func test_Decimal_JSON() {
        for (testLine, decimal) in decimalValues {
            // Decimal encodes as a number in JSON and cannot be encoded at the top level.
            expectRoundTripEqualityThroughJSON(for: TopLevelWrapper(decimal), lineNumber: testLine)
        }
    }

    func test_Decimal_Plist() {
        for (testLine, decimal) in decimalValues {
            expectRoundTripEqualityThroughPlist(for: decimal, lineNumber: testLine)
        }
    }

    @available(SwiftStdlib 5.6, *)
    func test_Dictionary_JSON() {
        enum X: String, Codable { case a, b }
        enum Y: String, Codable, CodingKeyRepresentable { case a, b }
        enum Z: Codable, CodingKeyRepresentable {
            case a
            case b
            init?<T: CodingKey>(codingKey: T) {
                switch codingKey.stringValue {
                case "α":
                    self = .a
                case "β":
                    self = .b
                default:
                    return nil
                }
            }

            var codingKey: CodingKey {
                GenericCodingKey(stringValue: encoded)
            }

            var encoded: String {
                switch self {
                case .a: return "α"
                case .b: return "β"
                }
            }
        }
        enum S: Character, Codable, CodingKeyRepresentable {
            case a = "a"
            case b = "b"

            init?<T: CodingKey>(codingKey: T) {
                guard codingKey.stringValue.count == 1 else { return nil }
                self.init(rawValue: codingKey.stringValue.first!)
            }

            var codingKey: CodingKey {
                GenericCodingKey(stringValue: "\(self.rawValue)")
            }
        }

        enum U: Int, Codable { case a = 0, b}
        enum V: Int, Codable, CodingKeyRepresentable { case a = 0, b }
        enum W: Codable, CodingKeyRepresentable {
            case a
            case b
            init?<T: CodingKey>(codingKey: T) {
                guard let intValue = codingKey.intValue else { return nil }
                switch intValue {
                case 42:
                    self = .a
                case 64:
                    self = .b
                default:
                    return nil
                }
            }
            var codingKey: CodingKey {
                GenericCodingKey(intValue: self.encoded)
            }
            var encoded: Int {
                switch self {
                case .a: return 42
                case .b: return 64
                }
            }
        }

        let uuid = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
        let uuidWrapper = UUIDCodingWrapper(uuid)

        expectRoundTripEqualityThroughJSON(for: [X.a: true],             expectedJSON: #"["a",true]"#,                                    lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], expectedJSON: #"{"a":true,"b":false}"#,                          lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], expectedJSON: #"{"α":true,"β":false}"#,                          lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [S.a: true, S.b: false], expectedJSON: #"{"a":true,"b":false}"#,                          lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [uuidWrapper: true],     expectedJSON: #"{"E621E1F8-C36C-495A-93FC-0C247A3E6E5F":true}"#, lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [uuid: true],            expectedJSON: #"["E621E1F8-C36C-495A-93FC-0C247A3E6E5F",true]"#, lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [U.a: true],             expectedJSON: #"[0,true]"#,                                      lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], expectedJSON: #"{"0":true,"1":false}"#,                          lineNumber: #line)
        expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], expectedJSON: #"{"42":true,"64":false}"#,                        lineNumber: #line)
    }


    // MARK: - IndexPath
    lazy var indexPathValues: [Int : IndexPath] = [
        #line : IndexPath(), // empty
        #line : IndexPath(index: 0), // single
        #line : IndexPath(indexes: [1, 2]), // pair
        #line : IndexPath(indexes: [3, 4, 5, 6, 7, 8]), // array
    ]

    func test_IndexPath_JSON() {
        for (testLine, indexPath) in indexPathValues {
            expectRoundTripEqualityThroughJSON(for: indexPath, lineNumber: testLine)
        }
    }

    func test_IndexPath_Plist() {
        for (testLine, indexPath) in indexPathValues {
            expectRoundTripEqualityThroughPlist(for: indexPath, lineNumber: testLine)
        }
    }

    // MARK: - IndexSet
    lazy var indexSetValues: [Int : IndexSet] = [
        #line : IndexSet(),
        #line : IndexSet(integer: 42),
    ]
    lazy var indexSetMaxValues: [Int : IndexSet] = [
        #line : IndexSet(integersIn: 0 ..< Int.max)
    ]

    func test_IndexSet_JSON() {
        for (testLine, indexSet) in indexSetValues {
            expectRoundTripEqualityThroughJSON(for: indexSet, lineNumber: testLine)
        }
        if #available(macOS 10.10, iOS 8, *) {
            // Mac OS X 10.9 and iOS 7 weren't able to round-trip Int.max in JSON.
            for (testLine, indexSet) in indexSetMaxValues {
                expectRoundTripEqualityThroughJSON(for: indexSet, lineNumber: testLine)
            }
        }
    }

    func test_IndexSet_Plist() {
        for (testLine, indexSet) in indexSetValues {
            expectRoundTripEqualityThroughPlist(for: indexSet, lineNumber: testLine)
        }
        for (testLine, indexSet) in indexSetMaxValues {
            expectRoundTripEqualityThroughPlist(for: indexSet, lineNumber: testLine)
        }
    }

    // MARK: - Locale
    lazy var localeValues: [Int : Locale] = [
        #line : Locale(identifier: ""),
        #line : Locale(identifier: "en"),
        #line : Locale(identifier: "en_US"),
        #line : Locale(identifier: "en_US_POSIX"),
        #line : Locale(identifier: "uk"),
        #line : Locale(identifier: "fr_FR"),
        #line : Locale(identifier: "fr_BE"),
        #line : Locale(identifier: "zh-Hant-HK")
    ]

    func test_Locale_JSON() {
        for (testLine, locale) in localeValues {
            expectRoundTripEqualityThroughJSON(for: locale, lineNumber: testLine)
        }
    }

    func test_Locale_Plist() {
        for (testLine, locale) in localeValues {
            expectRoundTripEqualityThroughPlist(for: locale, lineNumber: testLine)
        }
    }

    // MARK: - Measurement
    @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
    static let unitValues: [Int : Dimension] = [
        #line : UnitAcceleration.metersPerSecondSquared,
        #line : UnitMass.kilograms,
        #line : UnitLength.miles
    ]

    @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
    func test_Measurement_JSON() {
        for (testLine, unit) in Self.unitValues {
            // FIXME: <rdar://problem/49026133>
            // Terminating due to uncaught exception NSInvalidArgumentException:
            // *** You must override baseUnit in your class NSDimension to define its base unit.
            expectCrashLater()
            expectRoundTripEqualityThroughJSON(for: Measurement(value: 42, unit: unit), lineNumber: testLine)
        }
    }

    @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
    func test_Measurement_Plist() {
        for (testLine, unit) in Self.unitValues {
            // FIXME: <rdar://problem/49026133>
            // Terminating due to uncaught exception NSInvalidArgumentException:
            // *** You must override baseUnit in your class NSDimension to define its base unit.
            expectCrashLater()
            expectRoundTripEqualityThroughPlist(for: Measurement(value: 42, unit: unit), lineNumber: testLine)
        }
    }

    // MARK: - Never
    @available(SwiftStdlib 5.9, *)
    func test_Never() {
        struct Nope: Codable {
            var no: Never
        }
      
        do {
            let neverJSON = Data(#"{"no":"never"}"#.utf8)
            _ = try JSONDecoder().decode(Nope.self, from: neverJSON)
            fatalError("Incorrectly decoded `Never` instance.")
        } catch {}
    }

    // MARK: - NSRange
    lazy var nsrangeValues: [Int : NSRange] = [
        #line : NSRange(),
        #line : NSRange(location: 5, length: 20),
    ]
    lazy var nsrangeMaxValues: [Int : NSRange] = [
        #line : NSRange(location: 0, length: Int.max),
        #line : NSRange(location: NSNotFound, length: 0),
    ]

    func test_NSRange_JSON() {
        for (testLine, range) in nsrangeValues {
            expectRoundTripEqualityThroughJSON(for: range, lineNumber: testLine)
        }
        if #available(macOS 10.10, iOS 8, *) {
            // Mac OS X 10.9 and iOS 7 weren't able to round-trip Int.max in JSON.
            for (testLine, range) in nsrangeMaxValues {
                expectRoundTripEqualityThroughJSON(for: range, lineNumber: testLine)
            }
        }
    }

    func test_NSRange_Plist() {
        for (testLine, range) in nsrangeValues {
            expectRoundTripEqualityThroughPlist(for: range, lineNumber: testLine)
        }
        for (testLine, range) in nsrangeMaxValues {
            expectRoundTripEqualityThroughPlist(for: range, lineNumber: testLine)
        }
    }

    // MARK: - PartialRangeFrom
    func test_PartialRangeFrom_JSON() {
        let value = 0...
        let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try JSONDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded PartialRangeFrom <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    func test_PartialRangeFrom_Plist() {
        let value = 0...
        let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded PartialRangeFrom <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    // MARK: - PartialRangeThrough
    func test_PartialRangeThrough_JSON() {
        let value = ...Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try JSONDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded PartialRangeThrough <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    func test_PartialRangeThrough_Plist() {
        let value = ...Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded PartialRangeThrough <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    // MARK: - PartialRangeUpTo
    func test_PartialRangeUpTo_JSON() {
        let value = ..<Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try JSONDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded PartialRangeUpTo <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    func test_PartialRangeUpTo_Plist() {
        let value = ..<Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded PartialRangeUpTo <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    // MARK: - PersonNameComponents
    @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
    static let personNameComponentsValues: [Int : PersonNameComponents] = [
        #line : makePersonNameComponents(givenName: "John", familyName: "Appleseed"),
        #line : makePersonNameComponents(givenName: "John", familyName: "Appleseed", nickname: "Johnny"),
        #line : makePersonNameComponents(namePrefix: "Dr.", givenName: "Jane", middleName: "A.", familyName: "Appleseed", nameSuffix: "Esq.", nickname: "Janie")
    ]

    @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
    func test_PersonNameComponents_JSON() {
        for (testLine, components) in Self.personNameComponentsValues {
            expectRoundTripEqualityThroughJSON(for: components, lineNumber: testLine)
        }
    }

    @available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *)
    func test_PersonNameComponents_Plist() {
        for (testLine, components) in Self.personNameComponentsValues {
            expectRoundTripEqualityThroughPlist(for: components, lineNumber: testLine)
        }
    }

    // MARK: - Range
    func test_Range_JSON() {
        let value = 0..<Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try JSONEncoder().encode($0) }, decode: { try JSONDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded Range upperBound <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
        expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded Range lowerBound<\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }

    func test_Range_Plist() {
        let value = 0..<Int.max
        let decoded = performEncodeAndDecode(of: value, encode: { try PropertyListEncoder().encode($0) }, decode: { try PropertyListDecoder().decode($0, from: $1)  }, lineNumber: #line)
        expectEqual(value.upperBound, decoded.upperBound, "\(#file):\(#line): Decoded Range upperBound<\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
        expectEqual(value.lowerBound, decoded.lowerBound, "\(#file):\(#line): Decoded Range lowerBound<\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
    }
    
    func test_Range_JSON_Errors() {
        expectDecodingErrorViaJSON(
            type: Range<Int>.self,
            json: "[5,0]",
            errorKind: .dataCorrupted)
        expectDecodingErrorViaJSON(
            type: Range<Int>.self,
            json: "[5,]",
            errorKind: .valueNotFound)
        expectDecodingErrorViaJSON(
            type: Range<Int>.self,
            json: "[0,Hello]",
            errorKind: .dataCorrupted)
    }

    // MARK: - TimeZone
    lazy var timeZoneValues: [Int : TimeZone] = [
        #line : TimeZone(identifier: "America/Los_Angeles")!,
        #line : TimeZone(identifier: "UTC")!,
        #line : TimeZone.current
    ]

    func test_TimeZone_JSON() {
        for (testLine, timeZone) in timeZoneValues {
            expectRoundTripEqualityThroughJSON(for: timeZone, lineNumber: testLine)
        }
    }

    func test_TimeZone_Plist() {
        for (testLine, timeZone) in timeZoneValues {
            expectRoundTripEqualityThroughPlist(for: timeZone, lineNumber: testLine)
        }
    }
    
    // MARK: - URL
    lazy var urlValues: [Int : URL] = {
        var values: [Int : URL] = [
            #line : URL(fileURLWithPath: NSTemporaryDirectory()),
            #line : URL(fileURLWithPath: "/"),
            #line : URL(string: "http://swift.org")!,
            #line : URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!
        ]

        if #available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *) {
            values[#line] = URL(fileURLWithPath: "bin/sh", relativeTo: URL(fileURLWithPath: "/"))
        }

        return values
    }()

    func test_URL_JSON() {
        for (testLine, url) in urlValues {
            // URLs encode as single strings in JSON. They lose their baseURL this way.
            // For relative URLs, we don't expect them to be equal to the original.
            if url.baseURL == nil {
                // This is an absolute URL; we can expect equality.
                expectRoundTripEqualityThroughJSON(for: TopLevelWrapper(url), lineNumber: testLine)
            } else {
                // This is a relative URL. Make it absolute first.
                let absoluteURL = URL(string: url.absoluteString)!
                expectRoundTripEqualityThroughJSON(for: TopLevelWrapper(absoluteURL), lineNumber: testLine)
            }
        }
    }

    func test_URL_Plist() {
        for (testLine, url) in urlValues {
            expectRoundTripEqualityThroughPlist(for: url, lineNumber: testLine)
        }
    }
    
    // MARK: - URLComponents
    lazy var urlComponentsValues: [Int : URLComponents] = [
        #line : URLComponents(),

        #line : URLComponents(string: "http://swift.org")!,
        #line : URLComponents(string: "http://swift.org:80")!,
        #line : URLComponents(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!,
        #line : URLComponents(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!,

        #line : URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: false)!,
        #line : URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: false)!,
        #line : URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!, resolvingAgainstBaseURL: false)!,
        #line : URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: false)!,
        #line : URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: false)!,
        #line : URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: false)!,
        #line : URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: false)!,

        #line : URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: true)!,
        #line : URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: true)!,
        #line : URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi&param2=hello")!, resolvingAgainstBaseURL: true)!,
        #line : URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: true)!,
        #line : URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: true)!,
        #line : URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: true)!,
        #line : URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: true)!,

        #line : {
            var components = URLComponents()
            components.scheme = "https"
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.user = "johnny"
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.password = "apples"
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.host = "0.0.0.0"
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.port = 8080
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.path = ".."
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.query = "param1=hi&param2=there"
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.fragment = "anchor"
            return components
        }(),

        #line : {
            var components = URLComponents()
            components.scheme = "ftp"
            components.user = "johnny"
            components.password = "apples"
            components.host = "0.0.0.0"
            components.port = 4242
            components.path = "/some/file"
            components.query = "utf8=✅"
            components.fragment = "anchor"
            return components
        }()
    ]

    func test_URLComponents_JSON() {
        for (testLine, components) in urlComponentsValues {
            expectRoundTripEqualityThroughJSON(for: components, lineNumber: testLine)
        }
    }

    func test_URLComponents_Plist() {
        for (testLine, components) in urlComponentsValues {
            expectRoundTripEqualityThroughPlist(for: components, lineNumber: testLine)
        }
    }

    // MARK: - UUID
    lazy var uuidValues: [Int : UUID] = [
        #line : UUID(),
        #line : UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!,
        #line : UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f")!,
        #line : UUID(uuid: uuid_t(0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f))
    ]

    func test_UUID_JSON() {
        for (testLine, uuid) in uuidValues {
            // We have to wrap the UUID since we cannot have a top-level string.
            expectRoundTripEqualityThroughJSON(for: UUIDCodingWrapper(uuid), lineNumber: testLine)
        }
    }

    func test_UUID_Plist() {
        for (testLine, uuid) in uuidValues {
            // We have to wrap the UUID since we cannot have a top-level string.
            expectRoundTripEqualityThroughPlist(for: UUIDCodingWrapper(uuid), lineNumber: testLine)
        }
    }

    // MARK: - DecodingError
    func expectErrorDescription(
        _ expectedErrorDescription: String,
        fromDecodingError error: DecodingError,
        lineNumber: UInt = #line
    ) {
        expectEqual(String(describing: error), expectedErrorDescription, "Unexpectedly wrong error: \(error)", line: lineNumber)
    }

    func test_decodingError_typeMismatch_nilUnderlyingError() {
        expectErrorDescription(
            #"""
            DecodingError.typeMismatch: Expected value of type String. Path: [0].address.city.birds[1].name. Debug description: This is where the debug description goes
            """#,
            fromDecodingError: DecodingError.typeMismatch(
                String.self,
                DecodingError.Context(
                    codingPath: [0, "address", "city", "birds", 1, "name"] as [GenericCodingKey],
                    debugDescription: "This is where the debug description goes"
                )
            )
        )
    }

    func test_decodingError_typeMismatch_nonNilUnderlyingError() {
        expectErrorDescription(
            #"""
            DecodingError.typeMismatch: Expected value of type String. Path: [0].address[1].street. Debug description: Some debug description. Underlying error: GenericError(name: "some generic error goes here")
            """#,
            fromDecodingError: DecodingError.typeMismatch(
                String.self,
                DecodingError.Context(
                    codingPath: [0, "address", 1, "street"] as [GenericCodingKey],
                    debugDescription: "Some debug description",
                    underlyingError: GenericError(name: "some generic error goes here")
                )
            )
        )
    }

    func test_decodingError_valueNotFound_nilUnderlyingError() {
        expectErrorDescription(
            #"""
            DecodingError.valueNotFound: Expected value of type String. Path: [0].firstName. Debug description: Description for debugging purposes
            """#,
            fromDecodingError: DecodingError.valueNotFound(
                String.self,
                DecodingError.Context(
                    codingPath: [0, "firstName"] as [GenericCodingKey],
                    debugDescription: "Description for debugging purposes"
                )
            )
        )
    }

    func test_decodingError_valueNotFound_nonNilUnderlyingError() {
        expectErrorDescription(
            #"""
            DecodingError.valueNotFound: Expected value of type Int. Path: [0].population. Debug description: Here is the debug description for value-not-found. Underlying error: GenericError(name: "these aren\'t the droids you\'re looking for")
            """#,
            fromDecodingError: DecodingError.valueNotFound(
                Int.self,
                DecodingError.Context(
                    codingPath: [0, "population"] as [GenericCodingKey],
                    debugDescription: "Here is the debug description for value-not-found",
                    underlyingError: GenericError(name: "these aren't the droids you're looking for")
                )
            )
        )
    }

    func test_decodingError_keyNotFound_nilUnderlyingError() {
        expectErrorDescription(
            #"""
            DecodingError.keyNotFound: Key 'name' not found in keyed decoding container. Path: [0].address.city. Debug description: How would you describe your relationship with your debugger?
            """#,
            fromDecodingError: DecodingError.keyNotFound(
                GenericCodingKey(stringValue: "name"),
                DecodingError.Context(
                    codingPath: [0, "address", "city"] as [GenericCodingKey],
                    debugDescription: "How would you describe your relationship with your debugger?"
                )
            )
        )
    }

    func test_decodingError_keyNotFound_nonNilUnderlyingError() {
        expectErrorDescription(
            #"""
            DecodingError.keyNotFound: Key 'name' not found in keyed decoding container. Path: [0].address.city. Debug description: Just some info to help you out. Underlying error: GenericError(name: "hey, who turned out the lights?")
            """#,
            fromDecodingError: DecodingError.keyNotFound(
                GenericCodingKey(stringValue: "name"),
                DecodingError.Context(
                    codingPath: [0, "address", "city"] as [GenericCodingKey],
                    debugDescription: "Just some info to help you out",
                    underlyingError: GenericError(name: "hey, who turned out the lights?")
                )
            )
        )
    }

    func test_decodingError_dataCorrupted_emptyCodingPath() {
        expectErrorDescription(
            #"""
            DecodingError.dataCorrupted: Data was corrupted. Debug description: The given data was not valid JSON. Underlying error: GenericError(name: "just some data corruption")
            """#,
            fromDecodingError: DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: [] as [GenericCodingKey], // sometimes empty when generated by JSONDecoder
                    debugDescription: "The given data was not valid JSON",
                    underlyingError: GenericError(name: "just some data corruption")
                )
            )
        )
    }

    func test_decodingError_dataCorrupted_nonEmptyCodingPath() {
        expectErrorDescription(
            #"""
            DecodingError.dataCorrupted: Data was corrupted. Path: first.second[2]. Debug description: There was apparently some data corruption!. Underlying error: GenericError(name: "This data corruption is getting out of hand")
            """#,
            fromDecodingError: DecodingError.dataCorrupted(
                DecodingError.Context(
                    codingPath: ["first", "second", 2] as [GenericCodingKey],
                    debugDescription: "There was apparently some data corruption!",
                    underlyingError: GenericError(name: "This data corruption is getting out of hand")
                )
            )
        )
    }

    // MARK: - EncodingError
    func expectErrorDescription(
        _ expectedErrorDescription: String,
        fromEncodingError error: EncodingError,
        lineNumber: UInt = #line
    ) {
        expectEqual(String(describing: error), expectedErrorDescription, "Unexpectedly wrong error: \(error)", line: lineNumber)
    }

    func test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError() {
        expectErrorDescription(
            #"""
            EncodingError.invalidValue: 123 (Int). Debug description: You cannot do that!
            """#,
            fromEncodingError: EncodingError.invalidValue(
                123 as Int,
                EncodingError.Context(
                    codingPath: [] as [GenericCodingKey],
                    debugDescription: "You cannot do that!"
                )
            )
        )
    }

    func test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError() {
        expectErrorDescription(
            #"""
            EncodingError.invalidValue: 234 (Int). Path: first.second[2]. Debug description: You cannot do that!
            """#,
            fromEncodingError: EncodingError.invalidValue(
                234 as Int,
                EncodingError.Context(
                    codingPath: ["first", "second", 2] as [GenericCodingKey],
                    debugDescription: "You cannot do that!"
                )
            )
        )
    }

    func test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError() {
        expectErrorDescription(
            #"""
            EncodingError.invalidValue: 345 (Int). Debug description: You cannot do that!. Underlying error: GenericError(name: "You really cannot do that")
            """#,
            fromEncodingError: EncodingError.invalidValue(
                345 as Int,
                EncodingError.Context(
                    codingPath: [] as [GenericCodingKey],
                    debugDescription: "You cannot do that!",
                    underlyingError: GenericError(name: "You really cannot do that")
                )
            )
        )
    }

    func test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError() {
        expectErrorDescription(
            #"""
            EncodingError.invalidValue: 456 (Int). Path: first.second[2]. Debug description: You cannot do that!. Underlying error: GenericError(name: "You really cannot do that")
            """#,
            fromEncodingError: EncodingError.invalidValue(
                456 as Int,
                EncodingError.Context(
                    codingPath: ["first", "second", 2] as [GenericCodingKey],
                    debugDescription: "You cannot do that!",
                    underlyingError: GenericError(name: "You really cannot do that")
                )
            )
        )
    }

    func test_encodingError_pathEscaping() {
        func expectCodingPathDescription(
            _ expectedErrorDescription: String,
            fromCodingPath path: [GenericCodingKey],
            lineNumber: UInt = #line
        ) {
            let error = EncodingError.invalidValue(234 as Int, EncodingError.Context(codingPath: path, debugDescription: ""))
            expectEqual(String(describing: error), expectedErrorDescription, "Unexpectedly wrong error for path \(path): \(error)", line: lineNumber)
        }

        expectCodingPathDescription(
            #"""
            EncodingError.invalidValue: 234 (Int). Path: `first.second`[3]
            """#,
            fromCodingPath: ["first.second", 3]
        )

        expectCodingPathDescription(
            #"""
            EncodingError.invalidValue: 234 (Int). Path: [1].`second\`third`
            """#,
            fromCodingPath: [1, "second`third"]
        )

        expectCodingPathDescription(
            #"""
            EncodingError.invalidValue: 234 (Int). Path: [1].`second\\third`
            """#,
            fromCodingPath: [1, "second\\third"]
        )

        expectCodingPathDescription(
            #"""
            EncodingError.invalidValue: 234 (Int). Path: [1].`two.three\\four\`five.six..seven\`\`\`eight`[9][10]
            """#,
            fromCodingPath: [1, "two.three\\four`five.six..seven```eight", 9, 10]
        )
    }
}

// MARK: - Helper Types

struct GenericCodingKey: CodingKey {
    var stringValue: String
    var intValue: Int?

    init(stringValue: String) {
        self.stringValue = stringValue
    }

    init(intValue: Int) {
        self.stringValue = "\(intValue)"
        self.intValue = intValue
    }
}

extension GenericCodingKey: ExpressibleByStringLiteral {
    init(stringLiteral: String) {
        self.init(stringValue: stringLiteral)
    }
}

extension GenericCodingKey: ExpressibleByIntegerLiteral {
    init(integerLiteral: Int) {
        self.init(intValue: integerLiteral)
    }
}

struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
    let value: T

    init(_ value: T) {
        self.value = value
    }

    static func ==(_ lhs: TopLevelWrapper<T>, _ rhs: TopLevelWrapper<T>) -> Bool {
        return lhs.value == rhs.value
    }
}

struct GenericError: Error {
    let name: String
}

// MARK: - Tests

#if !FOUNDATION_XCTEST
var tests = [
    "test_Calendar_JSON" : TestCodable.test_Calendar_JSON,
    "test_Calendar_Plist" : TestCodable.test_Calendar_Plist,
    "test_CharacterSet_JSON" : TestCodable.test_CharacterSet_JSON,
    "test_CharacterSet_Plist" : TestCodable.test_CharacterSet_Plist,
    "test_CGAffineTransform_JSON" : TestCodable.test_CGAffineTransform_JSON,
    "test_CGAffineTransform_Plist" : TestCodable.test_CGAffineTransform_Plist,
    "test_CGPoint_JSON" : TestCodable.test_CGPoint_JSON,
    "test_CGPoint_Plist" : TestCodable.test_CGPoint_Plist,
    "test_CGSize_JSON" : TestCodable.test_CGSize_JSON,
    "test_CGSize_Plist" : TestCodable.test_CGSize_Plist,
    "test_CGRect_JSON" : TestCodable.test_CGRect_JSON,
    "test_CGRect_Plist" : TestCodable.test_CGRect_Plist,
    "test_CGVector_JSON" : TestCodable.test_CGVector_JSON,
    "test_CGVector_Plist" : TestCodable.test_CGVector_Plist,
    "test_ClosedRange_JSON" : TestCodable.test_ClosedRange_JSON,
    "test_ClosedRange_Plist" : TestCodable.test_ClosedRange_Plist,
    "test_ClosedRange_JSON_Errors" : TestCodable.test_ClosedRange_JSON_Errors,
    "test_CollectionDifference_JSON" : TestCodable.test_CollectionDifference_JSON,
    "test_CollectionDifference_Plist" : TestCodable.test_CollectionDifference_Plist,
    "test_CollectionDifference_JSON_Errors" : TestCodable.test_CollectionDifference_JSON_Errors,
    "test_ContiguousArray_JSON" : TestCodable.test_ContiguousArray_JSON,
    "test_ContiguousArray_Plist" : TestCodable.test_ContiguousArray_Plist,
    "test_DateComponents_JSON" : TestCodable.test_DateComponents_JSON,
    "test_DateComponents_Plist" : TestCodable.test_DateComponents_Plist,
    "test_Decimal_JSON" : TestCodable.test_Decimal_JSON,
    "test_Decimal_Plist" : TestCodable.test_Decimal_Plist,
    "test_IndexPath_JSON" : TestCodable.test_IndexPath_JSON,
    "test_IndexPath_Plist" : TestCodable.test_IndexPath_Plist,
    "test_IndexSet_JSON" : TestCodable.test_IndexSet_JSON,
    "test_IndexSet_Plist" : TestCodable.test_IndexSet_Plist,
    "test_Locale_JSON" : TestCodable.test_Locale_JSON,
    "test_Locale_Plist" : TestCodable.test_Locale_Plist,
    "test_NSRange_JSON" : TestCodable.test_NSRange_JSON,
    "test_NSRange_Plist" : TestCodable.test_NSRange_Plist,
    "test_PartialRangeFrom_JSON" : TestCodable.test_PartialRangeFrom_JSON,
    "test_PartialRangeFrom_Plist" : TestCodable.test_PartialRangeFrom_Plist,
    "test_PartialRangeThrough_JSON" : TestCodable.test_PartialRangeThrough_JSON,
    "test_PartialRangeThrough_Plist" : TestCodable.test_PartialRangeThrough_Plist,
    "test_PartialRangeUpTo_JSON" : TestCodable.test_PartialRangeUpTo_JSON,
    "test_PartialRangeUpTo_Plist" : TestCodable.test_PartialRangeUpTo_Plist,
    "test_Range_JSON" : TestCodable.test_Range_JSON,
    "test_Range_Plist" : TestCodable.test_Range_Plist,
    "test_Range_JSON_Errors" : TestCodable.test_Range_JSON_Errors,
    "test_TimeZone_JSON" : TestCodable.test_TimeZone_JSON,
    "test_TimeZone_Plist" : TestCodable.test_TimeZone_Plist,
    "test_URL_JSON" : TestCodable.test_URL_JSON,
    "test_URL_Plist" : TestCodable.test_URL_Plist,
    "test_UUID_JSON" : TestCodable.test_UUID_JSON,
    "test_UUID_Plist" : TestCodable.test_UUID_Plist,
    "test_decodingError_typeMismatch_nilUnderlyingError" : TestCodable.test_decodingError_typeMismatch_nilUnderlyingError,
    "test_decodingError_typeMismatch_nonNilUnderlyingError" : TestCodable.test_decodingError_typeMismatch_nonNilUnderlyingError,
    "test_decodingError_valueNotFound_nilUnderlyingError" : TestCodable.test_decodingError_valueNotFound_nilUnderlyingError,
    "test_decodingError_valueNotFound_nonNilUnderlyingError" : TestCodable.test_decodingError_valueNotFound_nonNilUnderlyingError,
    "test_decodingError_keyNotFound_nilUnderlyingError" : TestCodable.test_decodingError_keyNotFound_nilUnderlyingError,
    "test_decodingError_keyNotFound_nonNilUnderlyingError" : TestCodable.test_decodingError_keyNotFound_nonNilUnderlyingError,
    "test_decodingError_dataCorrupted_emptyCodingPath" : TestCodable.test_decodingError_dataCorrupted_emptyCodingPath,
    "test_decodingError_dataCorrupted_nonEmptyCodingPath" : TestCodable.test_decodingError_dataCorrupted_nonEmptyCodingPath,
    "test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError": TestCodable.test_encodingError_invalidValue_emptyCodingPath_nilUnderlyingError,
    "test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError": TestCodable.test_encodingError_invalidValue_nonEmptyCodingPath_nilUnderlyingError,
    "test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError": TestCodable.test_encodingError_invalidValue_emptyCodingPath_nonNilUnderlyingError,
    "test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError": TestCodable.test_encodingError_invalidValue_nonEmptyCodingPath_nonNilUnderlyingError,
    "test_encodingError_pathEscaping": TestCodable.test_encodingError_pathEscaping,
]

#if os(macOS)
    tests["test_AffineTransform_JSON"] = TestCodable.test_AffineTransform_JSON
    tests["test_AffineTransform_Plist"] = TestCodable.test_AffineTransform_Plist
#endif

if #available(macOS 10.11, iOS 9.0, watchOS 2.0, tvOS 9.0, *) {
    tests["test_PersonNameComponents_JSON"] = TestCodable.test_PersonNameComponents_JSON
    tests["test_PersonNameComponents_Plist"] = TestCodable.test_PersonNameComponents_Plist
}

if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
    tests["test_DateInterval_JSON"] = TestCodable.test_DateInterval_JSON
    tests["test_DateInterval_Plist"] = TestCodable.test_DateInterval_Plist
    tests["test_Measurement_JSON"] = TestCodable.test_Measurement_JSON
    tests["test_Measurement_Plist"] = TestCodable.test_Measurement_Plist
}

if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
    tests["test_URLComponents_JSON"] = TestCodable.test_URLComponents_JSON
    tests["test_URLComponents_Plist"] = TestCodable.test_URLComponents_Plist
}

if #available(SwiftStdlib 5.6, *) {
    tests["test_Dictionary_JSON"] = TestCodable.test_Dictionary_JSON
}

if #available(SwiftStdlib 5.9, *) {
    tests["test_Never"] = TestCodable.test_Never
}

var CodableTests = TestSuite("TestCodable")
for (name, test) in tests {
    CodableTests.test(name) { test(TestCodable())() }
}

runAllTests()
#endif
