| // 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 http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| |
| #if DEPLOYMENT_RUNTIME_OBJC || os(Linux) |
| import Foundation |
| import XCTest |
| #else |
| import SwiftFoundation |
| import SwiftXCTest |
| #endif |
| |
| // MARK: - Helper Functions |
| |
| private 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 expectRoundTripEquality<T : Codable>(of value: T, encode: (T) throws -> Data, decode: (Data) throws -> T) throws where T : Equatable { |
| do { |
| let data = try encode(value) |
| let decoded: T = try decode(data) |
| if value != decoded { |
| throw NSError(domain: "Decode mismatch", code: 0, userInfo: ["msg": "Decoded \(T.self) <\(decoded)> not equal to original <\(value)>"]) |
| } |
| } |
| } |
| |
| func expectRoundTripEqualityThroughJSON<T : Codable>(for value: T) throws 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) |
| return try encoder.encode(value) |
| } |
| |
| let decode = { (_ data: Data) throws -> T in |
| let decoder = JSONDecoder() |
| decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: inf, |
| negativeInfinity: negInf, |
| nan: nan) |
| return try decoder.decode(T.self, from: data) |
| } |
| |
| try expectRoundTripEquality(of: value, encode: encode, decode: decode) |
| } |
| |
| // 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 { |
| let value: UUID |
| |
| init(_ value: UUID) { |
| self.value = value |
| } |
| |
| static func ==(_ lhs: UUIDCodingWrapper, _ rhs: UUIDCodingWrapper) -> Bool { |
| return lhs.value == rhs.value |
| } |
| } |
| |
| // MARK: - Tests |
| class TestCodable : XCTestCase { |
| |
| // MARK: - PersonNameComponents |
| lazy var personNameComponentsValues: [PersonNameComponents] = [ |
| makePersonNameComponents(givenName: "John", familyName: "Appleseed"), |
| makePersonNameComponents(givenName: "John", familyName: "Appleseed", nickname: "Johnny"), |
| makePersonNameComponents(namePrefix: "Dr.", givenName: "Jane", middleName: "A.", familyName: "Appleseed", nameSuffix: "Esq.", nickname: "Janie") |
| ] |
| |
| func test_PersonNameComponents_JSON() { |
| for components in personNameComponentsValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: components) |
| } catch let error { |
| XCTFail("\(error) for \(components)") |
| } |
| } |
| } |
| |
| // MARK: - UUID |
| lazy var uuidValues: [UUID] = [ |
| UUID(), |
| UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, |
| UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f")!, |
| 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 uuid in uuidValues { |
| // We have to wrap the UUID since we cannot have a top-level string. |
| do { |
| try expectRoundTripEqualityThroughJSON(for: UUIDCodingWrapper(uuid)) |
| } catch let error { |
| XCTFail("\(error) for \(uuid)") |
| } |
| } |
| } |
| |
| // MARK: - URL |
| lazy var urlValues: [URL] = [ |
| URL(fileURLWithPath: NSTemporaryDirectory()), |
| URL(fileURLWithPath: "/"), |
| URL(string: "http://apple.com")!, |
| URL(string: "swift", relativeTo: URL(string: "http://apple.com")!)!, |
| URL(fileURLWithPath: "bin/sh", relativeTo: URL(fileURLWithPath: "/")) |
| ] |
| |
| func test_URL_JSON() { |
| for url in urlValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: url) |
| } catch let error { |
| XCTFail("\(error) for \(url)") |
| } |
| } |
| } |
| |
| // MARK: - NSRange |
| lazy var nsrangeValues: [NSRange] = [ |
| NSRange(), |
| NSRange(location: 0, length: Int.max), |
| NSRange(location: NSNotFound, length: 0), |
| ] |
| |
| func test_NSRange_JSON() { |
| for range in nsrangeValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: range) |
| } catch let error { |
| XCTFail("\(error) for \(range)") |
| } |
| } |
| } |
| |
| // MARK: - Locale |
| lazy var localeValues: [Locale] = [ |
| Locale(identifier: ""), |
| Locale(identifier: "en"), |
| Locale(identifier: "en_US"), |
| Locale(identifier: "en_US_POSIX"), |
| Locale(identifier: "uk"), |
| Locale(identifier: "fr_FR"), |
| Locale(identifier: "fr_BE"), |
| Locale(identifier: "zh-Hant-HK") |
| ] |
| |
| func test_Locale_JSON() { |
| for locale in localeValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: locale) |
| } catch let error { |
| XCTFail("\(error) for \(locale)") |
| } |
| } |
| } |
| |
| // MARK: - IndexSet |
| lazy var indexSetValues: [IndexSet] = [ |
| IndexSet(), |
| IndexSet(integer: 42), |
| IndexSet(integersIn: 0 ..< Int.max) |
| ] |
| |
| func test_IndexSet_JSON() { |
| for indexSet in indexSetValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: indexSet) |
| } catch let error { |
| XCTFail("\(error) for \(indexSet)") |
| } |
| } |
| } |
| |
| // MARK: - IndexPath |
| lazy var indexPathValues: [IndexPath] = [ |
| IndexPath(), // empty |
| IndexPath(index: 0), // single |
| IndexPath(indexes: [1, 2]), // pair |
| IndexPath(indexes: [3, 4, 5, 6, 7, 8]), // array |
| ] |
| |
| func test_IndexPath_JSON() { |
| for indexPath in indexPathValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: indexPath) |
| } catch let error { |
| XCTFail("\(error) for \(indexPath)") |
| } |
| } |
| } |
| |
| // MARK: - AffineTransform |
| lazy var affineTransformValues: [AffineTransform] = [ |
| AffineTransform.identity, |
| AffineTransform(), |
| AffineTransform(translationByX: 2.0, byY: 2.0), |
| AffineTransform(scale: 2.0), |
| |
| // Disabled due to a bug: JSONSerialization loses precision for m12 and m21 |
| // 0.02741213359204429 is serialized to 0.0274121335920443 |
| // AffineTransform(rotationByDegrees: .pi / 2), |
| |
| AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7), |
| AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33), |
| AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2), |
| AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99), |
| AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44) |
| ] |
| |
| func test_AffineTransform_JSON() { |
| for transform in affineTransformValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: transform) |
| } catch let error { |
| XCTFail("\(error) for \(transform)") |
| } |
| } |
| } |
| |
| // MARK: - Decimal |
| lazy var decimalValues: [Decimal] = [ |
| Decimal.leastFiniteMagnitude, |
| Decimal.greatestFiniteMagnitude, |
| Decimal.leastNormalMagnitude, |
| Decimal.leastNonzeroMagnitude, |
| Decimal.pi, |
| Decimal() |
| ] |
| |
| func test_Decimal_JSON() { |
| for decimal in decimalValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: decimal) |
| } catch let error { |
| XCTFail("\(error) for \(decimal)") |
| } |
| } |
| } |
| |
| // MARK: - CGPoint |
| lazy var cgpointValues: [CGPoint] = [ |
| CGPoint(), |
| CGPoint.zero, |
| CGPoint(x: 10, y: 20), |
| CGPoint(x: -10, y: -20), |
| // Disabled due to limit on magnitude in JSON. See SR-5346 |
| // CGPoint(x: .greatestFiniteMagnitude, y: .greatestFiniteMagnitude), |
| ] |
| |
| func test_CGPoint_JSON() { |
| for point in cgpointValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: point) |
| } catch let error { |
| XCTFail("\(error) for \(point)") |
| } |
| } |
| } |
| |
| // MARK: - CGSize |
| lazy var cgsizeValues: [CGSize] = [ |
| CGSize(), |
| CGSize.zero, |
| CGSize(width: 30, height: 40), |
| CGSize(width: -30, height: -40), |
| // Disabled due to limit on magnitude in JSON. See SR-5346 |
| // CGSize(width: .greatestFiniteMagnitude, height: .greatestFiniteMagnitude), |
| ] |
| |
| func test_CGSize_JSON() { |
| for size in cgsizeValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: size) |
| } catch let error { |
| XCTFail("\(error) for \(size)") |
| } |
| } |
| } |
| |
| // MARK: - CGRect |
| lazy var cgrectValues: [CGRect] = [ |
| CGRect(), |
| CGRect.zero, |
| CGRect(origin: CGPoint(x: 10, y: 20), size: CGSize(width: 30, height: 40)), |
| CGRect(origin: CGPoint(x: -10, y: -20), size: CGSize(width: -30, height: -40)), |
| CGRect.null, |
| // Disabled due to limit on magnitude in JSON. See SR-5346 |
| // CGRect.infinite |
| ] |
| |
| func test_CGRect_JSON() { |
| for rect in cgrectValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: rect) |
| } catch let error { |
| XCTFail("\(error) for \(rect)") |
| } |
| } |
| } |
| |
| // MARK: - CharacterSet |
| lazy var characterSetValues: [CharacterSet] = [ |
| CharacterSet.controlCharacters, |
| CharacterSet.whitespaces, |
| CharacterSet.whitespacesAndNewlines, |
| CharacterSet.decimalDigits, |
| CharacterSet.letters, |
| CharacterSet.lowercaseLetters, |
| CharacterSet.uppercaseLetters, |
| CharacterSet.nonBaseCharacters, |
| CharacterSet.alphanumerics, |
| CharacterSet.decomposables, |
| CharacterSet.illegalCharacters, |
| CharacterSet.punctuationCharacters, |
| CharacterSet.capitalizedLetters, |
| CharacterSet.symbols, |
| CharacterSet.newlines, |
| CharacterSet(charactersIn: "abcd") |
| ] |
| |
| func test_CharacterSet_JSON() { |
| for characterSet in characterSetValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: characterSet) |
| } catch let error { |
| XCTFail("\(error) for \(characterSet)") |
| } |
| } |
| } |
| |
| // MARK: - TimeZone |
| lazy var timeZoneValues: [TimeZone] = { |
| #if !os(Android) |
| var values = [ |
| TimeZone(identifier: "America/Los_Angeles")!, |
| TimeZone(identifier: "UTC")!, |
| ] |
| |
| #if !os(Linux) |
| // Disabled due to [SR-5598] bug, which occurs on Linux, and breaks |
| // TimeZone.current == TimeZone(identifier: TimeZone.current.identifier) equality, |
| // causing encode -> decode -> compare test to fail. |
| values.append(TimeZone.current) |
| #endif |
| #else |
| var values = [ |
| TimeZone(identifier: "UTC")!, |
| TimeZone.current |
| ] |
| #endif |
| return values |
| }() |
| |
| func test_TimeZone_JSON() { |
| for timeZone in timeZoneValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: timeZone) |
| } catch let error { |
| XCTFail("\(error) for \(timeZone)") |
| } |
| } |
| } |
| |
| // MARK: - Calendar |
| lazy var calendarValues: [Calendar] = { |
| var values = [ |
| Calendar(identifier: .gregorian), |
| Calendar(identifier: .buddhist), |
| Calendar(identifier: .chinese), |
| Calendar(identifier: .coptic), |
| Calendar(identifier: .ethiopicAmeteMihret), |
| Calendar(identifier: .ethiopicAmeteAlem), |
| Calendar(identifier: .hebrew), |
| Calendar(identifier: .iso8601), |
| Calendar(identifier: .indian), |
| Calendar(identifier: .islamic), |
| Calendar(identifier: .islamicCivil), |
| Calendar(identifier: .japanese), |
| Calendar(identifier: .persian), |
| Calendar(identifier: .republicOfChina), |
| ] |
| |
| #if os(Linux) |
| // Custom timeZone set to work around [SR-5598] bug, which occurs on Linux, and breaks equality after |
| // serializing and deserializing TimeZone.current |
| for index in values.indices { |
| values[index].timeZone = TimeZone(identifier: "UTC")! |
| } |
| #endif |
| |
| return values |
| }() |
| |
| func test_Calendar_JSON() { |
| for calendar in calendarValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: calendar) |
| } catch let error { |
| XCTFail("\(error) for \(calendar)") |
| } |
| } |
| } |
| |
| // MARK: - DateComponents |
| lazy var dateComponents: Set<Calendar.Component> = [ |
| .era, |
| .year, |
| .month, |
| .day, |
| .hour, |
| .minute, |
| .second, |
| .weekday, |
| .weekdayOrdinal, |
| .weekOfMonth, |
| .weekOfYear, |
| .yearForWeekOfYear, |
| .timeZone, |
| .calendar, |
| // [SR-5576] Disabled due to a bug in Calendar.dateComponents(_:from:) which crashes on Darwin and returns |
| // invalid values on Linux if components include .nanosecond or .quarter. |
| // .nanosecond, |
| // .quarter, |
| ] |
| |
| func test_DateComponents_JSON() { |
| #if os(Linux) |
| var calendar = Calendar(identifier: .gregorian) |
| // Custom timeZone set to work around [SR-5598] bug, which occurs on Linux, and breaks equality after |
| // serializing and deserializing TimeZone.current |
| calendar.timeZone = TimeZone(identifier: "UTC")! |
| #else |
| let calendar = Calendar(identifier: .gregorian) |
| #endif |
| |
| let components = calendar.dateComponents(dateComponents, from: Date(timeIntervalSince1970: 1501283776)) |
| do { |
| try expectRoundTripEqualityThroughJSON(for: components) |
| } catch let error { |
| XCTFail("\(error)") |
| } |
| } |
| |
| // MARK: - Measurement |
| func test_Measurement_JSON() { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: Measurement(value: 42, unit: UnitAcceleration.metersPerSecondSquared)) |
| try expectRoundTripEqualityThroughJSON(for: Measurement(value: 42, unit: UnitMass.kilograms)) |
| try expectRoundTripEqualityThroughJSON(for: Measurement(value: 42, unit: UnitLength.miles)) |
| } catch let error { |
| XCTFail("\(error)") |
| } |
| } |
| |
| // MARK: - URLComponents |
| lazy var urlComponentsValues: [URLComponents] = [ |
| URLComponents(), |
| |
| URLComponents(string: "http://swift.org")!, |
| URLComponents(string: "http://swift.org:80")!, |
| URLComponents(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi¶m2=hello")!, |
| URLComponents(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, |
| |
| URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: false)!, |
| URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: false)!, |
| URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi¶m2=hello")!, resolvingAgainstBaseURL: false)!, |
| URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: false)!, |
| URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: false)!, |
| URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: false)!, |
| URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: false)!, |
| |
| URLComponents(url: URL(string: "http://swift.org")!, resolvingAgainstBaseURL: true)!, |
| URLComponents(url: URL(string: "http://swift.org:80")!, resolvingAgainstBaseURL: true)!, |
| URLComponents(url: URL(string: "https://www.mywebsite.org/api/v42/something.php#param1=hi¶m2=hello")!, resolvingAgainstBaseURL: true)!, |
| URLComponents(url: URL(string: "ftp://johnny:apples@myftpserver.org:4242/some/path")!, resolvingAgainstBaseURL: true)!, |
| URLComponents(url: URL(fileURLWithPath: NSTemporaryDirectory()), resolvingAgainstBaseURL: true)!, |
| URLComponents(url: URL(fileURLWithPath: "/"), resolvingAgainstBaseURL: true)!, |
| URLComponents(url: URL(string: "documentation", relativeTo: URL(string: "http://swift.org")!)!, resolvingAgainstBaseURL: true)!, |
| |
| { |
| var components = URLComponents() |
| components.scheme = "https" |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.user = "johnny" |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.password = "apples" |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.host = "0.0.0.0" |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.port = 8080 |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.path = ".." |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.query = "param1=hi¶m2=there" |
| return components |
| }(), |
| |
| { |
| var components = URLComponents() |
| components.fragment = "anchor" |
| return components |
| }(), |
| |
| { |
| 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() { |
| #if !DARWIN_COMPATIBILITY_TESTS // crashes on native Darwin |
| for (components) in urlComponentsValues { |
| do { |
| try expectRoundTripEqualityThroughJSON(for: components) |
| } catch let error { |
| XCTFail("\(error)") |
| } |
| } |
| #endif |
| } |
| } |
| |
| extension TestCodable { |
| static var allTests: [(String, (TestCodable) -> () throws -> Void)] { |
| return [ |
| ("test_PersonNameComponents_JSON", test_PersonNameComponents_JSON), |
| ("test_UUID_JSON", test_UUID_JSON), |
| ("test_URL_JSON", test_URL_JSON), |
| ("test_NSRange_JSON", test_NSRange_JSON), |
| ("test_Locale_JSON", test_Locale_JSON), |
| ("test_IndexSet_JSON", test_IndexSet_JSON), |
| ("test_IndexPath_JSON", test_IndexPath_JSON), |
| ("test_AffineTransform_JSON", test_AffineTransform_JSON), |
| ("test_Decimal_JSON", test_Decimal_JSON), |
| ("test_CGPoint_JSON", test_CGPoint_JSON), |
| ("test_CGSize_JSON", test_CGSize_JSON), |
| ("test_CGRect_JSON", test_CGRect_JSON), |
| ("test_CharacterSet_JSON", test_CharacterSet_JSON), |
| ("test_TimeZone_JSON", test_TimeZone_JSON), |
| ("test_Calendar_JSON", test_Calendar_JSON), |
| ("test_DateComponents_JSON", test_DateComponents_JSON), |
| ("test_Measurement_JSON", test_Measurement_JSON), |
| ("test_URLComponents_JSON", test_URLComponents_JSON), |
| ] |
| } |
| } |