From 21e02c08f651c71baa3acac951ae2549cf34f611 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Tue, 17 Jun 2025 10:57:35 -0700 Subject: [PATCH 1/3] Convert JSONEncoder tests --- .../JSONEncoderTests.swift | 1230 +++++++++-------- 1 file changed, 633 insertions(+), 597 deletions(-) diff --git a/Tests/FoundationEssentialsTests/JSONEncoderTests.swift b/Tests/FoundationEssentialsTests/JSONEncoderTests.swift index 6697df493..6d4d81c8d 100644 --- a/Tests/FoundationEssentialsTests/JSONEncoderTests.swift +++ b/Tests/FoundationEssentialsTests/JSONEncoderTests.swift @@ -9,138 +9,145 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -// REQUIRES: rdar49634697 -// REQUIRES: rdar55727144 -#if canImport(TestSupport) -import TestSupport -#endif // canImport(TestSupport) +import Testing + +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +@preconcurrency import Bionic +#elseif canImport(Glibc) +@preconcurrency import Glibc +#elseif canImport(Musl) +@preconcurrency import Musl +#elseif canImport(CRT) +import CRT +#elseif os(WASI) +@preconcurrency import WASILibc +#endif #if canImport(FoundationEssentials) @_spi(SwiftCorelibsFoundation) -@testable import FoundationEssentials +import FoundationEssentials #endif #if FOUNDATION_FRAMEWORK -@testable import Foundation +import Foundation #endif // MARK: - Test Suite -final class JSONEncoderTests : XCTestCase { +@Suite("JSONEncoder") +private struct JSONEncoderTests { // MARK: - Encoding Top-Level Empty Types - func testEncodingTopLevelEmptyStruct() { + @Test func encodingTopLevelEmptyStruct() { let empty = EmptyStruct() _testRoundTrip(of: empty, expectedJSON: _jsonEmptyDictionary) } - func testEncodingTopLevelEmptyClass() { + @Test func encodingTopLevelEmptyClass() { let empty = EmptyClass() _testRoundTrip(of: empty, expectedJSON: _jsonEmptyDictionary) } // MARK: - Encoding Top-Level Single-Value Types - func testEncodingTopLevelSingleValueEnum() { + @Test func encodingTopLevelSingleValueEnum() { _testRoundTrip(of: Switch.off) _testRoundTrip(of: Switch.on) } - func testEncodingTopLevelSingleValueStruct() { + @Test func encodingTopLevelSingleValueStruct() { _testRoundTrip(of: Timestamp(3141592653)) } - func testEncodingTopLevelSingleValueClass() { + @Test func encodingTopLevelSingleValueClass() { _testRoundTrip(of: Counter()) } // MARK: - Encoding Top-Level Structured Types - func testEncodingTopLevelStructuredStruct() { + @Test func encodingTopLevelStructuredStruct() { // Address is a struct type with multiple fields. let address = Address.testValue _testRoundTrip(of: address) } - func testEncodingTopLevelStructuredSingleStruct() { + @Test func encodingTopLevelStructuredSingleStruct() { // Numbers is a struct which encodes as an array through a single value container. let numbers = Numbers.testValue _testRoundTrip(of: numbers) } - func testEncodingTopLevelStructuredSingleClass() { + @Test func encodingTopLevelStructuredSingleClass() { // Mapping is a class which encodes as a dictionary through a single value container. let mapping = Mapping.testValue _testRoundTrip(of: mapping) } - func testEncodingTopLevelDeepStructuredType() { + @Test func encodingTopLevelDeepStructuredType() { // Company is a type with fields which are Codable themselves. let company = Company.testValue _testRoundTrip(of: company) } - func testEncodingClassWhichSharesEncoderWithSuper() { + @Test func encodingClassWhichSharesEncoderWithSuper() { // Employee is a type which shares its encoder & decoder with its superclass, Person. let employee = Employee.testValue _testRoundTrip(of: employee) } - func testEncodingTopLevelNullableType() { + @Test func encodingTopLevelNullableType() { // EnhancedBool is a type which encodes either as a Bool or as nil. - _testRoundTrip(of: EnhancedBool.true, expectedJSON: "true".data(using: String._Encoding.utf8)!) - _testRoundTrip(of: EnhancedBool.false, expectedJSON: "false".data(using: String._Encoding.utf8)!) - _testRoundTrip(of: EnhancedBool.fileNotFound, expectedJSON: "null".data(using: String._Encoding.utf8)!) + _testRoundTrip(of: EnhancedBool.true, expectedJSON: "true".data(using: .utf8)!) + _testRoundTrip(of: EnhancedBool.false, expectedJSON: "false".data(using: .utf8)!) + _testRoundTrip(of: EnhancedBool.fileNotFound, expectedJSON: "null".data(using: .utf8)!) } - func testEncodingTopLevelArrayOfInt() { + @Test func encodingTopLevelArrayOfInt() throws { let a = [1,2,3] - let result1 = String(data: try! JSONEncoder().encode(a), encoding: String._Encoding.utf8) - XCTAssertEqual(result1, "[1,2,3]") + let result1 = String(data: try JSONEncoder().encode(a), encoding: .utf8) + #expect(result1 == "[1,2,3]") let b : [Int] = [] - let result2 = String(data: try! JSONEncoder().encode(b), encoding: String._Encoding.utf8) - XCTAssertEqual(result2, "[]") + let result2 = String(data: try JSONEncoder().encode(b), encoding: .utf8) + #expect(result2 == "[]") } - func testEncodingTopLevelWithConfiguration() throws { + @Test func encodingTopLevelWithConfiguration() throws { // CodableTypeWithConfiguration is a struct that conforms to CodableWithConfiguration let value = CodableTypeWithConfiguration.testValue let encoder = JSONEncoder() let decoder = JSONDecoder() var decoded = try decoder.decode(CodableTypeWithConfiguration.self, from: try encoder.encode(value, configuration: .init(1)), configuration: .init(1)) - XCTAssertEqual(decoded, value) + #expect(decoded == value) decoded = try decoder.decode(CodableTypeWithConfiguration.self, from: try encoder.encode(value, configuration: CodableTypeWithConfiguration.ConfigProviding.self), configuration: CodableTypeWithConfiguration.ConfigProviding.self) - XCTAssertEqual(decoded, value) + #expect(decoded == value) } -#if false // FIXME: XCTest doesn't support crash tests yet rdar://20195010&22387653 - func testEncodingConflictedTypeNestedContainersWithTheSameTopLevelKey() { + #if FOUNDATION_EXIT_TESTS + @Test func encodingConflictedTypeNestedContainersWithTheSameTopLevelKey() async { struct Model : Encodable, Equatable { let first: String - + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: TopLevelCodingKeys.self) - + var firstNestedContainer = container.nestedContainer(keyedBy: FirstNestedCodingKeys.self, forKey: .top) try firstNestedContainer.encode(self.first, forKey: .first) - + // The following line would fail as it attempts to re-encode into already encoded container is invalid. This will always fail var secondNestedContainer = container.nestedUnkeyedContainer(forKey: .top) try secondNestedContainer.encode("second") } - + init(first: String) { self.first = first } - + static var testValue: Model { return Model(first: "Johnny Appleseed") } - + enum TopLevelCodingKeys : String, CodingKey { case top } @@ -148,20 +155,21 @@ final class JSONEncoderTests : XCTestCase { case first } } - - let model = Model.testValue - // This following test would fail as it attempts to re-encode into already encoded container is invalid. This will always fail - expectCrashLater() - _testEncodeFailure(of: model) + + await #expect(processExitsWith: .failure) { + let model = Model.testValue + // This following test would fail as it attempts to re-encode into already encoded container is invalid. This will always fail + _ = try JSONEncoder().encode(model) + } } -#endif + #endif // MARK: - Date Strategy Tests - func testEncodingDateSecondsSince1970() { + @Test func encodingDateSecondsSince1970() { // Cannot encode an arbitrary number of seconds since we've lost precision since 1970. let seconds = 1000.0 - let expectedJSON = "1000".data(using: String._Encoding.utf8)! + let expectedJSON = "1000".data(using: .utf8)! _testRoundTrip(of: Date(timeIntervalSince1970: seconds), expectedJSON: expectedJSON, @@ -175,10 +183,10 @@ final class JSONEncoderTests : XCTestCase { dateDecodingStrategy: .secondsSince1970) } - func testEncodingDateMillisecondsSince1970() { + @Test func encodingDateMillisecondsSince1970() { // Cannot encode an arbitrary number of seconds since we've lost precision since 1970. let seconds = 1000.0 - let expectedJSON = "1000000".data(using: String._Encoding.utf8)! + let expectedJSON = "1000000".data(using: .utf8)! _testRoundTrip(of: Date(timeIntervalSince1970: seconds), expectedJSON: expectedJSON, @@ -215,7 +223,7 @@ final class JSONEncoderTests : XCTestCase { } } - func test_encodingDateCustom() { + @Test func encodingDateCustom() { let timestamp = Date() // We'll encode a number instead of a date. @@ -225,7 +233,7 @@ final class JSONEncoderTests : XCTestCase { } let decode = { @Sendable (_: Decoder) throws -> Date in return timestamp } - let expectedJSON = "42".data(using: String._Encoding.utf8)! + let expectedJSON = "42".data(using: .utf8)! _testRoundTrip(of: timestamp, expectedJSON: expectedJSON, dateEncodingStrategy: .custom(encode), @@ -238,21 +246,21 @@ final class JSONEncoderTests : XCTestCase { dateDecodingStrategy: .custom(decode)) // So should wrapped dates. - let expectedJSON_array = "[42]".data(using: String._Encoding.utf8)! + let expectedJSON_array = "[42]".data(using: .utf8)! _testRoundTrip(of: TopLevelArrayWrapper(timestamp), expectedJSON: expectedJSON_array, dateEncodingStrategy: .custom(encode), dateDecodingStrategy: .custom(decode)) } - func testEncodingDateCustomEmpty() { + @Test func encodingDateCustomEmpty() { let timestamp = Date() // Encoding nothing should encode an empty keyed container ({}). let encode = { @Sendable (_: Date, _: Encoder) throws -> Void in } let decode = { @Sendable (_: Decoder) throws -> Date in return timestamp } - let expectedJSON = "{}".data(using: String._Encoding.utf8)! + let expectedJSON = "{}".data(using: .utf8)! _testRoundTrip(of: timestamp, expectedJSON: expectedJSON, dateEncodingStrategy: .custom(encode), @@ -266,10 +274,10 @@ final class JSONEncoderTests : XCTestCase { } // MARK: - Data Strategy Tests - func testEncodingData() { + @Test func encodingData() { let data = Data([0xDE, 0xAD, 0xBE, 0xEF]) - let expectedJSON = "[222,173,190,239]".data(using: String._Encoding.utf8)! + let expectedJSON = "[222,173,190,239]".data(using: .utf8)! _testRoundTrip(of: data, expectedJSON: expectedJSON, dataEncodingStrategy: .deferredToData, @@ -282,7 +290,7 @@ final class JSONEncoderTests : XCTestCase { dataDecodingStrategy: .deferredToData) } - func testEncodingDataCustom() { + @Test func encodingDataCustom() { // We'll encode a number instead of data. let encode = { @Sendable (_ data: Data, _ encoder: Encoder) throws -> Void in var container = encoder.singleValueContainer() @@ -290,7 +298,7 @@ final class JSONEncoderTests : XCTestCase { } let decode = { @Sendable (_: Decoder) throws -> Data in return Data() } - let expectedJSON = "42".data(using: String._Encoding.utf8)! + let expectedJSON = "42".data(using: .utf8)! _testRoundTrip(of: Data(), expectedJSON: expectedJSON, dataEncodingStrategy: .custom(encode), @@ -303,12 +311,12 @@ final class JSONEncoderTests : XCTestCase { dataDecodingStrategy: .custom(decode)) } - func testEncodingDataCustomEmpty() { + @Test func encodingDataCustomEmpty() { // Encoding nothing should encode an empty keyed container ({}). let encode = { @Sendable (_: Data, _: Encoder) throws -> Void in } let decode = { @Sendable (_: Decoder) throws -> Data in return Data() } - let expectedJSON = "{}".data(using: String._Encoding.utf8)! + let expectedJSON = "{}".data(using: .utf8)! _testRoundTrip(of: Data(), expectedJSON: expectedJSON, dataEncodingStrategy: .custom(encode), @@ -322,7 +330,7 @@ final class JSONEncoderTests : XCTestCase { } // MARK: - Non-Conforming Floating Point Strategy Tests - func testEncodingNonConformingFloats() { + @Test func encodingNonConformingFloats() { _testEncodeFailure(of: Float.infinity) _testEncodeFailure(of: Float.infinity) _testEncodeFailure(of: -Float.infinity) @@ -342,62 +350,62 @@ final class JSONEncoderTests : XCTestCase { _testEncodeFailure(of: Double.nan) } - func testEncodingNonConformingFloatStrings() { + @Test func encodingNonConformingFloatStrings() { let encodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN") let decodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN") _testRoundTrip(of: Float.infinity, - expectedJSON: "\"INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) _testRoundTrip(of: -Float.infinity, - expectedJSON: "\"-INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"-INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) // Since Float.nan != Float.nan, we have to use a placeholder that'll encode NaN but actually round-trip. _testRoundTrip(of: FloatNaNPlaceholder(), - expectedJSON: "\"NaN\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"NaN\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) _testRoundTrip(of: Double.infinity, - expectedJSON: "\"INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) _testRoundTrip(of: -Double.infinity, - expectedJSON: "\"-INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"-INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) // Since Double.nan != Double.nan, we have to use a placeholder that'll encode NaN but actually round-trip. _testRoundTrip(of: DoubleNaNPlaceholder(), - expectedJSON: "\"NaN\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"NaN\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) // Optional Floats and Doubles should encode the same way. _testRoundTrip(of: Optional(Float.infinity), - expectedJSON: "\"INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) _testRoundTrip(of: Optional(-Float.infinity), - expectedJSON: "\"-INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"-INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) _testRoundTrip(of: Optional(Double.infinity), - expectedJSON: "\"INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) _testRoundTrip(of: Optional(-Double.infinity), - expectedJSON: "\"-INF\"".data(using: String._Encoding.utf8)!, + expectedJSON: "\"-INF\"".data(using: .utf8)!, nonConformingFloatEncodingStrategy: encodingStrategy, nonConformingFloatDecodingStrategy: decodingStrategy) } // MARK: - Directly Encoded Array Tests - func testDirectlyEncodedArrays() { + @Test func directlyEncodedArrays() { let encodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN") let decodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN") @@ -430,7 +438,7 @@ final class JSONEncoderTests : XCTestCase { } } - func testEncodingKeyStrategyCustom() { + @Test func encodingKeyStrategyCustom() { let expected = "{\"QQQhello\":\"test\"}" let encoded = EncodeMe(keyName: "hello") @@ -441,9 +449,9 @@ final class JSONEncoderTests : XCTestCase { } encoder.keyEncodingStrategy = .custom(customKeyConversion) let resultData = try! encoder.encode(encoded) - let resultString = String(bytes: resultData, encoding: String._Encoding.utf8) + let resultString = String(bytes: resultData, encoding: .utf8) - XCTAssertEqual(expected, resultString) + #expect(expected == resultString) } private struct EncodeFailure : Encodable { @@ -462,7 +470,7 @@ final class JSONEncoderTests : XCTestCase { let outerValue: EncodeNested } - func testEncodingKeyStrategyPath() { + @Test func encodingKeyStrategyPath() throws { // Make sure a more complex path shows up the way we want // Make sure the path reflects keys in the Swift, not the resulting ones in the JSON let expected = "{\"QQQouterValue\":{\"QQQnestedValue\":{\"QQQhelloWorld\":\"test\"}}}" @@ -480,26 +488,26 @@ final class JSONEncoderTests : XCTestCase { callCount = callCount + 1 if path.count == 0 { - XCTFail("The path should always have at least one entry") + Issue.record("The path should always have at least one entry") } else if path.count == 1 { - XCTAssertEqual(["outerValue"], path.map { $0.stringValue }) + #expect(["outerValue"] == path.map { $0.stringValue }) } else if path.count == 2 { - XCTAssertEqual(["outerValue", "nestedValue"], path.map { $0.stringValue }) + #expect(["outerValue", "nestedValue"] == path.map { $0.stringValue }) } else if path.count == 3 { - XCTAssertEqual(["outerValue", "nestedValue", "helloWorld"], path.map { $0.stringValue }) + #expect(["outerValue", "nestedValue", "helloWorld"] == path.map { $0.stringValue }) } else { - XCTFail("The path mysteriously had more entries") + Issue.record("The path mysteriously had more entries") } let key = _TestKey(stringValue: "QQQ" + path.last!.stringValue)! return key } encoder.keyEncodingStrategy = .custom(customKeyConversion) - let resultData = try! encoder.encode(encoded) - let resultString = String(bytes: resultData, encoding: String._Encoding.utf8) + let resultData = try encoder.encode(encoded) + let resultString = String(bytes: resultData, encoding: .utf8) - XCTAssertEqual(expected, resultString) - XCTAssertEqual(3, callCount) + #expect(expected == resultString) + #expect(3 == callCount) } private struct DecodeMe : Decodable { @@ -516,8 +524,8 @@ final class JSONEncoderTests : XCTestCase { private struct DecodeMe2 : Decodable { var hello: String } - func testDecodingKeyStrategyCustom() { - let input = "{\"----hello\":\"test\"}".data(using: String._Encoding.utf8)! + @Test func decodingKeyStrategyCustom() throws { + let input = "{\"----hello\":\"test\"}".data(using: .utf8)! let decoder = JSONDecoder() let customKeyConversion = { @Sendable (_ path: [CodingKey]) -> CodingKey in // This converter removes the first 4 characters from the start of all string keys, if it has more than 4 characters @@ -527,31 +535,31 @@ final class JSONEncoderTests : XCTestCase { return _TestKey(stringValue: newString)! } decoder.keyDecodingStrategy = .custom(customKeyConversion) - let result = try! decoder.decode(DecodeMe2.self, from: input) + let result = try decoder.decode(DecodeMe2.self, from: input) - XCTAssertEqual("test", result.hello) + #expect("test" == result.hello) } - func testDecodingDictionaryStringKeyConversionUntouched() { - let input = "{\"leave_me_alone\":\"test\"}".data(using: String._Encoding.utf8)! + @Test func decodingDictionaryStringKeyConversionUntouched() throws { + let input = "{\"leave_me_alone\":\"test\"}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - let result = try! decoder.decode([String: String].self, from: input) + let result = try decoder.decode([String: String].self, from: input) - XCTAssertEqual(["leave_me_alone": "test"], result) + #expect(["leave_me_alone": "test"] == result) } - func testDecodingDictionaryFailureKeyPath() { - let input = "{\"leave_me_alone\":\"test\"}".data(using: String._Encoding.utf8)! + @Test func decodingDictionaryFailureKeyPath() { + let input = "{\"leave_me_alone\":\"test\"}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - do { - _ = try decoder.decode([String: Int].self, from: input) - } catch DecodingError.typeMismatch(_, let context) { - XCTAssertEqual(1, context.codingPath.count) - XCTAssertEqual("leave_me_alone", context.codingPath[0].stringValue) - } catch { - XCTFail("Unexpected error: \(String(describing: error))") + #expect { + try decoder.decode([String: Int].self, from: input) + } throws: { + guard case DecodingError.typeMismatch(_, let context) = $0 else { + return false + } + return (1 == context.codingPath.count) && ("leave_me_alone" == context.codingPath[0].stringValue) } } @@ -567,7 +575,7 @@ final class JSONEncoderTests : XCTestCase { var thisIsCamelCase : String } - func testKeyStrategyDuplicateKeys() { + @Test func keyStrategyDuplicateKeys() throws { // This test is mostly to make sure we don't assert on duplicate keys struct DecodeMe5 : Codable { var oneTwo : String @@ -603,48 +611,44 @@ final class JSONEncoderTests : XCTestCase { // Decoding // This input has a dictionary with two keys, but only one will end up in the container - let input = "{\"unused key 1\":\"test1\",\"unused key 2\":\"test2\"}".data(using: String._Encoding.utf8)! + let input = "{\"unused key 1\":\"test1\",\"unused key 2\":\"test2\"}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .custom(customKeyConversion) - let decodingResult = try! decoder.decode(DecodeMe5.self, from: input) + let decodingResult = try decoder.decode(DecodeMe5.self, from: input) // There will be only one result for oneTwo. - XCTAssertEqual(1, decodingResult.numberOfKeys) + #expect(1 == decodingResult.numberOfKeys) // While the order in which these values should be taken is NOT defined by the JSON spec in any way, the historical behavior has been to select the *first* value for a given key. - XCTAssertEqual(decodingResult.oneTwo, "test1") + #expect(decodingResult.oneTwo == "test1") // Encoding let encoded = DecodeMe5() let encoder = JSONEncoder() encoder.keyEncodingStrategy = .custom(customKeyConversion) - let decodingResultData = try! encoder.encode(encoded) - let decodingResultString = String(bytes: decodingResultData, encoding: String._Encoding.utf8) + let decodingResultData = try encoder.encode(encoded) + let decodingResultString = String(bytes: decodingResultData, encoding: .utf8) // There will be only one value in the result (the second one encoded) - XCTAssertEqual("{\"oneTwo\":\"test2\"}", decodingResultString) + #expect("{\"oneTwo\":\"test2\"}" == decodingResultString) } // MARK: - Encoder Features - func testNestedContainerCodingPaths() { + @Test func nestedContainerCodingPaths() { let encoder = JSONEncoder() - do { - let _ = try encoder.encode(NestedContainersTestType()) - } catch let error as NSError { - XCTFail("Caught error during encoding nested container types: \(error)") + #expect(throws: Never.self) { + try encoder.encode(NestedContainersTestType()) } } - func testSuperEncoderCodingPaths() { + @Test func superEncoderCodingPaths() { let encoder = JSONEncoder() - do { - let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) - } catch let error as NSError { - XCTFail("Caught error during encoding nested container types: \(error)") + #expect(throws: Never.self) { + try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) } } // MARK: - Type coercion - func testTypeCoercion() { + @Test func typeCoercion() { _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self) _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self) _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self) @@ -675,25 +679,19 @@ final class JSONEncoderTests : XCTestCase { _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self) } - func testDecodingConcreteTypeParameter() { + @Test func decodingConcreteTypeParameter() throws { let encoder = JSONEncoder() - guard let json = try? encoder.encode(Employee.testValue) else { - XCTFail("Unable to encode Employee.") - return - } + let json = try encoder.encode(Employee.testValue) let decoder = JSONDecoder() - guard let decoded = try? decoder.decode(Employee.self as Person.Type, from: json) else { - XCTFail("Failed to decode Employee as Person from JSON.") - return - } + let decoded = try decoder.decode(Employee.self as Person.Type, from: json) - expectEqual(type(of: decoded), Employee.self, "Expected decoded value to be of type Employee; got \(type(of: decoded)) instead.") + #expect(type(of: decoded) == Employee.self, "Expected decoded value to be of type Employee; got \(type(of: decoded)) instead.") } // MARK: - Encoder State // SR-6078 - func testEncoderStateThrowOnEncode() { + @Test func encoderStateThrowOnEncode() { struct ReferencingEncoderWrapper : Encodable { let value: T init(_ value: T) { self.value = value } @@ -722,7 +720,7 @@ final class JSONEncoderTests : XCTestCase { _ = try? JSONEncoder().encode(ReferencingEncoderWrapper([Float.infinity])) } - func testEncoderStateThrowOnEncodeCustomDate() { + @Test func encoderStateThrowOnEncodeCustomDate() { // This test is identical to testEncoderStateThrowOnEncode, except throwing via a custom Date closure. struct ReferencingEncoderWrapper : Encodable { let value: T @@ -746,7 +744,7 @@ final class JSONEncoderTests : XCTestCase { _ = try? encoder.encode(ReferencingEncoderWrapper(Date())) } - func testEncoderStateThrowOnEncodeCustomData() { + @Test func encoderStateThrowOnEncodeCustomData() { // This test is identical to testEncoderStateThrowOnEncode, except throwing via a custom Data closure. struct ReferencingEncoderWrapper : Encodable { let value: T @@ -770,7 +768,7 @@ final class JSONEncoderTests : XCTestCase { _ = try? encoder.encode(ReferencingEncoderWrapper(Data())) } - func test_106506794() throws { + @Test func issue106506794() throws { struct Level1: Codable, Equatable { let level2: Level2 @@ -802,25 +800,21 @@ final class JSONEncoderTests : XCTestCase { let value = Level1.init(level2: .init(name: "level2")) let data = try JSONEncoder().encode(value) - do { - let decodedValue = try JSONDecoder().decode(Level1.self, from: data) - XCTAssertEqual(value, decodedValue) - } catch { - XCTFail("Decode should not have failed with error: \(error))") - } + let decodedValue = try JSONDecoder().decode(Level1.self, from: data) + #expect(value == decodedValue) } // MARK: - Decoder State // SR-6048 - func testDecoderStateThrowOnDecode() { + @Test func decoderStateThrowOnDecode() throws { // The container stack here starts as [[1,2,3]]. Attempting to decode as [String] matches the outer layer (Array), and begins decoding the array. // Once Array decoding begins, 1 is pushed onto the container stack ([[1,2,3], 1]), and 1 is attempted to be decoded as String. This throws a .typeMismatch, but the container is not popped off the stack. // When attempting to decode [Int], the container stack is still ([[1,2,3], 1]), and 1 fails to decode as [Int]. - let json = "[1,2,3]".data(using: String._Encoding.utf8)! - let _ = try! JSONDecoder().decode(EitherDecodable<[String], [Int]>.self, from: json) + let json = "[1,2,3]".data(using: .utf8)! + let _ = try JSONDecoder().decode(EitherDecodable<[String], [Int]>.self, from: json) } - func testDecoderStateThrowOnDecodeCustomDate() { + @Test func decoderStateThrowOnDecodeCustomDate() throws { // This test is identical to testDecoderStateThrowOnDecode, except we're going to fail because our closure throws an error, not because we hit a type mismatch. let decoder = JSONDecoder() decoder.dateDecodingStrategy = .custom({ decoder in @@ -828,11 +822,11 @@ final class JSONEncoderTests : XCTestCase { throw CustomError.foo }) - let json = "1".data(using: String._Encoding.utf8)! - let _ = try! decoder.decode(EitherDecodable.self, from: json) + let json = "1".data(using: .utf8)! + let _ = try decoder.decode(EitherDecodable.self, from: json) } - func testDecoderStateThrowOnDecodeCustomData() { + @Test func decoderStateThrowOnDecodeCustomData() throws { // This test is identical to testDecoderStateThrowOnDecode, except we're going to fail because our closure throws an error, not because we hit a type mismatch. let decoder = JSONDecoder() decoder.dataDecodingStrategy = .custom({ decoder in @@ -840,20 +834,20 @@ final class JSONEncoderTests : XCTestCase { throw CustomError.foo }) - let json = "1".data(using: String._Encoding.utf8)! - let _ = try! decoder.decode(EitherDecodable.self, from: json) + let json = "1".data(using: .utf8)! + let _ = try decoder.decode(EitherDecodable.self, from: json) } - func testDecodingFailure() { + @Test func decodingFailure() { struct DecodeFailure : Decodable { var invalid: String } let toDecode = "{\"invalid\": json}"; - _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: String._Encoding.utf8)!) + _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: .utf8)!) } - func testDecodingFailureThrowInInitKeyedContainer() { + @Test func decodingFailureThrowInInitKeyedContainer() { struct DecodeFailure : Decodable { private enum CodingKeys: String, CodingKey { case checkedString @@ -875,10 +869,10 @@ final class JSONEncoderTests : XCTestCase { } let toDecode = "{ \"checkedString\" : \"baz\" }" - _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: String._Encoding.utf8)!) + _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: .utf8)!) } - func testDecodingFailureThrowInInitSingleContainer() { + @Test func decodingFailureThrowInInitSingleContainer() { struct DecodeFailure : Decodable { private enum Error: Swift.Error { case expectedError @@ -896,18 +890,18 @@ final class JSONEncoderTests : XCTestCase { } let toDecode = "{ \"checkedString\" : \"baz\" }" - _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: String._Encoding.utf8)!) + _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: .utf8)!) } - func testInvalidFragment() { + @Test func invalidFragment() { struct DecodeFailure: Decodable { var foo: String } let toDecode = "\"foo" - _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: String._Encoding.utf8)!) + _testDecodeFailure(of: DecodeFailure.self, data: toDecode.data(using: .utf8)!) } - func testRepeatedFailedNilChecks() { + @Test func repeatedFailedNilChecks() { struct RepeatNilCheckDecodable : Decodable { enum Failure : Error { case badNil @@ -951,11 +945,13 @@ final class JSONEncoderTests : XCTestCase { } } } - let json = "[1, 2, 3]".data(using: String._Encoding.utf8)! - XCTAssertNoThrow(try JSONDecoder().decode(RepeatNilCheckDecodable.self, from: json)) + let json = "[1, 2, 3]".data(using: .utf8)! + #expect(throws: Never.self) { + try JSONDecoder().decode(RepeatNilCheckDecodable.self, from: json) + } } - func testDelayedDecoding() throws { + @Test func delayedDecoding() throws { // One variation is deferring the use of a container. struct DelayedDecodable_ContainerVersion : Codable { @@ -989,7 +985,9 @@ final class JSONEncoderTests : XCTestCase { let data = try JSONEncoder().encode(before) let decoded = try JSONDecoder().decode(DelayedDecodable_ContainerVersion.self, from: data) - XCTAssertNoThrow(try decoded.i) + #expect(throws: Never.self) { + try decoded.i + } // The other variant is deferring the use of the *top-level* decoder. This does NOT work for non-top level decoders. struct DelayedDecodable_DecoderVersion : Codable { @@ -1020,29 +1018,25 @@ final class JSONEncoderTests : XCTestCase { } // Reuse the same data. let decoded2 = try JSONDecoder().decode(DelayedDecodable_DecoderVersion.self, from: data) - XCTAssertNoThrow(try decoded2.i) + #expect(throws: Never.self) { + try decoded2.i + } } // MARK: - Helper Functions private var _jsonEmptyDictionary: Data { - return "{}".data(using: String._Encoding.utf8)! + return "{}".data(using: .utf8)! } - private func _testEncodeFailure(of value: T) { - do { - let _ = try JSONEncoder().encode(value) - XCTFail("Encode of top-level \(T.self) was expected to fail.") - } catch { - XCTAssertNotNil(error); + private func _testEncodeFailure(of value: T, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(throws: (any Error).self, "Encode of top-level \(T.self) was expected to fail.", sourceLocation: sourceLocation) { + try JSONEncoder().encode(value) } } - private func _testDecodeFailure(of value: T.Type, data: Data) { - do { - let _ = try JSONDecoder().decode(value, from: data) - XCTFail("Decode of top-level \(value) was expected to fail.") - } catch { - XCTAssertNotNil(error); + private func _testDecodeFailure(of value: T.Type, data: Data, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(throws: (any Error).self, "Decode of top-level \(value) was expected to fail.", sourceLocation: sourceLocation) { + try JSONDecoder().decode(value, from: data) } } @@ -1056,8 +1050,9 @@ final class JSONEncoderTests : XCTestCase { keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy = .useDefaultKeys, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys, nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .throw, - nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .throw) where T : Codable, T : Equatable { - var payload: Data! = nil + nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .throw, + sourceLocation: SourceLocation = #_sourceLocation) where T : Codable, T : Equatable { + var payload: Data do { let encoder = JSONEncoder() encoder.outputFormatting = outputFormatting @@ -1067,13 +1062,14 @@ final class JSONEncoderTests : XCTestCase { encoder.keyEncodingStrategy = keyEncodingStrategy payload = try encoder.encode(value) } catch { - XCTFail("Failed to encode \(T.self) to JSON: \(error)") + Issue.record("Failed to encode \(T.self) to JSON: \(error)", sourceLocation: sourceLocation) + return } if let expectedJSON = json { let expected = String(data: expectedJSON, encoding: .utf8)! let actual = String(data: payload, encoding: .utf8)! - XCTAssertEqual(expected, actual, "Produced JSON not identical to expected JSON.") + #expect(expected == actual, "Produced JSON not identical to expected JSON.", sourceLocation: sourceLocation) } do { @@ -1083,27 +1079,21 @@ final class JSONEncoderTests : XCTestCase { decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy decoder.keyDecodingStrategy = keyDecodingStrategy let decoded = try decoder.decode(T.self, from: payload) - XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.") + #expect(decoded == value, "\(T.self) did not round-trip to an equal value.", sourceLocation: sourceLocation) } catch { - XCTFail("Failed to decode \(T.self) from JSON: \(error)") + Issue.record("Failed to decode \(T.self) from JSON: \(error)", sourceLocation: sourceLocation) } } - private func _testRoundTripTypeCoercionFailure(of value: T, as type: U.Type) where T : Codable, U : Codable { - do { + private func _testRoundTripTypeCoercionFailure(of value: T, as type: U.Type, sourceLocation: SourceLocation = #_sourceLocation) where T : Codable, U : Codable { + #expect(throws: (any Error).self, "Coercion from \(T.self) to \(U.self) was expected to fail.", sourceLocation: sourceLocation) { let data = try JSONEncoder().encode(value) let _ = try JSONDecoder().decode(U.self, from: data) - XCTFail("Coercion from \(T.self) to \(U.self) was expected to fail.") - } catch {} + } } - private func _test(JSONString: String, to object: T) { -#if FOUNDATION_FRAMEWORK - let encs : [String._Encoding] = [.utf8, .utf16BigEndian, .utf16LittleEndian, .utf32BigEndian, .utf32LittleEndian] -#else - // TODO: Reenable other encoding once string.data(using:) is fully implemented. - let encs : [String._Encoding] = [.utf8, .utf16BigEndian, .utf16LittleEndian] -#endif + private func _test(JSONString: String, to object: T, sourceLocation: SourceLocation = #_sourceLocation) { + let encs : [String.Encoding] = [.utf8, .utf16BigEndian, .utf16LittleEndian, .utf32BigEndian, .utf32LittleEndian] let decoder = JSONDecoder() for enc in encs { let data = JSONString.data(using: enc)! @@ -1111,26 +1101,26 @@ final class JSONEncoderTests : XCTestCase { do { parsed = try decoder.decode(T.self, from: data) } catch { - XCTFail("Failed to decode \(JSONString) with encoding \(enc): Error: \(error)") + Issue.record("Failed to decode \(JSONString) with encoding \(enc): Error: \(error)", sourceLocation: sourceLocation) continue } - XCTAssertEqual(object, parsed) + #expect(object == parsed, sourceLocation: sourceLocation) } } - func test_JSONEscapedSlashes() { + @Test func jsonEscapedSlashes() { _test(JSONString: "\"\\/test\\/path\"", to: "/test/path") _test(JSONString: "\"\\\\/test\\\\/path\"", to: "\\/test\\/path") } - func test_JSONEscapedForwardSlashes() { + @Test func jsonEscapedForwardSlashes() { _testRoundTrip(of: ["/":1], expectedJSON: """ {"\\/":1} -""".data(using: String._Encoding.utf8)!) +""".data(using: .utf8)!) } - func test_JSONUnicodeCharacters() { + @Test func jsonUnicodeCharacters() { // UTF8: // E9 96 86 E5 B4 AC EB B0 BA EB 80 AB E9 A2 92 // 閆崬밺뀫颒 @@ -1138,7 +1128,7 @@ final class JSONEncoderTests : XCTestCase { _test(JSONString: "[\"本日\"]", to: ["本日"]) } - func test_JSONUnicodeEscapes() throws { + @Test func jsonUnicodeEscapes() throws { let testCases = [ // e-acute and greater-than-or-equal-to "\"\\u00e9\\u2265\"" : "é≥", @@ -1157,7 +1147,7 @@ final class JSONEncoderTests : XCTestCase { } } - func test_encodingJSONHexUnicodeEscapes() throws { + @Test func encodingJSONHexUnicodeEscapes() throws { let testCases = [ "\u{0001}\u{0002}\u{0003}": "\"\\u0001\\u0002\\u0003\"", "\u{0010}\u{0018}\u{001f}": "\"\\u0010\\u0018\\u001f\"", @@ -1167,58 +1157,58 @@ final class JSONEncoderTests : XCTestCase { } } - func test_JSONBadUnicodeEscapes() { - let badCases = ["\\uD834", "\\uD834hello", "hello\\uD834", "\\uD834\\u1221", "\\uD8", "\\uD834x\\uDD1E"] - for str in badCases { - let data = str.data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try JSONDecoder().decode(String.self, from: data)) + @Test(arguments: [ + "\\uD834", "\\uD834hello", "hello\\uD834", "\\uD834\\u1221", "\\uD8", "\\uD834x\\uDD1E" + ]) + func jsonBadUnicodeEscapes(str: String) { + let data = str.data(using: .utf8)! + #expect(throws: (any Error).self) { + try JSONDecoder().decode(String.self, from: data) } } - func test_nullByte() throws { + @Test func nullByte() throws { let string = "abc\u{0000}def" let encoder = JSONEncoder() let decoder = JSONDecoder() let data = try encoder.encode([string]) let decoded = try decoder.decode([String].self, from: data) - XCTAssertEqual([string], decoded) + #expect([string] == decoded) let data2 = try encoder.encode([string:string]) let decoded2 = try decoder.decode([String:String].self, from: data2) - XCTAssertEqual([string:string], decoded2) + #expect([string:string] == decoded2) struct Container: Codable { let s: String } let data3 = try encoder.encode(Container(s: string)) let decoded3 = try decoder.decode(Container.self, from: data3) - XCTAssertEqual(decoded3.s, string) + #expect(decoded3.s == string) } - func test_superfluouslyEscapedCharacters() { + @Test func superfluouslyEscapedCharacters() { let json = "[\"\\h\\e\\l\\l\\o\"]" - XCTAssertThrowsError(try JSONDecoder().decode([String].self, from: json.data(using: String._Encoding.utf8)!)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode([String].self, from: json.data(using: .utf8)!) + } } - func test_equivalentUTF8Sequences() { + @Test func equivalentUTF8Sequences() throws { let json = """ { "caf\\u00e9" : true, "cafe\\u0301" : false } -""".data(using: String._Encoding.utf8)! +""".data(using: .utf8)! - do { - let dict = try JSONDecoder().decode([String:Bool].self, from: json) - XCTAssertEqual(dict.count, 1) - } catch { - XCTFail("Unexpected error: \(error)") - } + let dict = try JSONDecoder().decode([String:Bool].self, from: json) + #expect(dict.count == 1) } - func test_JSONControlCharacters() { + @Test func jsonControlCharacters() { let array = [ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", @@ -1235,7 +1225,7 @@ final class JSONEncoderTests : XCTestCase { } } - func test_JSONNumberFragments() { + @Test func jsonNumberFragments() { let array = ["0 ", "1.0 ", "0.1 ", "1e3 ", "-2.01e-3 ", "0", "1.0", "1e3", "-2.01e-3", "0e-10"] let expected = [0, 1.0, 0.1, 1000, -0.00201, 0, 1.0, 1000, -0.00201, 0] for (json, expected) in zip(array, expected) { @@ -1243,55 +1233,57 @@ final class JSONEncoderTests : XCTestCase { } } - func test_invalidJSONNumbersFailAsExpected() { + @Test func invalidJSONNumbersFailAsExpected() { let array = ["0.", "1e ", "-2.01e- ", "+", "2.01e-1234", "+2.0q", "2s", "NaN", "nan", "Infinity", "inf", "-", "0x42", "1.e2"] for json in array { - let data = json.data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try JSONDecoder().decode(Float.self, from: data), "Expected error for input \"\(json)\"") + let data = json.data(using: .utf8)! + #expect(throws: (any Error).self, "Expected error for input \"\(json)\"") { + _ = try JSONDecoder().decode(Float.self, from: data) + } } } - func _checkExpectedThrownDataCorruptionUnderlyingError(contains substring: String, closure: () throws -> Void) { + func _checkExpectedThrownDataCorruptionUnderlyingError(contains substring: String, sourceLocation: SourceLocation = #_sourceLocation, closure: () throws -> Void) { do { try closure() - XCTFail("Expected failure containing string: \"\(substring)\"") + Issue.record("Expected failure containing string: \"\(substring)\"", sourceLocation: sourceLocation) } catch let error as DecodingError { guard case let .dataCorrupted(context) = error else { - XCTFail("Unexpected DecodingError type: \(error)") + Issue.record("Unexpected DecodingError type: \(error)", sourceLocation: sourceLocation) return } #if FOUNDATION_FRAMEWORK let nsError = context.underlyingError! as NSError - XCTAssertTrue(nsError.debugDescription.contains(substring), "Description \"\(nsError.debugDescription)\" doesn't contain substring \"\(substring)\"") + #expect(nsError.debugDescription.contains(substring), "Description \"\(nsError.debugDescription)\" doesn't contain substring \"\(substring)\"", sourceLocation: sourceLocation) #endif } catch { - XCTFail("Unexpected error type: \(error)") + Issue.record("Unexpected error type: \(error)", sourceLocation: sourceLocation) } } - func test_topLevelFragmentsWithGarbage() { + @Test func topLevelFragmentsWithGarbage() { _checkExpectedThrownDataCorruptionUnderlyingError(contains: "Unexpected character") { - let _ = try JSONDecoder().decode(Bool.self, from: "tru_".data(using: String._Encoding.utf8)!) - let _ = try json5Decoder.decode(Bool.self, from: "tru_".data(using: String._Encoding.utf8)!) + let _ = try JSONDecoder().decode(Bool.self, from: "tru_".data(using: .utf8)!) + let _ = try json5Decoder.decode(Bool.self, from: "tru_".data(using: .utf8)!) } _checkExpectedThrownDataCorruptionUnderlyingError(contains: "Unexpected character") { - let _ = try JSONDecoder().decode(Bool.self, from: "fals_".data(using: String._Encoding.utf8)!) - let _ = try json5Decoder.decode(Bool.self, from: "fals_".data(using: String._Encoding.utf8)!) + let _ = try JSONDecoder().decode(Bool.self, from: "fals_".data(using: .utf8)!) + let _ = try json5Decoder.decode(Bool.self, from: "fals_".data(using: .utf8)!) } _checkExpectedThrownDataCorruptionUnderlyingError(contains: "Unexpected character") { - let _ = try JSONDecoder().decode(Bool?.self, from: "nul_".data(using: String._Encoding.utf8)!) - let _ = try json5Decoder.decode(Bool?.self, from: "nul_".data(using: String._Encoding.utf8)!) + let _ = try JSONDecoder().decode(Bool?.self, from: "nul_".data(using: .utf8)!) + let _ = try json5Decoder.decode(Bool?.self, from: "nul_".data(using: .utf8)!) } } - func test_topLevelNumberFragmentsWithJunkDigitCharacters() { - let fullData = "3.141596".data(using: String._Encoding.utf8)! + @Test func topLevelNumberFragmentsWithJunkDigitCharacters() throws { + let fullData = "3.141596".data(using: .utf8)! let partialData = fullData[0..<4] - XCTAssertEqual(3.14, try JSONDecoder().decode(Double.self, from: partialData)) + #expect(try 3.14 == JSONDecoder().decode(Double.self, from: partialData)) } - func test_depthTraversal() { + @Test func depthTraversal() { struct SuperNestedArray : Decodable { init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() @@ -1305,44 +1297,50 @@ final class JSONEncoderTests : XCTestCase { let jsonGood = String(repeating: "[", count: MAX_DEPTH / 2) + String(repeating: "]", count: MAX_DEPTH / 2) let jsonBad = String(repeating: "[", count: MAX_DEPTH + 1) + String(repeating: "]", count: MAX_DEPTH + 1) - XCTAssertNoThrow(try JSONDecoder().decode(SuperNestedArray.self, from: jsonGood.data(using: String._Encoding.utf8)!)) - XCTAssertThrowsError(try JSONDecoder().decode(SuperNestedArray.self, from: jsonBad.data(using: String._Encoding.utf8)!)) + #expect(throws: Never.self) { + try JSONDecoder().decode(SuperNestedArray.self, from: jsonGood.data(using: .utf8)!) + } + #expect(throws: (any Error).self) { + try JSONDecoder().decode(SuperNestedArray.self, from: jsonBad.data(using: .utf8)!) + } } - func test_JSONPermitsTrailingCommas() { + @Test func jsonPermitsTrailingCommas() throws { // Trailing commas aren't valid JSON and should never be emitted, but are syntactically unambiguous and are allowed by // most parsers for ease of use. let json = "{\"key\" : [ true, ],}" - let data = json.data(using: String._Encoding.utf8)! + let data = json.data(using: .utf8)! - let result = try! JSONDecoder().decode([String:[Bool]].self, from: data) + let result = try JSONDecoder().decode([String:[Bool]].self, from: data) let expected = ["key" : [true]] - XCTAssertEqual(result, expected) + #expect(result == expected) } - func test_whitespaceOnlyData() { - let data = " ".data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try JSONDecoder().decode(Int.self, from: data)) + @Test func whitespaceOnlyData() { + let data = " ".data(using: .utf8)! + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Int.self, from: data) + } } - func test_smallFloatNumber() { + @Test func smallFloatNumber() { _testRoundTrip(of: [["magic_number" : 7.45673334164903e-115]]) } - func test_largeIntegerNumber() { + @Test func largeIntegerNumber() throws { let num : UInt64 = 6032314514195021674 let json = "{\"a\":\(num)}" - let data = json.data(using: String._Encoding.utf8)! + let data = json.data(using: .utf8)! - let result = try! JSONDecoder().decode([String:UInt64].self, from: data) - let number = result["a"]! - XCTAssertEqual(number, num) + let result = try JSONDecoder().decode([String:UInt64].self, from: data) + let number = try #require(result["a"]) + #expect(number == num) } - func test_largeIntegerNumberIsNotRoundedToNearestDoubleWhenDecodingAsAnInteger() { - XCTAssertEqual(Double(sign: .plus, exponent: 63, significand: 1).ulp, 2048) - XCTAssertEqual(Double(sign: .plus, exponent: 64, significand: 1).ulp, 4096) + @Test func largeIntegerNumberIsNotRoundedToNearestDoubleWhenDecodingAsAnInteger() { + #expect(Double(sign: .plus, exponent: 63, significand: 1).ulp == 2048) + #expect(Double(sign: .plus, exponent: 64, significand: 1).ulp == 4096) let int64s: [(String, Int64?)] = [ ("-9223372036854776833", nil), // -2^63 - 1025 (Double: -2^63 - 2048) @@ -1373,18 +1371,18 @@ final class JSONEncoderTests : XCTestCase { decoder.allowsJSON5 = json5 for (json, value) in int64s { - let result = try? decoder.decode(Int64.self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(result, value, "Unexpected \(decoder) result for input \"\(json)\"") + let result = try? decoder.decode(Int64.self, from: json.data(using: .utf8)!) + #expect(result == value, "Unexpected \(decoder) result for input \"\(json)\"") } for (json, value) in uint64s { - let result = try? decoder.decode(UInt64.self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(result, value, "Unexpected \(decoder) result for input \"\(json)\"") + let result = try? decoder.decode(UInt64.self, from: json.data(using: .utf8)!) + #expect(result == value, "Unexpected \(decoder) result for input \"\(json)\"") } } } - func test_roundTrippingExtremeValues() { + @Test func roundTrippingExtremeValues() { struct Numbers : Codable, Equatable { let floats : [Float] let doubles : [Double] @@ -1393,32 +1391,30 @@ final class JSONEncoderTests : XCTestCase { _testRoundTrip(of: testValue) } - func test_roundTrippingInt128() { - let values = [ - Int128.min, - Int128.min + 1, - -0x1_0000_0000_0000_0000, - 0x0_8000_0000_0000_0000, - -1, - 0, - 0x7fff_ffff_ffff_ffff, - 0x8000_0000_0000_0000, - 0xffff_ffff_ffff_ffff, - 0x1_0000_0000_0000_0000, - .max - ] - for i128 in values { - _testRoundTrip(of: i128) - } + @Test(arguments: [ + Int128.min, + Int128.min + 1, + -0x1_0000_0000_0000_0000, + 0x0_8000_0000_0000_0000, + -1, + 0, + 0x7fff_ffff_ffff_ffff, + 0x8000_0000_0000_0000, + 0xffff_ffff_ffff_ffff, + 0x1_0000_0000_0000_0000, + .max + ]) + func roundTrippingInt128(i128: Int128) { + _testRoundTrip(of: i128) } - func test_Int128SlowPath() { + @Test func int128SlowPath() throws { let decoder = JSONDecoder() let work: [Int128] = [18446744073709551615, -18446744073709551615] for value in work { // force the slow-path by appending ".0" - let json = "\(value).0".data(using: String._Encoding.utf8)! - XCTAssertEqual(value, try? decoder.decode(Int128.self, from: json)) + let json = "\(value).0".data(using: .utf8)! + #expect(try value == decoder.decode(Int128.self, from: json)) } // These should work, but making them do so probably requires // rewriting the slow path to use a dedicated parser. For now, @@ -1429,35 +1425,35 @@ final class JSONEncoderTests : XCTestCase { ] for value in shouldWorkButDontYet { // force the slow-path by appending ".0" - let json = "\(value).0".data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try decoder.decode(Int128.self, from: json)) + let json = "\(value).0".data(using: .utf8)! + #expect(throws: (any Error).self) { + try decoder.decode(Int128.self, from: json) + } } } - func test_roundTrippingUInt128() { - let values = [ - UInt128.zero, - 1, - 0x0000_0000_0000_0000_7fff_ffff_ffff_ffff, - 0x0000_0000_0000_0000_8000_0000_0000_0000, - 0x0000_0000_0000_0000_ffff_ffff_ffff_ffff, - 0x0000_0000_0000_0001_0000_0000_0000_0000, - 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff, - 0x8000_0000_0000_0000_0000_0000_0000_0000, - .max - ] - for u128 in values { - _testRoundTrip(of: u128) - } + @Test(arguments: [ + UInt128.zero, + 1, + 0x0000_0000_0000_0000_7fff_ffff_ffff_ffff, + 0x0000_0000_0000_0000_8000_0000_0000_0000, + 0x0000_0000_0000_0000_ffff_ffff_ffff_ffff, + 0x0000_0000_0000_0001_0000_0000_0000_0000, + 0x7fff_ffff_ffff_ffff_ffff_ffff_ffff_ffff, + 0x8000_0000_0000_0000_0000_0000_0000_0000, + .max + ]) + func roundTrippingUInt128(u128: UInt128) { + _testRoundTrip(of: u128) } - func test_UInt128SlowPath() { + @Test func uint128SlowPath() throws { let decoder = JSONDecoder() let work: [UInt128] = [18446744073709551615] for value in work { // force the slow-path by appending ".0" - let json = "\(value).0".data(using: String._Encoding.utf8)! - XCTAssertEqual(value, try? decoder.decode(UInt128.self, from: json)) + let json = "\(value).0".data(using: .utf8)! + #expect(try value == decoder.decode(UInt128.self, from: json)) } // These should work, but making them do so probably requires // rewriting the slow path to use a dedicated parser. For now, @@ -1468,12 +1464,14 @@ final class JSONEncoderTests : XCTestCase { ] for value in shouldWorkButDontYet { // force the slow-path by appending ".0" - let json = "\(value).0".data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try decoder.decode(UInt128.self, from: json)) + let json = "\(value).0".data(using: .utf8)! + #expect(throws: (any Error).self) { + try decoder.decode(UInt128.self, from: json) + } } } - func test_roundTrippingDoubleValues() { + @Test func roundTrippingDoubleValues() { struct Numbers : Codable, Equatable { let doubles : [String:Double] let decimals : [String:Decimal] @@ -1508,12 +1506,14 @@ final class JSONEncoderTests : XCTestCase { _testRoundTrip(of: testValue) } - func test_decodeLargeDoubleAsInteger() { - let data = try! JSONEncoder().encode(Double.greatestFiniteMagnitude) - XCTAssertThrowsError(try JSONDecoder().decode(UInt64.self, from: data)) + @Test func decodeLargeDoubleAsInteger() throws { + let data = try JSONEncoder().encode(Double.greatestFiniteMagnitude) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(UInt64.self, from: data) + } } - func test_localeDecimalPolicyIndependence() { + @Test func localeDecimalPolicyIndependence() throws { var currentLocale: UnsafeMutablePointer? = nil if let localePtr = setlocale(LC_ALL, nil) { currentLocale = strdup(localePtr) @@ -1528,114 +1528,110 @@ final class JSONEncoderTests : XCTestCase { let orig = ["decimalValue" : 1.1] - do { - setlocale(LC_ALL, "fr_FR") - let data = try JSONEncoder().encode(orig) + setlocale(LC_ALL, "fr_FR") + let data = try JSONEncoder().encode(orig) #if os(Windows) - setlocale(LC_ALL, "en_US") + setlocale(LC_ALL, "en_US") #else - setlocale(LC_ALL, "en_US_POSIX") + setlocale(LC_ALL, "en_US_POSIX") #endif - let decoded = try JSONDecoder().decode(type(of: orig).self, from: data) + let decoded = try JSONDecoder().decode(type(of: orig).self, from: data) - XCTAssertEqual(orig, decoded) - } catch { - XCTFail("Error: \(error)") - } + #expect(orig == decoded) } - func test_whitespace() { + @Test func whitespace() { let tests : [(json: String, expected: [String:Bool])] = [ ("{\"v\"\n : true}", ["v":true]), ("{\"v\"\r\n : true}", ["v":true]), ("{\"v\"\r : true}", ["v":true]) ] for test in tests { - let data = test.json.data(using: String._Encoding.utf8)! + let data = test.json.data(using: .utf8)! let decoded = try! JSONDecoder().decode([String:Bool].self, from: data) - XCTAssertEqual(test.expected, decoded) + #expect(test.expected == decoded) } } - func test_assumesTopLevelDictionary() { + @Test func assumesTopLevelDictionary() throws { let decoder = JSONDecoder() decoder.assumesTopLevelDictionary = true let json = "\"x\" : 42" - do { - let result = try decoder.decode([String:Int].self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(result, ["x" : 42]) - } catch { - XCTFail("Error thrown while decoding assumed top-level dictionary: \(error)") - } + var result = try decoder.decode([String:Int].self, from: json.data(using: .utf8)!) + #expect(result == ["x" : 42]) let jsonWithBraces = "{\"x\" : 42}" - do { - let result = try decoder.decode([String:Int].self, from: jsonWithBraces.data(using: String._Encoding.utf8)!) - XCTAssertEqual(result, ["x" : 42]) - } catch { - XCTFail("Error thrown while decoding assumed top-level dictionary: \(error)") - } + result = try decoder.decode([String:Int].self, from: jsonWithBraces.data(using: .utf8)!) + #expect(result == ["x" : 42]) - do { - let result = try decoder.decode([String:Int].self, from: Data()) - XCTAssertEqual(result, [:]) - } catch { - XCTFail("Error thrown while decoding empty assumed top-level dictionary: \(error)") - } + result = try decoder.decode([String:Int].self, from: Data()) + #expect(result == [:]) let jsonWithEndBraceOnly = "\"x\" : 42}" - XCTAssertThrowsError(try decoder.decode([String:Int].self, from: jsonWithEndBraceOnly.data(using: String._Encoding.utf8)!)) + #expect(throws: (any Error).self) { + try decoder.decode([String:Int].self, from: jsonWithEndBraceOnly.data(using: .utf8)!) + } let jsonWithStartBraceOnly = "{\"x\" : 42" - XCTAssertThrowsError(try decoder.decode([String:Int].self, from: jsonWithStartBraceOnly.data(using: String._Encoding.utf8)!)) + #expect(throws: (any Error).self) { + try decoder.decode([String:Int].self, from: jsonWithStartBraceOnly.data(using: .utf8)!) + } } - func test_BOMPrefixes() { + @Test func bomPrefixes() throws { let json = "\"👍🏻\"" let decoder = JSONDecoder() // UTF-8 BOM let utf8_BOM = Data([0xEF, 0xBB, 0xBF]) - XCTAssertEqual("👍🏻", try decoder.decode(String.self, from: utf8_BOM + json.data(using: String._Encoding.utf8)!)) + #expect(try "👍🏻" == decoder.decode(String.self, from: utf8_BOM + json.data(using: .utf8)!)) // UTF-16 BE let utf16_BE_BOM = Data([0xFE, 0xFF]) - XCTAssertEqual("👍🏻", try decoder.decode(String.self, from: utf16_BE_BOM + json.data(using: String._Encoding.utf16BigEndian)!)) + #expect(try "👍🏻" == decoder.decode(String.self, from: utf16_BE_BOM + json.data(using: .utf16BigEndian)!)) // UTF-16 LE let utf16_LE_BOM = Data([0xFF, 0xFE]) - XCTAssertEqual("👍🏻", try decoder.decode(String.self, from: utf16_LE_BOM + json.data(using: String._Encoding.utf16LittleEndian)!)) + #expect(try "👍🏻" == decoder.decode(String.self, from: utf16_LE_BOM + json.data(using: .utf16LittleEndian)!)) // UTF-32 BE let utf32_BE_BOM = Data([0x0, 0x0, 0xFE, 0xFF]) - XCTAssertEqual("👍🏻", try decoder.decode(String.self, from: utf32_BE_BOM + json.data(using: String._Encoding.utf32BigEndian)!)) + #expect(try "👍🏻" == decoder.decode(String.self, from: utf32_BE_BOM + json.data(using: .utf32BigEndian)!)) // UTF-32 LE let utf32_LE_BOM = Data([0xFE, 0xFF, 0, 0]) - XCTAssertEqual("👍🏻", try decoder.decode(String.self, from: utf32_LE_BOM + json.data(using: String._Encoding.utf32LittleEndian)!)) + #expect(try "👍🏻" == decoder.decode(String.self, from: utf32_LE_BOM + json.data(using: .utf32LittleEndian)!)) // Try some mismatched BOMs - XCTAssertThrowsError(try decoder.decode(String.self, from: utf32_LE_BOM + json.data(using: String._Encoding.utf32BigEndian)!)) + #expect(throws: (any Error).self) { + try decoder.decode(String.self, from: utf32_LE_BOM + json.data(using: .utf32BigEndian)!) + } - XCTAssertThrowsError(try decoder.decode(String.self, from: utf16_BE_BOM + json.data(using: String._Encoding.utf32LittleEndian)!)) + #expect(throws: (any Error).self) { + try decoder.decode(String.self, from: utf16_BE_BOM + json.data(using: .utf32LittleEndian)!) + } - XCTAssertThrowsError(try decoder.decode(String.self, from: utf8_BOM + json.data(using: String._Encoding.utf16BigEndian)!)) + #expect(throws: (any Error).self) { + try decoder.decode(String.self, from: utf8_BOM + json.data(using: .utf16BigEndian)!) + } } - func test_invalidKeyUTF8() { + @Test func invalidKeyUTF8() { // {"key[255]":"value"} // The invalid UTF-8 byte sequence in the key should trigger a thrown error, not a crash. let data = Data([123, 34, 107, 101, 121, 255, 34, 58, 34, 118, 97, 108, 117, 101, 34, 125]) struct Example: Decodable { let key: String } - XCTAssertThrowsError(try JSONDecoder().decode(Example.self, from: data)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Example.self, from: data) + } } - func test_valueNotFoundError() { + @Test func valueNotFoundError() { struct ValueNotFound : Decodable { let a: Bool let nope: String? @@ -1657,28 +1653,36 @@ final class JSONEncoderTests : XCTestCase { } } } - let json = "{\"a\":true}".data(using: String._Encoding.utf8)! + let json = "{\"a\":true}".data(using: .utf8)! // The expected valueNotFound error is swalled by the init(from:) implementation. - XCTAssertNoThrow(try JSONDecoder().decode(ValueNotFound.self, from: json)) + #expect(throws: Never.self) { + try JSONDecoder().decode(ValueNotFound.self, from: json) + } } - func test_infiniteDate() { + @Test func infiniteDate() { let date = Date(timeIntervalSince1970: .infinity) let encoder = JSONEncoder() encoder.dateEncodingStrategy = .deferredToDate - XCTAssertThrowsError(try encoder.encode([date])) + #expect(throws: (any Error).self) { + try encoder.encode([date]) + } encoder.dateEncodingStrategy = .secondsSince1970 - XCTAssertThrowsError(try encoder.encode([date])) + #expect(throws: (any Error).self) { + try encoder.encode([date]) + } encoder.dateEncodingStrategy = .millisecondsSince1970 - XCTAssertThrowsError(try encoder.encode([date])) + #expect(throws: (any Error).self) { + try encoder.encode([date]) + } } - func test_typeEncodesNothing() { + @Test func typeEncodesNothing() { struct EncodesNothing : Encodable { func encode(to encoder: Encoder) throws { // Intentionally nothing. @@ -1686,18 +1690,20 @@ final class JSONEncoderTests : XCTestCase { } let enc = JSONEncoder() - XCTAssertThrowsError(try enc.encode(EncodesNothing())) + #expect(throws: (any Error).self) { + try enc.encode(EncodesNothing()) + } // Unknown if the following behavior is strictly correct, but it's what the prior implementation does, so this test exists to make sure we maintain compatibility. let arrayData = try! enc.encode([EncodesNothing()]) - XCTAssertEqual("[{}]", String(data: arrayData, encoding: .utf8)) + #expect("[{}]" == String(data: arrayData, encoding: .utf8)) let objectData = try! enc.encode(["test" : EncodesNothing()]) - XCTAssertEqual("{\"test\":{}}", String(data: objectData, encoding: .utf8)) + #expect("{\"test\":{}}" == String(data: objectData, encoding: .utf8)) } - func test_superEncoders() { + @Test func superEncoders() throws { struct SuperEncoding : Encodable { enum CodingKeys: String, CodingKey { case firstSuper @@ -1731,16 +1737,16 @@ final class JSONEncoderTests : XCTestCase { // NOTE!!! At present, the order in which the values in the unkeyed container's superEncoders above get inserted into the resulting array depends on the order in which the superEncoders are deinit'd!! This can result in some very unexpected results, and this pattern is not recommended. This test exists just to verify compatibility. } } - let data = try! JSONEncoder().encode(SuperEncoding()) + let data = try JSONEncoder().encode(SuperEncoding()) let string = String(data: data, encoding: .utf8)! - XCTAssertTrue(string.contains("\"firstSuper\":\"First\"")) - XCTAssertTrue(string.contains("\"secondSuper\":\"Second\"")) - XCTAssertTrue(string.contains("[0,\"First\",\"Second\",42]")) - XCTAssertTrue(string.contains("{\"direct\":\"super\"}")) + #expect(string.contains("\"firstSuper\":\"First\"")) + #expect(string.contains("\"secondSuper\":\"Second\"")) + #expect(string.contains("[0,\"First\",\"Second\",42]")) + #expect(string.contains("{\"direct\":\"super\"}")) } - func testRedundantKeys() { + @Test func redundantKeys() throws { // Last encoded key wins. struct RedundantEncoding : Encodable { @@ -1773,26 +1779,26 @@ final class JSONEncoderTests : XCTestCase { } } } - var data = try! JSONEncoder().encode(RedundantEncoding(replacedType: .value, useSuperEncoder: false)) - XCTAssertEqual(String(data: data, encoding: .utf8), ("{\"key\":42}")) + var data = try JSONEncoder().encode(RedundantEncoding(replacedType: .value, useSuperEncoder: false)) + #expect(String(data: data, encoding: .utf8) == ("{\"key\":42}")) - data = try! JSONEncoder().encode(RedundantEncoding(replacedType: .value, useSuperEncoder: true)) - XCTAssertEqual(String(data: data, encoding: .utf8), ("{\"key\":42}")) + data = try JSONEncoder().encode(RedundantEncoding(replacedType: .value, useSuperEncoder: true)) + #expect(String(data: data, encoding: .utf8) == ("{\"key\":42}")) - data = try! JSONEncoder().encode(RedundantEncoding(replacedType: .keyedContainer, useSuperEncoder: false)) - XCTAssertEqual(String(data: data, encoding: .utf8), ("{\"key\":42}")) + data = try JSONEncoder().encode(RedundantEncoding(replacedType: .keyedContainer, useSuperEncoder: false)) + #expect(String(data: data, encoding: .utf8) == ("{\"key\":42}")) - data = try! JSONEncoder().encode(RedundantEncoding(replacedType: .keyedContainer, useSuperEncoder: true)) - XCTAssertEqual(String(data: data, encoding: .utf8), ("{\"key\":42}")) + data = try JSONEncoder().encode(RedundantEncoding(replacedType: .keyedContainer, useSuperEncoder: true)) + #expect(String(data: data, encoding: .utf8) == ("{\"key\":42}")) - data = try! JSONEncoder().encode(RedundantEncoding(replacedType: .unkeyedContainer, useSuperEncoder: false)) - XCTAssertEqual(String(data: data, encoding: .utf8), ("{\"key\":42}")) + data = try JSONEncoder().encode(RedundantEncoding(replacedType: .unkeyedContainer, useSuperEncoder: false)) + #expect(String(data: data, encoding: .utf8) == ("{\"key\":42}")) - data = try! JSONEncoder().encode(RedundantEncoding(replacedType: .unkeyedContainer, useSuperEncoder: true)) - XCTAssertEqual(String(data: data, encoding: .utf8), ("{\"key\":42}")) + data = try JSONEncoder().encode(RedundantEncoding(replacedType: .unkeyedContainer, useSuperEncoder: true)) + #expect(String(data: data, encoding: .utf8) == ("{\"key\":42}")) } - func test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip() throws { + @Test func SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip() throws { struct Something: Codable { struct Key: Codable, Hashable { var x: String @@ -1822,11 +1828,11 @@ final class JSONEncoderTests : XCTestCase { let toEncode = Something(dict: [:]) let data = try JSONEncoder().encode(toEncode) let result = try JSONDecoder().decode(Something.self, from: data) - XCTAssertEqual(result.dict.count, 0) + #expect(result.dict.count == 0) } - // None of these tests can be run in our automatic test suites right now, because they are expected to hit a preconditionFailure. They can only be verified manually. - func disabled_testPreconditionFailuresForContainerReplacement() { + #if FOUNDATION_EXIT_TESTS + @Test func preconditionFailuresForContainerReplacement() async { struct RedundantEncoding : Encodable { enum Subcase { case replaceValueWithKeyedContainer @@ -1860,36 +1866,45 @@ final class JSONEncoderTests : XCTestCase { } } } - let _ = try! JSONEncoder().encode(RedundantEncoding(subcase: .replaceValueWithKeyedContainer)) -// let _ = try! JSONEncoder().encode(RedundantEncoding(subcase: .replaceValueWithUnkeyedContainer)) -// let _ = try! JSONEncoder().encode(RedundantEncoding(subcase: .replaceKeyedContainerWithUnkeyed)) -// let _ = try! JSONEncoder().encode(RedundantEncoding(subcase: .replaceUnkeyedContainerWithKeyed)) + await #expect(processExitsWith: .failure) { + let _ = try JSONEncoder().encode(RedundantEncoding(subcase: .replaceValueWithKeyedContainer)) + } + await #expect(processExitsWith: .failure) { + let _ = try JSONEncoder().encode(RedundantEncoding(subcase: .replaceValueWithUnkeyedContainer)) + } + await #expect(processExitsWith: .failure) { + let _ = try JSONEncoder().encode(RedundantEncoding(subcase: .replaceKeyedContainerWithUnkeyed)) + } + await #expect(processExitsWith: .failure) { + let _ = try JSONEncoder().encode(RedundantEncoding(subcase: .replaceUnkeyedContainerWithKeyed)) + } } + #endif - func test_decodeIfPresent() throws { + @Test func decodeIfPresent() throws { let emptyDictJSON = try JSONEncoder().encode(DecodeIfPresentAllTypes.allNils) let testEmptyDict = try JSONDecoder().decode(DecodeIfPresentAllTypes.self, from: emptyDictJSON) - XCTAssertEqual(testEmptyDict, .allNils) + #expect(testEmptyDict == .allNils) let allNullDictJSON = try JSONEncoder().encode(DecodeIfPresentAllTypes.allNils) let testAllNullDict = try JSONDecoder().decode(DecodeIfPresentAllTypes.self, from: allNullDictJSON) - XCTAssertEqual(testAllNullDict, .allNils) + #expect(testAllNullDict == .allNils) let allOnesDictJSON = try JSONEncoder().encode(DecodeIfPresentAllTypes.allOnes) let testAllOnesDict = try JSONDecoder().decode(DecodeIfPresentAllTypes.self, from: allOnesDictJSON) - XCTAssertEqual(testAllOnesDict, .allOnes) + #expect(testAllOnesDict == .allOnes) let emptyArrayJSON = try JSONEncoder().encode(DecodeIfPresentAllTypes.allNils) let testEmptyArray = try JSONDecoder().decode(DecodeIfPresentAllTypes.self, from: emptyArrayJSON) - XCTAssertEqual(testEmptyArray, .allNils) + #expect(testEmptyArray == .allNils) let allNullArrayJSON = try JSONEncoder().encode(DecodeIfPresentAllTypes.allNils) let testAllNullArray = try JSONDecoder().decode(DecodeIfPresentAllTypes.self, from: allNullArrayJSON) - XCTAssertEqual(testAllNullArray, .allNils) + #expect(testAllNullArray == .allNils) let allOnesArrayJSON = try JSONEncoder().encode(DecodeIfPresentAllTypes.allOnes) let testAllOnesArray = try JSONDecoder().decode(DecodeIfPresentAllTypes.self, from: allOnesArrayJSON) - XCTAssertEqual(testAllOnesArray, .allOnes) + #expect(testAllOnesArray == .allOnes) } } @@ -1901,7 +1916,7 @@ extension JSONEncoderTests { return decoder } - func test_json5Numbers() { + @Test func json5Numbers() { let decoder = json5Decoder let successfulIntegers: [(String,Int)] = [ @@ -1929,11 +1944,9 @@ extension JSONEncoderTests { ("1E+02", 100), ] for (json, expected) in successfulIntegers { - do { - let val = try decoder.decode(Int.self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(val, expected, "Wrong value parsed from input \"\(json)\"") - } catch { - XCTFail("Error when parsing input \"\(json)\": \(error)") + #expect(throws: Never.self, "Error when parsing input \"\(json)\"") { + let val = try decoder.decode(Int.self, from: json.data(using: .utf8)!) + #expect(val == expected, "Wrong value parsed from input \"\(json)\"") } } @@ -1973,15 +1986,13 @@ extension JSONEncoderTests { ("+0X1f", Double(+0x1f)), ] for (json, expected) in successfulDoubles { - do { - let val = try decoder.decode(Double.self, from: json.data(using: String._Encoding.utf8)!) + #expect(throws: Never.self, "Error when parsing input \"\(json)\"") { + let val = try decoder.decode(Double.self, from: json.data(using: .utf8)!) if expected.isNaN { - XCTAssertTrue(val.isNaN, "Wrong value \(val) parsed from input \"\(json)\"") + #expect(val.isNaN, "Wrong value \(val) parsed from input \"\(json)\"") } else { - XCTAssertEqual(val, expected, "Wrong value parsed from input \"\(json)\"") + #expect(val == expected, "Wrong value parsed from input \"\(json)\"") } - } catch { - XCTFail("Error when parsing input \"\(json)\": \(error)") } } @@ -2007,10 +2018,9 @@ extension JSONEncoderTests { "-1E ", ] for json in unsuccessfulIntegers { - do { - let _ = try decoder.decode(Int.self, from: json.data(using: String._Encoding.utf8)!) - XCTFail("Expected failure for input \"\(json)\"") - } catch { } + #expect(throws: (any Error).self, "Expected failure for input \"\(json)\"") { + try decoder.decode(Int.self, from: json.data(using: .utf8)!) + } } let unsuccessfulDoubles = [ @@ -2038,14 +2048,13 @@ extension JSONEncoderTests { "0xFFFFFFFFFFFFFFFFFFFFFF", ]; for json in unsuccessfulDoubles { - do { - let _ = try decoder.decode(Double.self, from: json.data(using: String._Encoding.utf8)!) - XCTFail("Expected failure for input \"\(json)\"") - } catch { } + #expect(throws: (any Error).self, "Expected failure for input \"\(json)\"") { + try decoder.decode(Double.self, from: json.data(using: .utf8)!) + } } } - func test_json5Null() { + @Test func json5Null() { let validJSON = "null" let invalidJSON = [ "Null", @@ -2056,14 +2065,18 @@ extension JSONEncoderTests { "nu " ] - XCTAssertNoThrow(try json5Decoder.decode(NullReader.self, from: validJSON.data(using: String._Encoding.utf8)!)) + #expect(throws: Never.self) { + try json5Decoder.decode(NullReader.self, from: validJSON.data(using: .utf8)!) + } for json in invalidJSON { - XCTAssertThrowsError(try json5Decoder.decode(NullReader.self, from: json.data(using: String._Encoding.utf8)!), "Expected failure while decoding input \"\(json)\"") + #expect(throws: (any Error).self, "Expected failure while decoding input \"\(json)\"") { + try json5Decoder.decode(NullReader.self, from: json.data(using: .utf8)!) + } } } - func test_json5EsotericErrors() { + @Test func json5EsotericErrors() { // All of the following should fail let arrayStrings = [ "[", @@ -2092,17 +2105,23 @@ extension JSONEncoderTests { [.init(ascii: "{"), 0xf0, 0x80, 0x80], // Invalid UTF-8: Initial byte of 3-byte sequence with only one continuation ] for json in arrayStrings { - XCTAssertThrowsError(try json5Decoder.decode([String].self, from: json.data(using: String._Encoding.utf8)!), "Expected error for input \"\(json)\"") + #expect(throws: (any Error).self, "Expected error for input \"\(json)\"") { + try json5Decoder.decode([String].self, from: json.data(using: .utf8)!) + } } for json in objectStrings { - XCTAssertThrowsError(try json5Decoder.decode([String:Bool].self, from: json.data(using: String._Encoding.utf8)!), "Expected error for input \(json)") + #expect(throws: (any Error).self, "Expected error for input \(json)") { + try json5Decoder.decode([String:Bool].self, from: json.data(using: .utf8)!) + } } for json in objectCharacterArrays { - XCTAssertThrowsError(try json5Decoder.decode([String:Bool].self, from: Data(json)), "Expected error for input \(json)") + #expect(throws: (any Error).self, "Expected error for input \(json)") { + try json5Decoder.decode([String:Bool].self, from: Data(json)) + } } } - func test_json5Strings() { + @Test func json5Strings() { let stringsToTrues = [ "{v\n : true}", "{v \n : true}", @@ -2143,21 +2162,23 @@ extension JSONEncoderTests { ] for json in stringsToTrues { - XCTAssertNoThrow(try json5Decoder.decode([String:Bool].self, from: json.data(using: String._Encoding.utf8)!), "Failed to parse \"\(json)\"") + #expect(throws: Never.self, "Failed to parse \"\(json)\"") { + try json5Decoder.decode([String:Bool].self, from: json.data(using: .utf8)!) + } } for (json, expected) in stringsToStrings { do { - let decoded = try json5Decoder.decode([String:String].self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(expected, decoded["v"]) + let decoded = try json5Decoder.decode([String:String].self, from: json.data(using: .utf8)!) + #expect(expected == decoded["v"]) } catch { if let expected { - XCTFail("Expected \(expected) for input \"\(json)\", but failed with \(error)") + Issue.record("Expected \(expected) for input \"\(json)\", but failed with \(error)") } } } } - func test_json5AssumedDictionary() { + @Test func json5AssumedDictionary() { let decoder = json5Decoder decoder.assumesTopLevelDictionary = true @@ -2186,11 +2207,11 @@ extension JSONEncoderTests { ] for (json, expected) in stringsToString { do { - let decoded = try decoder.decode([String:String].self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(expected, decoded) + let decoded = try decoder.decode([String:String].self, from: json.data(using: .utf8)!) + #expect(expected == decoded) } catch { if let expected { - XCTFail("Expected \(expected) for input \"\(json)\", but failed with \(error)") + Issue.record("Expected \(expected) for input \"\(json)\", but failed with \(error)") } } } @@ -2208,22 +2229,26 @@ extension JSONEncoderTests { "hello: \"world\", goodbye: {\"hi\":\"there\",},", // more than one value, nested dictionary, trailing comma 2 ] for json in stringsToNestedDictionary { - do { - let decoded = try decoder.decode(HelloGoodbye.self, from: json.data(using: String._Encoding.utf8)!) - XCTAssertEqual(helloGoodbyeExpectedValue, decoded) - } catch { - XCTFail("Expected \(helloGoodbyeExpectedValue) for input \"\(json)\", but failed with \(error)") + #expect(throws: Never.self, "Unexpected error for input \"\(json)\"") { + let decoded = try decoder.decode(HelloGoodbye.self, from: json.data(using: .utf8)!) + #expect(helloGoodbyeExpectedValue == decoded) } } - let arrayJSON = "[1,2,3]".data(using: String._Encoding.utf8)! // Assumed dictionary can't be an array - XCTAssertThrowsError(try decoder.decode([Int].self, from: arrayJSON)) + let arrayJSON = "[1,2,3]".data(using: .utf8)! // Assumed dictionary can't be an array + #expect(throws: (any Error).self) { + try decoder.decode([Int].self, from: arrayJSON) + } - let strFragmentJSON = "fragment".data(using: String._Encoding.utf8)! // Assumed dictionary can't be a fragment - XCTAssertThrowsError(try decoder.decode(String.self, from: strFragmentJSON)) + let strFragmentJSON = "fragment".data(using: .utf8)! // Assumed dictionary can't be a fragment + #expect(throws: (any Error).self) { + try decoder.decode(String.self, from: strFragmentJSON) + } - let numFragmentJSON = "42".data(using: String._Encoding.utf8)! // Assumed dictionary can't be a fragment - XCTAssertThrowsError(try decoder.decode(Int.self, from: numFragmentJSON)) + let numFragmentJSON = "42".data(using: .utf8)! // Assumed dictionary can't be a fragment + #expect(throws: (any Error).self) { + try decoder.decode(Int.self, from: numFragmentJSON) + } } enum JSON5SpecTestType { @@ -2247,7 +2272,7 @@ extension JSONEncoderTests { // MARK: - SnakeCase Tests extension JSONEncoderTests { - func testDecodingKeyStrategyCamel() { + @Test func decodingKeyStrategyCamel() throws { let fromSnakeCaseTests = [ ("", ""), // don't die on empty string ("a", "a"), // single character @@ -2286,30 +2311,30 @@ extension JSONEncoderTests { for test in fromSnakeCaseTests { // This JSON contains the camel case key that the test object should decode with, then it uses the snake case key (test.0) as the actual key for the boolean value. - let input = "{\"camelCaseKey\":\"\(test.1)\",\"\(test.0)\":true}".data(using: String._Encoding.utf8)! + let input = "{\"camelCaseKey\":\"\(test.1)\",\"\(test.0)\":true}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - let result = try! decoder.decode(DecodeMe.self, from: input) + let result = try decoder.decode(DecodeMe.self, from: input) - XCTAssertTrue(result.found) + #expect(result.found) } } - func testEncodingDictionaryStringKeyConversionUntouched() { + @Test func encodingDictionaryStringKeyConversionUntouched() throws { let expected = "{\"leaveMeAlone\":\"test\"}" let toEncode: [String: String] = ["leaveMeAlone": "test"] let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let resultData = try! encoder.encode(toEncode) - let resultString = String(bytes: resultData, encoding: String._Encoding.utf8) + let resultData = try encoder.encode(toEncode) + let resultString = String(bytes: resultData, encoding: .utf8) - XCTAssertEqual(expected, resultString) + #expect(expected == resultString) } - func testKeyStrategySnakeGeneratedAndCustom() { + @Test func keyStrategySnakeGeneratedAndCustom() throws { // Test that this works with a struct that has automatically generated keys struct DecodeMe4 : Codable { var thisIsCamelCase : String @@ -2321,72 +2346,74 @@ extension JSONEncoderTests { } // Decoding - let input = "{\"foo_bar\":\"test\",\"this_is_camel_case_too\":\"test2\"}".data(using: String._Encoding.utf8)! + let input = "{\"foo_bar\":\"test\",\"this_is_camel_case_too\":\"test2\"}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - let decodingResult = try! decoder.decode(DecodeMe4.self, from: input) + let decodingResult = try decoder.decode(DecodeMe4.self, from: input) - XCTAssertEqual("test", decodingResult.thisIsCamelCase) - XCTAssertEqual("test2", decodingResult.thisIsCamelCaseToo) + #expect("test" == decodingResult.thisIsCamelCase) + #expect("test2" == decodingResult.thisIsCamelCaseToo) // Encoding let encoded = DecodeMe4(thisIsCamelCase: "test", thisIsCamelCaseToo: "test2") let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let encodingResultData = try! encoder.encode(encoded) - let encodingResultString = String(bytes: encodingResultData, encoding: String._Encoding.utf8) - XCTAssertTrue(encodingResultString!.contains("foo_bar")) - XCTAssertTrue(encodingResultString!.contains("this_is_camel_case_too")) + let encodingResultData = try encoder.encode(encoded) + let encodingResultString = try #require(String(bytes: encodingResultData, encoding: .utf8)) + #expect(encodingResultString.contains("foo_bar")) + #expect(encodingResultString.contains("this_is_camel_case_too")) } - func testDecodingDictionaryFailureKeyPathNested() { - let input = "{\"top_level\": {\"sub_level\": {\"nested_value\": {\"int_value\": \"not_an_int\"}}}}".data(using: String._Encoding.utf8)! + @Test func decodingDictionaryFailureKeyPathNested() { + let input = "{\"top_level\": {\"sub_level\": {\"nested_value\": {\"int_value\": \"not_an_int\"}}}}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase do { _ = try decoder.decode([String: [String : DecodeFailureNested]].self, from: input) } catch DecodingError.typeMismatch(_, let context) { - XCTAssertEqual(4, context.codingPath.count) - XCTAssertEqual("top_level", context.codingPath[0].stringValue) - XCTAssertEqual("sub_level", context.codingPath[1].stringValue) - XCTAssertEqual("nestedValue", context.codingPath[2].stringValue) - XCTAssertEqual("intValue", context.codingPath[3].stringValue) + #expect(4 == context.codingPath.count) + #expect("top_level" == context.codingPath[0].stringValue) + #expect("sub_level" == context.codingPath[1].stringValue) + #expect("nestedValue" == context.codingPath[2].stringValue) + #expect("intValue" == context.codingPath[3].stringValue) } catch { - XCTFail("Unexpected error: \(String(describing: error))") + Issue.record("Unexpected error: \(String(describing: error))") } } - func testDecodingKeyStrategyCamelGenerated() { + @Test func decodingKeyStrategyCamelGenerated() throws { let encoded = DecodeMe3(thisIsCamelCase: "test") let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let resultData = try! encoder.encode(encoded) - let resultString = String(bytes: resultData, encoding: String._Encoding.utf8) - XCTAssertEqual("{\"this_is_camel_case\":\"test\"}", resultString) + let resultData = try encoder.encode(encoded) + let resultString = String(bytes: resultData, encoding: .utf8) + #expect("{\"this_is_camel_case\":\"test\"}" == resultString) } - func testDecodingStringExpectedType() { - let input = #"{"thisIsCamelCase": null}"#.data(using: String._Encoding.utf8)! - do { + @Test func decodingStringExpectedType() { + let input = #"{"thisIsCamelCase": null}"#.data(using: .utf8)! + #expect { _ = try JSONDecoder().decode(DecodeMe3.self, from: input) - } catch DecodingError.valueNotFound(let expected, _) { - XCTAssertTrue(expected == String.self) - } catch { - XCTFail("Unexpected error: \(String(describing: error))") + } throws: { + guard let decodingError = $0 as? DecodingError, + case let DecodingError.valueNotFound(expected, _) = decodingError else { + return false + } + return expected == String.self } } - func testEncodingKeyStrategySnakeGenerated() { + @Test func encodingKeyStrategySnakeGenerated() throws { // Test that this works with a struct that has automatically generated keys - let input = "{\"this_is_camel_case\":\"test\"}".data(using: String._Encoding.utf8)! + let input = "{\"this_is_camel_case\":\"test\"}".data(using: .utf8)! let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - let result = try! decoder.decode(DecodeMe3.self, from: input) + let result = try decoder.decode(DecodeMe3.self, from: input) - XCTAssertEqual("test", result.thisIsCamelCase) + #expect("test" == result.thisIsCamelCase) } - func testEncodingDictionaryFailureKeyPath() { + @Test func encodingDictionaryFailureKeyPath() { let toEncode: [String: EncodeFailure] = ["key": EncodeFailure(someValue: Double.nan)] let encoder = JSONEncoder() @@ -2394,15 +2421,15 @@ extension JSONEncoderTests { do { _ = try encoder.encode(toEncode) } catch EncodingError.invalidValue(_, let context) { - XCTAssertEqual(2, context.codingPath.count) - XCTAssertEqual("key", context.codingPath[0].stringValue) - XCTAssertEqual("someValue", context.codingPath[1].stringValue) + #expect(2 == context.codingPath.count) + #expect("key" == context.codingPath[0].stringValue) + #expect("someValue" == context.codingPath[1].stringValue) } catch { - XCTFail("Unexpected error: \(String(describing: error))") + Issue.record("Unexpected error: \(String(describing: error))") } } - func testEncodingDictionaryFailureKeyPathNested() { + @Test func encodingDictionaryFailureKeyPathNested() { let toEncode: [String: [String: EncodeFailureNested]] = ["key": ["sub_key": EncodeFailureNested(nestedValue: EncodeFailure(someValue: Double.nan))]] let encoder = JSONEncoder() @@ -2410,17 +2437,17 @@ extension JSONEncoderTests { do { _ = try encoder.encode(toEncode) } catch EncodingError.invalidValue(_, let context) { - XCTAssertEqual(4, context.codingPath.count) - XCTAssertEqual("key", context.codingPath[0].stringValue) - XCTAssertEqual("sub_key", context.codingPath[1].stringValue) - XCTAssertEqual("nestedValue", context.codingPath[2].stringValue) - XCTAssertEqual("someValue", context.codingPath[3].stringValue) + #expect(4 == context.codingPath.count) + #expect("key" == context.codingPath[0].stringValue) + #expect("sub_key" == context.codingPath[1].stringValue) + #expect("nestedValue" == context.codingPath[2].stringValue) + #expect("someValue" == context.codingPath[3].stringValue) } catch { - XCTFail("Unexpected error: \(String(describing: error))") + Issue.record("Unexpected error: \(String(describing: error))") } } - func testEncodingKeyStrategySnake() { + @Test func encodingKeyStrategySnake() throws { let toSnakeCaseTests = [ ("simpleOneTwo", "simple_one_two"), ("myURL", "my_url"), @@ -2459,22 +2486,22 @@ extension JSONEncoderTests { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase - let resultData = try! encoder.encode(encoded) - let resultString = String(bytes: resultData, encoding: String._Encoding.utf8) + let resultData = try encoder.encode(encoded) + let resultString = String(bytes: resultData, encoding: .utf8) - XCTAssertEqual(expected, resultString) + #expect(expected == resultString) } } - func test_twoByteUTF16Inputs() { + @Test func twoByteUTF16Inputs() throws { let json = "7" let decoder = JSONDecoder() - XCTAssertEqual(7, try decoder.decode(Int.self, from: json.data(using: .utf16BigEndian)!)) - XCTAssertEqual(7, try decoder.decode(Int.self, from: json.data(using: .utf16LittleEndian)!)) + #expect(try 7 == decoder.decode(Int.self, from: json.data(using: .utf16BigEndian)!)) + #expect(try 7 == decoder.decode(Int.self, from: json.data(using: .utf16LittleEndian)!)) } - private func _run_passTest(name: String, json5: Bool = false, type: T.Type) { + private func _run_passTest(name: String, json5: Bool = false, type: T.Type, sourceLocation: SourceLocation = #_sourceLocation) { let jsonData = testData(forResource: name, withExtension: json5 ? "json5" : "json" , subdirectory: json5 ? "JSON5/pass" : "JSON/pass")! let plistData = testData(forResource: name, withExtension: "plist", subdirectory: "JSON/pass") @@ -2484,7 +2511,7 @@ extension JSONEncoderTests { do { decoded = try decoder.decode(T.self, from: jsonData) } catch { - XCTFail("Pass test \"\(name)\" failed with error: \(error)") + Issue.record("Pass test \"\(name)\" failed with error: \(error)", sourceLocation: sourceLocation) return } @@ -2492,18 +2519,22 @@ extension JSONEncoderTests { prettyPrintEncoder.outputFormatting = .prettyPrinted for encoder in [JSONEncoder(), prettyPrintEncoder] { - let reencodedData = try! encoder.encode(decoded) - let redecodedObjects = try! decoder.decode(T.self, from: reencodedData) - XCTAssertEqual(decoded, redecodedObjects) + #expect(throws: Never.self, sourceLocation: sourceLocation) { + let reencodedData = try encoder.encode(decoded) + let redecodedObjects = try decoder.decode(T.self, from: reencodedData) + #expect(decoded == redecodedObjects) + } if let plistData { - let decodedPlistObjects = try! PropertyListDecoder().decode(T.self, from: plistData) - XCTAssertEqual(decoded, decodedPlistObjects) + #expect(throws: Never.self, sourceLocation: sourceLocation) { + let decodedPlistObjects = try PropertyListDecoder().decode(T.self, from: plistData) + #expect(decoded == decodedPlistObjects) + } } } } - func test_JSONPassTests() { + @Test func jsonPassTests() { _run_passTest(name: "pass1-utf8", type: JSONPass.Test1.self) _run_passTest(name: "pass1-utf16be", type: JSONPass.Test1.self) _run_passTest(name: "pass1-utf16le", type: JSONPass.Test1.self) @@ -2525,7 +2556,7 @@ extension JSONEncoderTests { _run_passTest(name: "pass15", type: JSONPass.Test15.self) } - func test_json5PassJSONFiles() { + @Test func json5PassJSONFiles() { _run_passTest(name: "example", json5: true, type: JSON5Pass.Example.self) _run_passTest(name: "hex", json5: true, type: JSON5Pass.Hex.self) _run_passTest(name: "numbers", json5: true, type: JSON5Pass.Numbers.self) @@ -2533,20 +2564,17 @@ extension JSONEncoderTests { _run_passTest(name: "whitespace", json5: true, type: JSON5Pass.Whitespace.self) } - private func _run_failTest(name: String, type: T.Type) { + private func _run_failTest(name: String, type: T.Type, sourceLocation: SourceLocation = #_sourceLocation) { let jsonData = testData(forResource: name, withExtension: "json", subdirectory: "JSON/fail")! let decoder = JSONDecoder() decoder.assumesTopLevelDictionary = true - do { - let _ = try decoder.decode(T.self, from: jsonData) - XCTFail("Decoding should have failed for invalid JSON data (test name: \(name))") - } catch { - print(error as NSError) + #expect(throws: (any Error).self, "Decoding should have failed for invalid JSON data (test name: \(name))", sourceLocation: sourceLocation) { + try decoder.decode(T.self, from: jsonData) } } - func test_JSONFailTests() { + @Test func jsonFailTests() { _run_failTest(name: "fail1", type: JSONFail.Test1.self) _run_failTest(name: "fail2", type: JSONFail.Test2.self) _run_failTest(name: "fail3", type: JSONFail.Test3.self) @@ -2590,7 +2618,7 @@ extension JSONEncoderTests { } - func _run_json5SpecTest(_ category: String, _ name: String, testType: JSON5SpecTestType, type: T.Type) { + func _run_json5SpecTest(_ category: String, _ name: String, testType: JSON5SpecTestType, type: T.Type, sourceLocation: SourceLocation = #_sourceLocation) { let subdirectory = "/JSON5/spec/\(category)" let ext = testType.fileExtension let jsonData = testData(forResource: name, withExtension: ext, subdirectory: subdirectory)! @@ -2601,47 +2629,53 @@ extension JSONEncoderTests { switch testType { case .json, .json5_foundationPermissiveJSON: // Valid JSON should remain valid JSON5 - XCTAssertNoThrow(try json5.decode(type, from: jsonData)) + #expect(throws: Never.self, sourceLocation: sourceLocation) { + _ = try json5.decode(type, from: jsonData) + } // Repeat with non-JSON5-compliant decoder. - XCTAssertNoThrow(try json.decode(type, from: jsonData)) + #expect(throws: Never.self, sourceLocation: sourceLocation) { + _ = try json.decode(type, from: jsonData) + } case .json5: - XCTAssertNoThrow(try json5.decode(type, from: jsonData)) + #expect(throws: Never.self, sourceLocation: sourceLocation) { + _ = try json5.decode(type, from: jsonData) + } // Regular JSON decoder should throw. do { let val = try json.decode(type, from: jsonData) - XCTFail("Expected decode failure (original JSON)for test \(name).\(ext), but got: \(val)") + Issue.record("Expected decode failure (original JSON)for test \(name).\(ext), but got: \(val)", sourceLocation: sourceLocation) } catch { } case .js: // Valid ES5 that's explicitly disallowed by JSON5 is also invalid JSON. do { let val = try json5.decode(type, from: jsonData) - XCTFail("Expected decode failure (JSON5) for test \(name).\(ext), but got: \(val)") + Issue.record("Expected decode failure (JSON5) for test \(name).\(ext), but got: \(val)", sourceLocation: sourceLocation) } catch { } // Regular JSON decoder should also throw. do { let val = try json.decode(type, from: jsonData) - XCTFail("Expected decode failure (original JSON) for test \(name).\(ext), but got: \(val)") + Issue.record("Expected decode failure (original JSON) for test \(name).\(ext), but got: \(val)", sourceLocation: sourceLocation) } catch { } case .malformed: // Invalid ES5 should remain invalid JSON5 do { let val = try json5.decode(type, from: jsonData) - XCTFail("Expected decode failure (JSON5) for test \(name).\(ext), but got: \(val)") + Issue.record("Expected decode failure (JSON5) for test \(name).\(ext), but got: \(val)", sourceLocation: sourceLocation) } catch { } // Regular JSON decoder should also throw. do { let val = try json.decode(type, from: jsonData) - XCTFail("Expected decode failure (original JSON) for test \(name).\(ext), but got: \(val)") + Issue.record("Expected decode failure (original JSON) for test \(name).\(ext), but got: \(val)", sourceLocation: sourceLocation) } catch { } } } // Also tests non-JSON5 decoder against the non-JSON5 tests in this test suite. - func test_json5Spec() { + @Test func json5Spec() { // Expected successes: _run_json5SpecTest("arrays", "empty-array", testType: .json, type: [Bool].self) _run_json5SpecTest("arrays", "regular-array", testType: .json, type: [Bool?].self) @@ -2768,9 +2802,9 @@ extension JSONEncoderTests { } - func testEncodingDateISO8601() { + @Test func encodingDateISO8601() { let timestamp = Date(timeIntervalSince1970: 1000) - let expectedJSON = "\"\(timestamp.formatted(.iso8601))\"".data(using: String._Encoding.utf8)! + let expectedJSON = "\"\(timestamp.formatted(.iso8601))\"".data(using: .utf8)! _testRoundTrip(of: timestamp, expectedJSON: expectedJSON, @@ -2785,10 +2819,10 @@ extension JSONEncoderTests { dateDecodingStrategy: .iso8601) } - func testEncodingDataBase64() { + @Test func encodingDataBase64() { let data = Data([0xDE, 0xAD, 0xBE, 0xEF]) - let expectedJSON = "\"3q2+7w==\"".data(using: String._Encoding.utf8)! + let expectedJSON = "\"3q2+7w==\"".data(using: .utf8)! _testRoundTrip(of: data, expectedJSON: expectedJSON) // Optional data should encode the same way. @@ -2798,8 +2832,8 @@ extension JSONEncoderTests { // MARK: - Decimal Tests extension JSONEncoderTests { - func testInterceptDecimal() { - let expectedJSON = "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".data(using: String._Encoding.utf8)! + @Test func interceptDecimal() { + let expectedJSON = "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".data(using: .utf8)! // Want to make sure we write out a JSON number, not the keyed encoding here. // 1e127 is too big to fit natively in a Double, too, so want to make sure it's encoded as a Decimal. @@ -2810,16 +2844,16 @@ extension JSONEncoderTests { _testRoundTrip(of: Optional(decimal), expectedJSON: expectedJSON) } - func test_hugeNumbers() { + @Test func hugeNumbers() throws { let json = "23456789012000000000000000000000000000000000000000000000000000000000000000000 " - let data = json.data(using: String._Encoding.utf8)! + let data = json.data(using: .utf8)! - let decimal = try! JSONDecoder().decode(Decimal.self, from: data) + let decimal = try JSONDecoder().decode(Decimal.self, from: data) let expected = Decimal(string: json) - XCTAssertEqual(decimal, expected) + #expect(decimal == expected) } - func testInterceptLargeDecimal() { + @Test func interceptLargeDecimal() { struct TestBigDecimal: Codable, Equatable { var uint64Max: Decimal = Decimal(UInt64.max) var unit64MaxPlus1: Decimal = Decimal( @@ -2845,9 +2879,11 @@ extension JSONEncoderTests { _testRoundTrip(of: testBigDecimal) } - func testOverlargeDecimal() { + @Test func overlargeDecimal() { // Check value too large fails to decode. - XCTAssertThrowsError(try JSONDecoder().decode(Decimal.self, from: "100e200".data(using: .utf8)!)) + #expect(throws: (any Error).self) { + try JSONDecoder().decode(Decimal.self, from: "100e200".data(using: .utf8)!) + } } } @@ -2856,13 +2892,13 @@ extension JSONEncoderTests { #if FOUNDATION_FRAMEWORK extension JSONEncoderTests { // This will remain a framework-only test due to dependence on `DateFormatter`. - func testEncodingDateFormatted() { + @Test func encodingDateFormatted() { let formatter = DateFormatter() formatter.dateStyle = .full formatter.timeStyle = .full let timestamp = Date(timeIntervalSince1970: 1000) - let expectedJSON = "\"\(formatter.string(from: timestamp))\"".data(using: String._Encoding.utf8)! + let expectedJSON = "\"\(formatter.string(from: timestamp))\"".data(using: .utf8)! _testRoundTrip(of: timestamp, expectedJSON: expectedJSON, @@ -2876,7 +2912,7 @@ extension JSONEncoderTests { dateDecodingStrategy: .formatted(formatter)) // So should wrapped dates. - let expectedJSON_array = "[\"\(formatter.string(from: timestamp))\"]".data(using: String._Encoding.utf8)! + let expectedJSON_array = "[\"\(formatter.string(from: timestamp))\"]".data(using: .utf8)! _testRoundTrip(of: TopLevelArrayWrapper(timestamp), expectedJSON: expectedJSON_array, dateEncodingStrategy: .formatted(formatter), @@ -2887,26 +2923,26 @@ extension JSONEncoderTests { // MARK: - .sortedKeys Tests extension JSONEncoderTests { - func testEncodingTopLevelStructuredClass() { + @Test func encodingTopLevelStructuredClass() { // Person is a class with multiple fields. - let expectedJSON = "{\"email\":\"appleseed@apple.com\",\"name\":\"Johnny Appleseed\"}".data(using: String._Encoding.utf8)! + let expectedJSON = "{\"email\":\"appleseed@apple.com\",\"name\":\"Johnny Appleseed\"}".data(using: .utf8)! let person = Person.testValue _testRoundTrip(of: person, expectedJSON: expectedJSON, outputFormatting: [.sortedKeys]) } - func testEncodingOutputFormattingSortedKeys() { - let expectedJSON = "{\"email\":\"appleseed@apple.com\",\"name\":\"Johnny Appleseed\"}".data(using: String._Encoding.utf8)! + @Test func encodingOutputFormattingSortedKeys() { + let expectedJSON = "{\"email\":\"appleseed@apple.com\",\"name\":\"Johnny Appleseed\"}".data(using: .utf8)! let person = Person.testValue _testRoundTrip(of: person, expectedJSON: expectedJSON, outputFormatting: [.sortedKeys]) } - func testEncodingOutputFormattingPrettyPrintedSortedKeys() { - let expectedJSON = "{\n \"email\" : \"appleseed@apple.com\",\n \"name\" : \"Johnny Appleseed\"\n}".data(using: String._Encoding.utf8)! + @Test func encodingOutputFormattingPrettyPrintedSortedKeys() { + let expectedJSON = "{\n \"email\" : \"appleseed@apple.com\",\n \"name\" : \"Johnny Appleseed\"\n}".data(using: .utf8)! let person = Person.testValue _testRoundTrip(of: person, expectedJSON: expectedJSON, outputFormatting: [.prettyPrinted, .sortedKeys]) } - func testEncodingSortedKeys() { + @Test func encodingSortedKeys() { // When requesting sorted keys, dictionary keys are sorted prior to being written out. // This sort should be stable, numeric, and follow human-readable sorting rules as defined by the system locale. let dict = [ @@ -2928,14 +2964,14 @@ extension JSONEncoderTests { "bar" : 10 ] - _testRoundTrip(of: dict, expectedJSON: #"{"FOO":2,"Foo":1,"Foo11":8,"Foo2":5,"bar":10,"foo":3,"foo1":4,"foo12":7,"foo3":6,"føo":9}"#.data(using: String._Encoding.utf8)!, outputFormatting: [.sortedKeys]) + _testRoundTrip(of: dict, expectedJSON: #"{"FOO":2,"Foo":1,"Foo11":8,"Foo2":5,"bar":10,"foo":3,"foo1":4,"foo12":7,"foo3":6,"føo":9}"#.data(using: .utf8)!, outputFormatting: [.sortedKeys]) } - func testEncodingSortedKeysStableOrdering() { + @Test func encodingSortedKeysStableOrdering() { // We want to make sure that keys of different length (but with identical prefixes) always sort in a stable way, regardless of their hash ordering. var dict = ["AAA" : 1, "AAAAAAB" : 2] var expectedJSONString = "{\"AAA\":1,\"AAAAAAB\":2}" - _testRoundTrip(of: dict, expectedJSON: expectedJSONString.data(using: String._Encoding.utf8)!, outputFormatting: [.sortedKeys]) + _testRoundTrip(of: dict, expectedJSON: expectedJSONString.data(using: .utf8)!, outputFormatting: [.sortedKeys]) // We don't want this test to rely on the hashing of Strings or how Dictionary uses that hash. // We'll insert a large number of keys into this dictionary and guarantee that the ordering of the above keys has indeed not changed. @@ -2960,10 +2996,10 @@ extension JSONEncoderTests { expectedJSONString.insert(contentsOf: insertedKeyJSON, at: expectedJSONString.index(before: expectedJSONString.endIndex)) } - _testRoundTrip(of: dict, expectedJSON: expectedJSONString.data(using: String._Encoding.utf8)!, outputFormatting: [.sortedKeys]) + _testRoundTrip(of: dict, expectedJSON: expectedJSONString.data(using: .utf8)!, outputFormatting: [.sortedKeys]) } - func testEncodingMultipleNestedContainersWithTheSameTopLevelKey() { + @Test func encodingMultipleNestedContainersWithTheSameTopLevelKey() { struct Model : Codable, Equatable { let first: String let second: String @@ -3011,11 +3047,11 @@ extension JSONEncoderTests { } let model = Model.testValue - let expectedJSON = "{\"top\":{\"first\":\"Johnny Appleseed\",\"second\":\"appleseed@apple.com\"}}".data(using: String._Encoding.utf8)! + let expectedJSON = "{\"top\":{\"first\":\"Johnny Appleseed\",\"second\":\"appleseed@apple.com\"}}".data(using: .utf8)! _testRoundTrip(of: model, expectedJSON: expectedJSON, outputFormatting: [.sortedKeys]) } - func test_redundantKeyedContainer() { + @Test func redundantKeyedContainer() throws { struct EncodesTwice: Encodable { enum CodingKeys: String, CodingKey { case container @@ -3041,13 +3077,13 @@ extension JSONEncoderTests { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys - let data = try! encoder.encode(EncodesTwice()) + let data = try encoder.encode(EncodesTwice()) let string = String(data: data, encoding: .utf8)! - XCTAssertEqual(string, "{\"container\":{\"foo\":\"Test\",\"somethingElse\":\"SecondAgain\"},\"somethingElse\":\"Foo\"}") + #expect(string == "{\"container\":{\"foo\":\"Test\",\"somethingElse\":\"SecondAgain\"},\"somethingElse\":\"Foo\"}") } - func test_singleValueDictionaryAmendedByContainer() { + @Test func singleValueDictionaryAmendedByContainer() throws { struct Test: Encodable { enum CodingKeys: String, CodingKey { case a @@ -3063,18 +3099,18 @@ extension JSONEncoderTests { } let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys - let data = try! encoder.encode(Test()) + let data = try encoder.encode(Test()) let string = String(data: data, encoding: .utf8)! - XCTAssertEqual(string, "{\"a\":\"c\",\"other\":\"foo\"}") + #expect(string == "{\"a\":\"c\",\"other\":\"foo\"}") } } // MARK: - URL Tests extension JSONEncoderTests { - func testInterceptURL() { + @Test func interceptURL() { // Want to make sure JSONEncoder writes out single-value URLs, not the keyed encoding. - let expectedJSON = "\"http:\\/\\/swift.org\"".data(using: String._Encoding.utf8)! + let expectedJSON = "\"http:\\/\\/swift.org\"".data(using: .utf8)! let url = URL(string: "http://swift.org")! _testRoundTrip(of: url, expectedJSON: expectedJSON) @@ -3082,9 +3118,9 @@ extension JSONEncoderTests { _testRoundTrip(of: Optional(url), expectedJSON: expectedJSON) } - func testInterceptURLWithoutEscapingOption() { + @Test func interceptURLWithoutEscapingOption() { // Want to make sure JSONEncoder writes out single-value URLs, not the keyed encoding. - let expectedJSON = "\"http://swift.org\"".data(using: String._Encoding.utf8)! + let expectedJSON = "\"http://swift.org\"".data(using: .utf8)! let url = URL(string: "http://swift.org")! _testRoundTrip(of: url, expectedJSON: expectedJSON, outputFormatting: [.withoutEscapingSlashes]) @@ -3094,30 +3130,30 @@ extension JSONEncoderTests { } // MARK: - Helper Global Functions -func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) { - if lhs.count != rhs.count { - XCTFail("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)") - return - } +func expectEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String, sourceLocation: SourceLocation = #_sourceLocation) { + if lhs.count != rhs.count { + Issue.record("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)", sourceLocation: sourceLocation) + return + } - for (key1, key2) in zip(lhs, rhs) { - switch (key1.intValue, key2.intValue) { - case (.none, .none): break - case (.some(let i1), .none): - XCTFail("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") - return - case (.none, .some(let i2)): - XCTFail("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") - return - case (.some(let i1), .some(let i2)): - guard i1 == i2 else { - XCTFail("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))") + for (key1, key2) in zip(lhs, rhs) { + switch (key1.intValue, key2.intValue) { + case (.none, .none): break + case (.some(let i1), .none): + Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil", sourceLocation: sourceLocation) return - } + case (.none, .some(let i2)): + Issue.record("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))", sourceLocation: sourceLocation) + return + case (.some(let i1), .some(let i2)): + guard i1 == i2 else { + Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))", sourceLocation: sourceLocation) + return + } } - XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')") - } + #expect(key1.stringValue == key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')", sourceLocation: sourceLocation) + } } // MARK: - Test Types From 4310316819b8e98b497d3c43f8229323d93cfa44 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Tue, 17 Jun 2025 11:48:20 -0700 Subject: [PATCH 2/3] Convert PropertyListEncoder tests --- .../PropertyListEncoderTests.swift | 872 +++++++++--------- 1 file changed, 448 insertions(+), 424 deletions(-) diff --git a/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift b/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift index cd8336bd0..4dda0e1f4 100644 --- a/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift +++ b/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift @@ -7,9 +7,7 @@ //===----------------------------------------------------------------------===// // -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -19,24 +17,23 @@ import TestSupport // MARK: - Test Suite -class TestPropertyListEncoder : XCTestCase { +@Suite("PropertyListEncoder") +private struct PropertyListEncoderTests { // MARK: - Encoding Top-Level Empty Types -#if FIXED_64141381 - func testEncodingTopLevelEmptyStruct() { + @Test func encodingTopLevelEmptyStruct() { let empty = EmptyStruct() _testRoundTrip(of: empty, in: .binary, expectedPlist: _plistEmptyDictionaryBinary) _testRoundTrip(of: empty, in: .xml, expectedPlist: _plistEmptyDictionaryXML) } - func testEncodingTopLevelEmptyClass() { + @Test func encodingTopLevelEmptyClass() { let empty = EmptyClass() _testRoundTrip(of: empty, in: .binary, expectedPlist: _plistEmptyDictionaryBinary) _testRoundTrip(of: empty, in: .xml, expectedPlist: _plistEmptyDictionaryXML) } -#endif // MARK: - Encoding Top-Level Single-Value Types - func testEncodingTopLevelSingleValueEnum() { + @Test func encodingTopLevelSingleValueEnum() { let s1 = Switch.off _testEncodeFailure(of: s1, in: .binary) _testEncodeFailure(of: s1, in: .xml) @@ -50,7 +47,7 @@ class TestPropertyListEncoder : XCTestCase { _testRoundTrip(of: TopLevelWrapper(s2), in: .xml) } - func testEncodingTopLevelSingleValueStruct() { + @Test func encodingTopLevelSingleValueStruct() { let t = Timestamp(3141592653) _testEncodeFailure(of: t, in: .binary) _testEncodeFailure(of: t, in: .xml) @@ -58,7 +55,7 @@ class TestPropertyListEncoder : XCTestCase { _testRoundTrip(of: TopLevelWrapper(t), in: .xml) } - func testEncodingTopLevelSingleValueClass() { + @Test func encodingTopLevelSingleValueClass() { let c = Counter() _testEncodeFailure(of: c, in: .binary) _testEncodeFailure(of: c, in: .xml) @@ -67,49 +64,49 @@ class TestPropertyListEncoder : XCTestCase { } // MARK: - Encoding Top-Level Structured Types - func testEncodingTopLevelStructuredStruct() { + @Test func encodingTopLevelStructuredStruct() { // Address is a struct type with multiple fields. let address = Address.testValue _testRoundTrip(of: address, in: .binary) _testRoundTrip(of: address, in: .xml) } - func testEncodingTopLevelStructuredClass() { + @Test func encodingTopLevelStructuredClass() { // Person is a class with multiple fields. let person = Person.testValue _testRoundTrip(of: person, in: .binary) _testRoundTrip(of: person, in: .xml) } - func testEncodingTopLevelStructuredSingleStruct() { + @Test func encodingTopLevelStructuredSingleStruct() { // Numbers is a struct which encodes as an array through a single value container. let numbers = Numbers.testValue _testRoundTrip(of: numbers, in: .binary) _testRoundTrip(of: numbers, in: .xml) } - func testEncodingTopLevelStructuredSingleClass() { + @Test func encodingTopLevelStructuredSingleClass() { // Mapping is a class which encodes as a dictionary through a single value container. let mapping = Mapping.testValue _testRoundTrip(of: mapping, in: .binary) _testRoundTrip(of: mapping, in: .xml) } - func testEncodingTopLevelDeepStructuredType() { + @Test func encodingTopLevelDeepStructuredType() { // Company is a type with fields which are Codable themselves. let company = Company.testValue _testRoundTrip(of: company, in: .binary) _testRoundTrip(of: company, in: .xml) } - func testEncodingClassWhichSharesEncoderWithSuper() { + @Test func encodingClassWhichSharesEncoderWithSuper() { // Employee is a type which shares its encoder & decoder with its superclass, Person. let employee = Employee.testValue _testRoundTrip(of: employee, in: .binary) _testRoundTrip(of: employee, in: .xml) } - func testEncodingTopLevelNullableType() { + @Test func encodingTopLevelNullableType() { // EnhancedBool is a type which encodes either as a Bool or as nil. _testEncodeFailure(of: EnhancedBool.true, in: .binary) _testEncodeFailure(of: EnhancedBool.true, in: .xml) @@ -126,20 +123,19 @@ class TestPropertyListEncoder : XCTestCase { _testRoundTrip(of: TopLevelWrapper(EnhancedBool.fileNotFound), in: .xml) } - func testEncodingTopLevelWithConfiguration() throws { + @Test func encodingTopLevelWithConfiguration() throws { // CodableTypeWithConfiguration is a struct that conforms to CodableWithConfiguration let value = CodableTypeWithConfiguration.testValue let encoder = PropertyListEncoder() let decoder = PropertyListDecoder() var decoded = try decoder.decode(CodableTypeWithConfiguration.self, from: try encoder.encode(value, configuration: .init(1)), configuration: .init(1)) - XCTAssertEqual(decoded, value) + #expect(decoded == value) decoded = try decoder.decode(CodableTypeWithConfiguration.self, from: try encoder.encode(value, configuration: CodableTypeWithConfiguration.ConfigProviding.self), configuration: CodableTypeWithConfiguration.ConfigProviding.self) - XCTAssertEqual(decoded, value) + #expect(decoded == value) } -#if FIXED_64141381 - func testEncodingMultipleNestedContainersWithTheSameTopLevelKey() { + @Test func encodingMultipleNestedContainersWithTheSameTopLevelKey() { struct Model : Codable, Equatable { let first: String let second: String @@ -186,13 +182,12 @@ class TestPropertyListEncoder : XCTestCase { } let model = Model.testValue - let expectedXML = "\n\n\n\n\ttop\n\t\n\t\tfirst\n\t\tJohnny Appleseed\n\t\tsecond\n\t\tappleseed@apple.com\n\t\n\n\n".data(using: String._Encoding.utf8)! + let expectedXML = "\n\n\n\n\ttop\n\t\n\t\tfirst\n\t\tJohnny Appleseed\n\t\tsecond\n\t\tappleseed@apple.com\n\t\n\n\n".data(using: .utf8)! _testRoundTrip(of: model, in: .xml, expectedPlist: expectedXML) } -#endif -#if false // FIXME: XCTest doesn't support crash tests yet rdar://20195010&22387653 - func testEncodingConflictedTypeNestedContainersWithTheSameTopLevelKey() { +#if FOUNDATION_EXIT_TESTS + @Test func encodingConflictedTypeNestedContainersWithTheSameTopLevelKey() async { struct Model : Encodable, Equatable { let first: String @@ -223,125 +218,128 @@ class TestPropertyListEncoder : XCTestCase { } } - let model = Model.testValue - // This following test would fail as it attempts to re-encode into already encoded container is invalid. This will always fail - expectCrashLater() - _testEncodeFailure(of: model, in: .xml) + await #expect(processExitsWith: .failure) { + let model = Model.testValue + // This following test would fail as it attempts to re-encode into already encoded container is invalid. This will always fail + let encoder = PropertyListEncoder() + encoder.outputFormat = .xml + let _ = try encoder.encode(model) + } } #endif // MARK: - Encoder Features - func testNestedContainerCodingPaths() { + @Test func nestedContainerCodingPaths() { let encoder = PropertyListEncoder() - do { - let _ = try encoder.encode(NestedContainersTestType()) - } catch let error as NSError { - XCTFail("Caught error during encoding nested container types: \(error)") + #expect(throws: Never.self) { + try encoder.encode(NestedContainersTestType()) } } - func testSuperEncoderCodingPaths() { + @Test func superEncoderCodingPaths() { let encoder = PropertyListEncoder() - do { - let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) - } catch let error as NSError { - XCTFail("Caught error during encoding nested container types: \(error)") + #expect(throws: Never.self) { + try encoder.encode(NestedContainersTestType(testSuperEncoder: true)) } } #if FOUNDATION_FRAMEWORK // requires PropertyListSerialization, JSONSerialization - func testEncodingTopLevelData() { - let data = try! JSONSerialization.data(withJSONObject: [String](), options: []) - _testRoundTrip(of: data, in: .binary, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: data, format: .binary, options: 0)) - _testRoundTrip(of: data, in: .xml, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: data, format: .xml, options: 0)) + @Test func encodingTopLevelData() throws { + let data = try JSONSerialization.data(withJSONObject: [String](), options: []) + _testRoundTrip(of: data, in: .binary, expectedPlist: try PropertyListSerialization.data(fromPropertyList: data, format: .binary, options: 0)) + _testRoundTrip(of: data, in: .xml, expectedPlist: try PropertyListSerialization.data(fromPropertyList: data, format: .xml, options: 0)) } - func testInterceptData() { - let data = try! JSONSerialization.data(withJSONObject: [String](), options: []) + @Test func interceptData() throws { + let data = try JSONSerialization.data(withJSONObject: [String](), options: []) let topLevel = TopLevelWrapper(data) let plist = ["value": data] - _testRoundTrip(of: topLevel, in: .binary, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)) - _testRoundTrip(of: topLevel, in: .xml, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)) + _testRoundTrip(of: topLevel, in: .binary, expectedPlist: try PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)) + _testRoundTrip(of: topLevel, in: .xml, expectedPlist: try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)) } - func testInterceptDate() { + @Test func interceptDate() throws { let date = Date(timeIntervalSinceReferenceDate: 0) let topLevel = TopLevelWrapper(date) let plist = ["value": date] - _testRoundTrip(of: topLevel, in: .binary, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)) - _testRoundTrip(of: topLevel, in: .xml, expectedPlist: try! PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)) + _testRoundTrip(of: topLevel, in: .binary, expectedPlist: try PropertyListSerialization.data(fromPropertyList: plist, format: .binary, options: 0)) + _testRoundTrip(of: topLevel, in: .xml, expectedPlist: try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)) } -#endif // FOUNDATION_FRaMEWORK +#endif // FOUNDATION_FRAMEWORK // MARK: - Type coercion - func testTypeCoercion() { - func _testRoundTripTypeCoercionFailure(of value: T, as type: U.Type) where T : Codable, U : Codable { + @Test func typeCoercion() throws { + func _testRoundTripTypeCoercionFailure(of value: T, as type: U.Type, sourceLocation: SourceLocation = #_sourceLocation) throws where T : Codable, U : Codable { let encoder = PropertyListEncoder() encoder.outputFormat = .xml - let xmlData = try! encoder.encode(value) - XCTAssertThrowsError(try PropertyListDecoder().decode(U.self, from: xmlData), "Coercion from \(T.self) to \(U.self) was expected to fail.") + let xmlData = try encoder.encode(value) + #expect(throws: (any Error).self, "Coercion from \(T.self) to \(U.self) was expected to fail.", sourceLocation: sourceLocation) { + try PropertyListDecoder().decode(U.self, from: xmlData) + } encoder.outputFormat = .binary - let binaryData = try! encoder.encode(value) - XCTAssertThrowsError(try PropertyListDecoder().decode(U.self, from: binaryData), "Coercion from \(T.self) to \(U.self) was expected to fail.") - } - - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self) - _testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self) - _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self) + let binaryData = try encoder.encode(value) + #expect(throws: (any Error).self, "Coercion from \(T.self) to \(U.self) was expected to fail.", sourceLocation: sourceLocation) { + try PropertyListDecoder().decode(U.self, from: binaryData) + } + } + + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int8].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int16].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int32].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Int64].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt8].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt16].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt32].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [UInt64].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Float].self) + try _testRoundTripTypeCoercionFailure(of: [false, true], as: [Double].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int8], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int16], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int32], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [Int64], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt8], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt16], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt32], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0, 1] as [UInt64], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Float], as: [Bool].self) + try _testRoundTripTypeCoercionFailure(of: [0.0, 1.0] as [Double], as: [Bool].self) // Real -> Integer coercions that are impossible. - _testRoundTripTypeCoercionFailure(of: [256] as [Double], as: [UInt8].self) - _testRoundTripTypeCoercionFailure(of: [-129] as [Double], as: [Int8].self) - _testRoundTripTypeCoercionFailure(of: [-1.0] as [Double], as: [UInt64].self) - _testRoundTripTypeCoercionFailure(of: [3.14159] as [Double], as: [UInt64].self) - _testRoundTripTypeCoercionFailure(of: [.infinity] as [Double], as: [UInt64].self) - _testRoundTripTypeCoercionFailure(of: [.nan] as [Double], as: [UInt64].self) + try _testRoundTripTypeCoercionFailure(of: [256] as [Double], as: [UInt8].self) + try _testRoundTripTypeCoercionFailure(of: [-129] as [Double], as: [Int8].self) + try _testRoundTripTypeCoercionFailure(of: [-1.0] as [Double], as: [UInt64].self) + try _testRoundTripTypeCoercionFailure(of: [3.14159] as [Double], as: [UInt64].self) + try _testRoundTripTypeCoercionFailure(of: [.infinity] as [Double], as: [UInt64].self) + try _testRoundTripTypeCoercionFailure(of: [.nan] as [Double], as: [UInt64].self) // Especially for binary plist, ensure we maintain different encoded representations of special values like Int64(-1) and UInt64.max, which have the same 8 byte representation. - _testRoundTripTypeCoercionFailure(of: [Int64(-1)], as: [UInt64].self) - _testRoundTripTypeCoercionFailure(of: [UInt64.max], as: [Int64].self) + try _testRoundTripTypeCoercionFailure(of: [Int64(-1)], as: [UInt64].self) + try _testRoundTripTypeCoercionFailure(of: [UInt64.max], as: [Int64].self) } - func testIntegerRealCoercion() throws { - func _testRoundTripTypeCoercion(of value: T, expectedCoercedValue: U) throws { + @Test func integerRealCoercion() throws { + func _testRoundTripTypeCoercion(of value: T, expectedCoercedValue: U, sourceLocation: SourceLocation = #_sourceLocation) throws { let encoder = PropertyListEncoder() encoder.outputFormat = .xml let xmlData = try encoder.encode([value]) var decoded = try PropertyListDecoder().decode([U].self, from: xmlData) - XCTAssertEqual(decoded.first!, expectedCoercedValue) + #expect(decoded.first == expectedCoercedValue, sourceLocation: sourceLocation) encoder.outputFormat = .binary let binaryData = try encoder.encode([value]) decoded = try PropertyListDecoder().decode([U].self, from: binaryData) - XCTAssertEqual(decoded.first!, expectedCoercedValue) + #expect(decoded.first == expectedCoercedValue, sourceLocation: sourceLocation) } try _testRoundTripTypeCoercion(of: 1 as UInt64, expectedCoercedValue: 1.0 as Double) @@ -358,25 +356,19 @@ class TestPropertyListEncoder : XCTestCase { try _testRoundTripTypeCoercion(of: 2.99792458e8 as Double, expectedCoercedValue: 299792458) } - func testDecodingConcreteTypeParameter() { + @Test func decodingConcreteTypeParameter() throws { let encoder = PropertyListEncoder() - guard let plist = try? encoder.encode(Employee.testValue) else { - XCTFail("Unable to encode Employee.") - return - } + let plist = try encoder.encode(Employee.testValue) let decoder = PropertyListDecoder() - guard let decoded = try? decoder.decode(Employee.self as Person.Type, from: plist) else { - XCTFail("Failed to decode Employee as Person from plist.") - return - } + let decoded = try decoder.decode(Employee.self as Person.Type, from: plist) - expectEqual(type(of: decoded), Employee.self, "Expected decoded value to be of type Employee; got \(type(of: decoded)) instead.") + #expect(type(of: decoded) == Employee.self, "Expected decoded value to be of type Employee; got \(type(of: decoded)) instead.") } // MARK: - Encoder State // SR-6078 - func testEncoderStateThrowOnEncode() { + @Test func encoderStateThrowOnEncode() { struct Wrapper : Encodable { let value: T init(_ value: T) { self.value = value } @@ -420,14 +412,16 @@ class TestPropertyListEncoder : XCTestCase { // MARK: - Decoder State // SR-6048 - func testDecoderStateThrowOnDecode() { - let plist = try! PropertyListEncoder().encode([1,2,3]) - let _ = try! PropertyListDecoder().decode(EitherDecodable<[String], [Int]>.self, from: plist) + @Test func decoderStateThrowOnDecode() { + #expect(throws: Never.self) { + let plist = try PropertyListEncoder().encode([1,2,3]) + let _ = try PropertyListDecoder().decode(EitherDecodable<[String], [Int]>.self, from: plist) + } } #if FOUNDATION_FRAMEWORK // MARK: - NSKeyedArchiver / NSKeyedUnarchiver integration - func testArchiving() { + @Test func archiving() throws { struct CodableType: Codable, Equatable { let willBeNil: String? let arrayOfOptionals: [String?] @@ -442,18 +436,14 @@ class TestPropertyListEncoder : XCTestCase { arrayOfOptionals: ["a", "b", nil, "c"], dictionaryOfArrays: [ "data" : [Data([0xfe, 0xed, 0xfa, 0xce]), Data([0xba, 0xaa, 0xaa, 0xad])]]) - do { - try keyedArchiver.encodeEncodable(value, forKey: "strings") - keyedArchiver.finishEncoding() - let data = keyedArchiver.encodedData - - let keyedUnarchiver = try NSKeyedUnarchiver(forReadingFrom: data) - let unarchived = try keyedUnarchiver.decodeTopLevelDecodable(CodableType.self, forKey: "strings") - - XCTAssertEqual(unarchived, value) - } catch { - XCTFail("Unexpected error: \(error)") - } + try keyedArchiver.encodeEncodable(value, forKey: "strings") + keyedArchiver.finishEncoding() + let data = keyedArchiver.encodedData + + let keyedUnarchiver = try NSKeyedUnarchiver(forReadingFrom: data) + let unarchived = try keyedUnarchiver.decodeTopLevelDecodable(CodableType.self, forKey: "strings") + + #expect(unarchived == value) } #endif @@ -463,41 +453,40 @@ class TestPropertyListEncoder : XCTestCase { } private var _plistEmptyDictionaryXML: Data { - return "\n\n\n\n\n".data(using: String._Encoding.utf8)! + return "\n\n\n\n\n".data(using: .utf8)! } - private func _testEncodeFailure(of value: T, in format: PropertyListDecoder.PropertyListFormat) { - do { + private func _testEncodeFailure(of value: T, in format: PropertyListDecoder.PropertyListFormat, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(throws: (any Error).self, "Encode of top-level \(T.self) was expected to fail.", sourceLocation: sourceLocation) { let encoder = PropertyListEncoder() encoder.outputFormat = format let _ = try encoder.encode(value) - XCTFail("Encode of top-level \(T.self) was expected to fail.") - } catch {} + } } @discardableResult - private func _testRoundTrip(of value: T, in format: PropertyListDecoder.PropertyListFormat, expectedPlist plist: Data? = nil) -> T? where T : Codable, T : Equatable { + private func _testRoundTrip(of value: T, in format: PropertyListDecoder.PropertyListFormat, expectedPlist plist: Data? = nil, sourceLocation: SourceLocation = #_sourceLocation) -> T? where T : Codable, T : Equatable { var payload: Data! = nil do { let encoder = PropertyListEncoder() encoder.outputFormat = format payload = try encoder.encode(value) } catch { - XCTFail("Failed to encode \(T.self) to plist: \(error)") + Issue.record("Failed to encode \(T.self) to plist: \(error)") } if let expectedPlist = plist { - XCTAssertEqual(expectedPlist, payload, "Produced plist not identical to expected plist.") + #expect(expectedPlist == payload, "Produced plist not identical to expected plist.") } do { var decodedFormat: PropertyListDecoder.PropertyListFormat = format let decoded = try PropertyListDecoder().decode(T.self, from: payload, format: &decodedFormat) - XCTAssertEqual(format, decodedFormat, "Encountered plist format differed from requested format.") - XCTAssertEqual(decoded, value, "\(T.self) did not round-trip to an equal value.") + #expect(format == decodedFormat, "Encountered plist format differed from requested format.") + #expect(decoded == value, "\(T.self) did not round-trip to an equal value.") return decoded } catch { - XCTFail("Failed to decode \(T.self) from plist: \(error)") + Issue.record("Failed to decode \(T.self) from plist: \(error)") return nil } } @@ -508,7 +497,7 @@ class TestPropertyListEncoder : XCTestCase { } // MARK: - Other tests - func testUnkeyedContainerContainingNulls() throws { + @Test func unkeyedContainerContainingNulls() throws { struct UnkeyedContainerContainingNullTestType : Codable, Equatable { var array = [String?]() @@ -543,32 +532,40 @@ class TestPropertyListEncoder : XCTestCase { _testRoundTrip(of: UnkeyedContainerContainingNullTestType(array: array), in: .binary) } - func test_invalidNSDataKey_82142612() { + @Test func invalidNSDataKey_82142612() { let data = testData(forResource: "Test_82142612", withExtension: "bad")! let decoder = PropertyListDecoder() - XCTAssertThrowsError(try decoder.decode([String:String].self, from: data)) + #expect(throws: (any Error).self) { + try decoder.decode([String:String].self, from: data) + } // Repeat something similar with XML. - let xmlData = "abcdxyz".data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try decoder.decode([String:String].self, from: xmlData)) + let xmlData = "abcdxyz".data(using: .utf8)! + #expect(throws: (any Error).self) { + try decoder.decode([String:String].self, from: xmlData) + } } #if FOUNDATION_FRAMEWORK // TODO: Depends on data's range(of:) implementation - func test_nonStringDictionaryKey() { + @Test func nonStringDictionaryKey() throws { let decoder = PropertyListDecoder() let encoder = PropertyListEncoder() encoder.outputFormat = .binary - var data = try! encoder.encode(["abcd":"xyz"]) + var data = try encoder.encode(["abcd":"xyz"]) // Replace the tag for the ASCII string (0101) that is length 4 ("abcd" => length: 0100) with a boolean "true" tag (0000_1001) let range = data.range(of: Data([0b0101_0100]))! data.replaceSubrange(range, with: Data([0b000_1001])) - XCTAssertThrowsError(try decoder.decode([String:String].self, from: data)) + #expect(throws: (any Error).self) { + try decoder.decode([String:String].self, from: data) + } - let xmlData = "abcdxyz".data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try decoder.decode([String:String].self, from: xmlData)) + let xmlData = "abcdxyz".data(using: .utf8)! + #expect(throws: (any Error).self) { + try decoder.decode([String:String].self, from: xmlData) + } } #endif @@ -605,42 +602,46 @@ class TestPropertyListEncoder : XCTestCase { } } - func test_5616259() throws { + @Test func issue5616259() throws { let plistData = testData(forResource: "Test_5616259", withExtension: "bad")! - XCTAssertThrowsError(try PropertyListDecoder().decode([String].self, from: plistData)) + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode([String].self, from: plistData) + } } - func test_genericProperties_XML() throws { + @Test func genericProperties_XML() throws { let data = testData(forResource: "Generic_XML_Properties", withExtension: "plist")! let props = try PropertyListDecoder().decode(GenericProperties.self, from: data) - XCTAssertNil(props.assertionFailure) + #expect(props.assertionFailure == nil) } - func test_genericProperties_binary() throws { + @Test func genericProperties_binary() throws { let data = testData(forResource: "Generic_XML_Properties_Binary", withExtension: "plist")! let props = try PropertyListDecoder().decode(GenericProperties.self, from: data) - XCTAssertNil(props.assertionFailure) + #expect(props.assertionFailure == nil) } // Binary plist parser should parse any version 'bplist0?' - func test_5877417() throws { + @Test func issue5877417() throws { var data = testData(forResource: "Generic_XML_Properties_Binary", withExtension: "plist")! // Modify the data so the header starts with bplist0x data[7] = UInt8(ascii: "x") let props = try PropertyListDecoder().decode(GenericProperties.self, from: data) - XCTAssertNil(props.assertionFailure) + #expect(props.assertionFailure == nil) } - func test_xmlErrors() { + @Test func xmlErrors() { let data = testData(forResource: "Generic_XML_Properties", withExtension: "plist")! let originalXML = String(data: data, encoding: .utf8)! // Try an empty plist - XCTAssertThrowsError(try PropertyListDecoder().decode(GenericProperties.self, from: Data())) + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode(GenericProperties.self, from: Data()) + } // We'll modify this string in all kinds of nasty ways to introduce errors // --- /* @@ -669,44 +670,46 @@ class TestPropertyListEncoder : XCTestCase { var errorPlists = [String : String]() errorPlists["Deleted leading <"] = String(originalXML[originalXML.index(after: originalXML.startIndex)...]) - errorPlists["Unterminated comment"] = originalXML.replacingOccurrences(of: "", with: "<-- unending comment\n") - errorPlists["Mess with DOCTYPE"] = originalXML.replacingOccurrences(of: "DOCTYPE", with: "foobar") + errorPlists["Unterminated comment"] = originalXML.replacing("", with: "<-- unending comment\n") + errorPlists["Mess with DOCTYPE"] = originalXML.replacing("DOCTYPE", with: "foobar") - let range = originalXML.range(of: "//EN")! + let range = originalXML.firstRange(of: "//EN")! errorPlists["Early EOF"] = String(originalXML[originalXML.startIndex ..< range.lowerBound]) - errorPlists["MalformedDTD"] = originalXML.replacingOccurrences(of: "", with: "") - errorPlists["Bad open tag"] = originalXML.replacingOccurrences(of: "", with: "") - errorPlists["Extra plist object"] = originalXML.replacingOccurrences(of: "", with: "hello\n") - errorPlists["Non-key inside dict"] = originalXML.replacingOccurrences(of: "array1", with: "hello\narray1") - errorPlists["Missing value for key"] = originalXML.replacingOccurrences(of: "value1", with: "") - errorPlists["Malformed real tag"] = originalXML.replacingOccurrences(of: "42", with: "abc123") - errorPlists["Empty int tag"] = originalXML.replacingOccurrences(of: "42", with: "") - errorPlists["Strange int tag"] = originalXML.replacingOccurrences(of: "42", with: "42q") - errorPlists["Hex digit in non-hex int"] = originalXML.replacingOccurrences(of: "42", with: "42A") - errorPlists["Enormous int"] = originalXML.replacingOccurrences(of: "42", with: "99999999999999999999999999999999999999999") + errorPlists["MalformedDTD"] = originalXML.replacing("", with: "") + errorPlists["Bad open tag"] = originalXML.replacing("", with: "") + errorPlists["Extra plist object"] = originalXML.replacing("", with: "hello\n") + errorPlists["Non-key inside dict"] = originalXML.replacing("array1", with: "hello\narray1") + errorPlists["Missing value for key"] = originalXML.replacing("value1", with: "") + errorPlists["Malformed real tag"] = originalXML.replacing("42", with: "abc123") + errorPlists["Empty int tag"] = originalXML.replacing("42", with: "") + errorPlists["Strange int tag"] = originalXML.replacing("42", with: "42q") + errorPlists["Hex digit in non-hex int"] = originalXML.replacing("42", with: "42A") + errorPlists["Enormous int"] = originalXML.replacing("42", with: "99999999999999999999999999999999999999999") errorPlists["Empty plist"] = "" - errorPlists["Empty date"] = originalXML.replacingOccurrences(of: "1976-04-01T12:00:00Z", with: "") - errorPlists["Empty real"] = originalXML.replacingOccurrences(of: "42", with: "") - errorPlists["Fake inline DTD"] = originalXML.replacingOccurrences(of: "PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"", with: "[]") + errorPlists["Empty date"] = originalXML.replacing("1976-04-01T12:00:00Z", with: "") + errorPlists["Empty real"] = originalXML.replacing("42", with: "") + errorPlists["Fake inline DTD"] = originalXML.replacing("PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"", with: "[]") for (name, badPlist) in errorPlists { - let data = badPlist.data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try PropertyListDecoder().decode(GenericProperties.self, from: data), "Case \(name) did not fail as expected") + let data = badPlist.data(using: .utf8)! + #expect(throws: (any Error).self, "Case \(name) did not fail as expected") { + try PropertyListDecoder().decode(GenericProperties.self, from: data) + } } } - func test_6164184() throws { + @Test func issue6164184() throws { let xml = "0x721B0x1111-0xFFFF" - let array = try PropertyListDecoder().decode([Int].self, from: xml.data(using: String._Encoding.utf8)!) - XCTAssertEqual([0x721B, 0x1111, -0xFFFF], array) + let array = try PropertyListDecoder().decode([Int].self, from: xml.data(using: .utf8)!) + #expect([0x721B, 0x1111, -0xFFFF] == array) } - func test_xmlIntegerEdgeCases() throws { - func checkValidEdgeCase(_ xml: String, type: T.Type, expected: T) throws { - let value = try PropertyListDecoder().decode(type, from: xml.data(using: String._Encoding.utf8)!) - XCTAssertEqual(value, expected) + @Test func xmlIntegerEdgeCases() throws { + func checkValidEdgeCase(_ xml: String, type: T.Type, expected: T, sourceLocation: SourceLocation = #_sourceLocation) throws { + let value = try PropertyListDecoder().decode(type, from: xml.data(using: .utf8)!) + #expect(value == expected, sourceLocation: sourceLocation) } try checkValidEdgeCase("127", type: Int8.self, expected: .max) @@ -732,8 +735,10 @@ class TestPropertyListEncoder : XCTestCase { try checkValidEdgeCase("4294967295", type: UInt32.self, expected: .max) try checkValidEdgeCase("18446744073709551615", type: UInt64.self, expected: .max) - func checkInvalidEdgeCase(_ xml: String, type: T.Type) { - XCTAssertThrowsError(try PropertyListDecoder().decode(type, from: xml.data(using: String._Encoding.utf8)!)) + func checkInvalidEdgeCase(_ xml: String, type: T.Type, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(throws: (any Error).self, sourceLocation: sourceLocation) { + try PropertyListDecoder().decode(type, from: xml.data(using: .utf8)!) + } } checkInvalidEdgeCase("128", type: Int8.self) @@ -760,14 +765,14 @@ class TestPropertyListEncoder : XCTestCase { checkInvalidEdgeCase("18446744073709551616", type: UInt64.self) } - func test_xmlIntegerWhitespace() throws { + @Test func xmlIntegerWhitespace() throws { let xml = " +\t42\t- 99 -\t0xFACE" - let value = try PropertyListDecoder().decode([Int].self, from: xml.data(using: String._Encoding.utf8)!) - XCTAssertEqual(value, [42, -99, -0xFACE]) + let value = try PropertyListDecoder().decode([Int].self, from: xml.data(using: .utf8)!) + #expect(value == [42, -99, -0xFACE]) } - func test_binaryNumberEdgeCases() throws { + @Test func binaryNumberEdgeCases() throws { _testRoundTrip(of: [Int8.max], in: .binary) _testRoundTrip(of: [Int8.min], in: .binary) _testRoundTrip(of: [Int16.max], in: .binary) @@ -795,8 +800,8 @@ class TestPropertyListEncoder : XCTestCase { _testRoundTrip(of: [-Double.infinity], in: .binary) } - func test_binaryReals() throws { - func encode(_: T.Type) -> (data: Data, expected: [T]) { + @Test func binaryReals() throws { + func encode(_: T.Type) throws -> (data: Data, expected: [T]) { let expected: [T] = [ 1.5, 2, @@ -808,27 +813,23 @@ class TestPropertyListEncoder : XCTestCase { ] let encoder = PropertyListEncoder() encoder.outputFormat = .binary - let data = try! encoder.encode(expected) + let data = try encoder.encode(expected) return (data, expected) } - func test(_ type: T.Type) { - let (data, expected) = encode(type) - do { - let result = try PropertyListDecoder().decode([T].self, from: data) - XCTAssertEqual(result, expected, "Type: \(type)") - } catch { - XCTFail("Expected error \(error) for type: \(type)") - } + func test(_ type: T.Type) throws { + let (data, expected) = try encode(type) + let result = try PropertyListDecoder().decode([T].self, from: data) + #expect(result == expected, "Type: \(type)") } - test(Float.self) - test(Double.self) + try test(Float.self) + try test(Double.self) } - func test_XMLReals() throws { + @Test func xmlReals() throws { let xml = "1.52 -3.141.00000000000000000000000131415.9e-4-iNfinfInItY" - let array = try PropertyListDecoder().decode([Float].self, from: xml.data(using: String._Encoding.utf8)!) + let array = try PropertyListDecoder().decode([Float].self, from: xml.data(using: .utf8)!) let expected: [Float] = [ 1.5, 2, @@ -838,76 +839,78 @@ class TestPropertyListEncoder : XCTestCase { -.infinity, .infinity ] - XCTAssertEqual(array, expected) + #expect(array == expected) // nan doesn't work with equality. let xmlNAN = "nAnNANnan" - let arrayNAN = try PropertyListDecoder().decode([Float].self, from: xmlNAN.data(using: String._Encoding.utf8)!) + let arrayNAN = try PropertyListDecoder().decode([Float].self, from: xmlNAN.data(using: .utf8)!) for val in arrayNAN { - XCTAssertTrue(val.isNaN) + #expect(val.isNaN) } } - func test_bad_XMLReals() { - let badRealXMLs = [ - "0x10", - "notanumber", - "infinite", - "1.2.3", - "1.e", - "1.5 ", // Trailing whitespace is rejected, unlike leading whitespace. - "", - ] - for xml in badRealXMLs { - XCTAssertThrowsError(try PropertyListDecoder().decode(Float.self, from: xml.data(using: String._Encoding.utf8)!), "Input: \(xml)") + @Test(arguments: [ + "0x10", + "notanumber", + "infinite", + "1.2.3", + "1.e", + "1.5 ", // Trailing whitespace is rejected, unlike leading whitespace. + "", + ]) + func bad_XMLReals(xml: String) { + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode(Float.self, from: xml.data(using: .utf8)!) } } - func test_oldStylePlist_invalid() { - let data = "goodbye cruel world".data(using: String._Encoding.utf16)! - XCTAssertThrowsError(try PropertyListDecoder().decode(String.self, from: data)) + @Test func oldStylePlist_invalid() { + let data = "goodbye cruel world".data(using: .utf16)! + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode(String.self, from: data) + } } // Microsoft: Microsoft vso 1857102 : High Sierra regression that caused data loss : CFBundleCopyLocalizedString returns incorrect string // Escaped octal chars can be shorter than 3 chars long; i.e. \5 ≡ \05 ≡ \005. - func test_oldStylePlist_getSlashedChars_octal() { + @Test func oldStylePlist_getSlashedChars_octal() throws { // ('\0', '\00', '\000', '\1', '\01', '\001', ..., '\777') let data = testData(forResource: "test_oldStylePlist_getSlashedChars_octal", withExtension: "plist")! - let actualStrings = try! PropertyListDecoder().decode([String].self, from: data) + let actualStrings = try PropertyListDecoder().decode([String].self, from: data) let expectedData = testData(forResource: "test_oldStylePlist_getSlashedChars_octal_expected", withExtension: "plist")! - let expectedStrings = try! PropertyListDecoder().decode([String].self, from: expectedData) + let expectedStrings = try PropertyListDecoder().decode([String].self, from: expectedData) - XCTAssertEqual(actualStrings, expectedStrings) + #expect(actualStrings == expectedStrings) } // Old-style plists support Unicode literals via \U syntax. They can be 1–4 characters wide. - func test_oldStylePlist_getSlashedChars_unicode() { + @Test func oldStylePlist_getSlashedChars_unicode() throws { // ('\U0', '\U00', '\U000', '\U0000', '\U1', ..., '\UFFFF') let data = testData(forResource: "test_oldStylePlist_getSlashedChars_unicode", withExtension: "plist")! - let actualStrings = try! PropertyListDecoder().decode([String].self, from: data) + let actualStrings = try PropertyListDecoder().decode([String].self, from: data) let expectedData = testData(forResource: "test_oldStylePlist_getSlashedChars_unicode_expected", withExtension: "plist")! - let expectedStrings = try! PropertyListDecoder().decode([String].self, from: expectedData) + let expectedStrings = try PropertyListDecoder().decode([String].self, from: expectedData) - XCTAssertEqual(actualStrings, expectedStrings) + #expect(actualStrings == expectedStrings) } - func test_oldStylePlist_getSlashedChars_literals() { + @Test func oldStylePlist_getSlashedChars_literals() throws { let literals = ["\u{7}", "\u{8}", "\u{12}", "\n", "\r", "\t", "\u{11}", "\"", "\\n"] - let data = "('\\a', '\\b', '\\f', '\\n', '\\r', '\\t', '\\v', '\\\"', '\\\\n')".data(using: String._Encoding.utf8)! + let data = "('\\a', '\\b', '\\f', '\\n', '\\r', '\\t', '\\v', '\\\"', '\\\\n')".data(using: .utf8)! - let strings = try! PropertyListDecoder().decode([String].self, from: data) - XCTAssertEqual(strings, literals) + let strings = try PropertyListDecoder().decode([String].self, from: data) + #expect(strings == literals) } - func test_oldStylePlist_dictionary() { + @Test func oldStylePlist_dictionary() { let data = """ { "test key" = value; testData = ; "nested array" = (a, b, c); } -""".data(using: String._Encoding.utf16)! +""".data(using: .utf16)! struct Values: Decodable { let testKey: String @@ -922,20 +925,20 @@ class TestPropertyListEncoder : XCTestCase { } do { let decoded = try PropertyListDecoder().decode(Values.self, from: data) - XCTAssertEqual(decoded.testKey, "value") - XCTAssertEqual(decoded.testData, Data([0xfe, 0xed, 0xfa, 0xce])) - XCTAssertEqual(decoded.nestedArray, ["a", "b", "c"]) + #expect(decoded.testKey == "value") + #expect(decoded.testData == Data([0xfe, 0xed, 0xfa, 0xce])) + #expect(decoded.nestedArray == ["a", "b", "c"]) } catch { - XCTFail("Unexpected error: \(error)") + Issue.record("Unexpected error: \(error)") } } - func test_oldStylePlist_stringsFileFormat() { + @Test func oldStylePlist_stringsFileFormat() { let data = """ string1 = "Good morning"; string2 = "Good afternoon"; string3 = "Good evening"; -""".data(using: String._Encoding.utf16)! +""".data(using: .utf16)! do { let decoded = try PropertyListDecoder().decode([String:String].self, from: data) @@ -944,19 +947,19 @@ string3 = "Good evening"; "string2": "Good afternoon", "string3": "Good evening" ] - XCTAssertEqual(decoded, expected) + #expect(decoded == expected) } catch { - XCTFail("Unexpected error: \(error)") + Issue.record("Unexpected error: \(error)") } } - func test_oldStylePlist_comments() { + @Test func oldStylePlist_comments() { let data = """ // Initial comment */ string1 = /*Test*/ "Good morning"; // Test string2 = "Good afternoon" /*Test// */; string3 = "Good evening"; // Test -""".data(using: String._Encoding.utf16)! +""".data(using: .utf16)! do { let decoded = try PropertyListDecoder().decode([String:String].self, from: data) @@ -965,30 +968,30 @@ string3 = "Good evening"; // Test "string2": "Good afternoon", "string3": "Good evening" ] - XCTAssertEqual(decoded, expected) + #expect(decoded == expected) } catch { - XCTFail("Unexpected error: \(error)") + Issue.record("Unexpected error: \(error)") } } #if FOUNDATION_FRAMEWORK // Requires __PlistDictionaryDecoder - func test_oldStylePlist_data() { + @Test func oldStylePlist_data() { let data = """ data1 = <7465 73 74 696E67 31 323334>; -""".data(using: String._Encoding.utf16)! +""".data(using: .utf16)! do { let decoded = try PropertyListDecoder().decode([String:Data].self, from: data) - let expected = ["data1" : "testing1234".data(using: String._Encoding.utf8)!] - XCTAssertEqual(decoded, expected) + let expected = ["data1" : "testing1234".data(using: .utf8)!] + #expect(decoded == expected) } catch { - XCTFail("Unexpected error: \(error)") + Issue.record("Unexpected error: \(error)") } } #endif @@ -996,40 +999,38 @@ data1 = <7465 #if FOUNDATION_FRAMEWORK // Requires PropertyListSerialization - func test_BPlistCollectionReferences() { + @Test func bplistCollectionReferences() throws { // Use NSArray/NSDictionary and PropertyListSerialization so that we get a bplist with internal references. let c: NSArray = [ "a", "a", "a" ] let b: NSArray = [ c, c, c ] let a: NSArray = [ b, b, b ] let d: NSDictionary = ["a" : a, "b" : b, "c" : c] - let data = try! PropertyListSerialization.data(fromPropertyList: d, format: .binary, options: 0) + let data = try PropertyListSerialization.data(fromPropertyList: d, format: .binary, options: 0) - do { - struct DecodedReferences: Decodable { - let a: [[[String]]] - let b: [[String]] - let c: [String] - } - - let decoded = try PropertyListDecoder().decode(DecodedReferences.self, from: data) - XCTAssertEqual(decoded.a, a as! [[[String]]]) - XCTAssertEqual(decoded.b, b as! [[String]]) - XCTAssertEqual(decoded.c, c as! [String]) - } catch { - XCTFail("Unexpected error: \(error)") + struct DecodedReferences: Decodable { + let a: [[[String]]] + let b: [[String]] + let c: [String] } + + let decoded = try PropertyListDecoder().decode(DecodedReferences.self, from: data) + #expect(decoded.a == a as? [[[String]]]) + #expect(decoded.b == b as? [[String]]) + #expect(decoded.c == c as? [String]) } #endif - func test_reallyOldDates_5842198() throws { + @Test func reallyOldDates_5842198() throws { let plist = "\n\n\n0009-09-15T23:16:13Z\n" - let data = plist.data(using: String._Encoding.utf8)! + let data = plist.data(using: .utf8)! - XCTAssertNoThrow(try PropertyListDecoder().decode(Date.self, from: data)) + #expect(throws: Never.self) { + try PropertyListDecoder().decode(Date.self, from: data) + } } - func test_badDates() throws { + @Test func badDates() throws { let timeInterval = TimeInterval(-63145612800) // This is the equivalent of an all-zero gregorian date. let date = Date(timeIntervalSinceReferenceDate: timeInterval) @@ -1037,26 +1038,26 @@ data1 = <7465 _testRoundTrip(of: [date], in: .binary) } - func test_badDate_encode() throws { + @Test func badDate_encode() throws { let date = Date(timeIntervalSinceReferenceDate: -63145612800) // 0000-01-02 AD let encoder = PropertyListEncoder() encoder.outputFormat = .xml let data = try encoder.encode([date]) let str = String(data: data, encoding: String.Encoding.utf8) - XCTAssertEqual(str, "\n\n\n\n\t0000-01-02T00:00:00Z\n\n\n") + #expect(str == "\n\n\n\n\t0000-01-02T00:00:00Z\n\n\n") } - func test_badDate_decode() throws { + @Test func badDate_decode() throws { // Test that we can correctly decode a distant date in the past let plist = "\n\n\n0000-01-02T00:00:00Z\n" - let data = plist.data(using: String._Encoding.utf8)! + let data = plist.data(using: .utf8)! let d = try PropertyListDecoder().decode(Date.self, from: data) - XCTAssertEqual(d.timeIntervalSinceReferenceDate, -63145612800) + #expect(d.timeIntervalSinceReferenceDate == -63145612800) } - func test_realEncodeRemoveZeroSuffix() throws { + @Test func realEncodeRemoveZeroSuffix() throws { // Tests that we encode "whole-value reals" (such as `2.0`, `-5.0`, etc) // **without** the `.0` for backwards compactability let encoder = PropertyListEncoder() @@ -1065,166 +1066,171 @@ data1 = <7465 let wholeFloat: Float = 2.0 var data = try encoder.encode([wholeFloat]) - var str = try XCTUnwrap(String(data: data, encoding: String.Encoding.utf8)) - var expected = template.replacingOccurrences( - of: "<%EXPECTED%>", with: "2") - XCTAssertEqual(str, expected) + var str = try #require(String(data: data, encoding: String.Encoding.utf8)) + var expected = template.replacing( + "<%EXPECTED%>", with: "2") + #expect(str == expected) let wholeDouble: Double = -5.0 data = try encoder.encode([wholeDouble]) - str = try XCTUnwrap(String(data: data, encoding: String.Encoding.utf8)) - expected = template.replacingOccurrences( - of: "<%EXPECTED%>", with: "-5") - XCTAssertEqual(str, expected) + str = try #require(String(data: data, encoding: String.Encoding.utf8)) + expected = template.replacing( + "<%EXPECTED%>", with: "-5") + #expect(str == expected) // Make sure other reals are not affacted let notWholeDouble = 0.5 data = try encoder.encode([notWholeDouble]) - str = try XCTUnwrap(String(data: data, encoding: String.Encoding.utf8)) - expected = template.replacingOccurrences( - of: "<%EXPECTED%>", with: "0.5") - XCTAssertEqual(str, expected) + str = try #require(String(data: data, encoding: String.Encoding.utf8)) + expected = template.replacing( + "<%EXPECTED%>", with: "0.5") + #expect(str == expected) } - func test_farFutureDates() throws { + @Test func farFutureDates() throws { let date = Date(timeIntervalSince1970: 999999999999.0) _testRoundTrip(of: [date], in: .xml) } - func test_122065123_encode() throws { + @Test func encode_122065123() throws { let date = Date(timeIntervalSinceReferenceDate: 728512994) // 2024-02-01 20:43:14 UTC let encoder = PropertyListEncoder() encoder.outputFormat = .xml let data = try encoder.encode([date]) let str = String(data: data, encoding: String.Encoding.utf8) - XCTAssertEqual(str, "\n\n\n\n\t2024-02-01T20:43:14Z\n\n\n") // Previously encoded as "2024-01-32T20:43:14Z" + #expect(str == "\n\n\n\n\t2024-02-01T20:43:14Z\n\n\n") // Previously encoded as "2024-01-32T20:43:14Z" } - func test_122065123_decodingCompatibility() throws { + @Test func decodingCompatibility_122065123() throws { // Test that we can correctly decode an invalid date let plist = "\n\n\n2024-01-32T20:43:14Z\n" - let data = plist.data(using: String._Encoding.utf8)! + let data = plist.data(using: .utf8)! let d = try PropertyListDecoder().decode(Date.self, from: data) - XCTAssertEqual(d.timeIntervalSinceReferenceDate, 728512994) // 2024-02-01T20:43:14Z + #expect(d.timeIntervalSinceReferenceDate == 728512994) // 2024-02-01T20:43:14Z } - func test_multibyteCharacters_escaped_noencoding() throws { - let plistData = "These are copyright signs © © blah blah blah.".data(using: String._Encoding.utf8)! + @Test func multibyteCharacters_escaped_noencoding() throws { + let plistData = "These are copyright signs © © blah blah blah.".data(using: .utf8)! let result = try PropertyListDecoder().decode(String.self, from: plistData) - XCTAssertEqual("These are copyright signs © © blah blah blah.", result) + #expect("These are copyright signs © © blah blah blah." == result) } - func test_escapedCharacters() throws { - let plistData = "&'<>"".data(using: String._Encoding.utf8)! + @Test func escapedCharacters() throws { + let plistData = "&'<>"".data(using: .utf8)! let result = try PropertyListDecoder().decode(String.self, from: plistData) - XCTAssertEqual("&'<>\"", result) + #expect("&'<>\"" == result) } - func test_dataWithBOM_utf8() throws { + @Test func dataWithBOM_utf8() throws { let bom = Data([0xef, 0xbb, 0xbf]) - let plist = bom + "\n\n\nhello\n".data(using: String._Encoding.utf8)! + let plist = bom + "\n\n\nhello\n".data(using: .utf8)! let result = try PropertyListDecoder().decode(String.self, from: plist) - XCTAssertEqual(result, "hello") + #expect(result == "hello") } - -#if FOUNDATION_FRAMEWORK - // TODO: Depends on UTF32 encoding on non-Darwin platforms - func test_dataWithBOM_utf32be() throws { + @Test func dataWithBOM_utf32be() throws { let bom = Data([0x00, 0x00, 0xfe, 0xff]) - let plist = bom + "\n\n\nhello\n".data(using: String._Encoding.utf32BigEndian)! + let plist = bom + "\n\n\nhello\n".data(using: .utf32BigEndian)! let result = try PropertyListDecoder().decode(String.self, from: plist) - XCTAssertEqual(result, "hello") + #expect(result == "hello") } - func test_dataWithBOM_utf32le() throws { + @Test func dataWithBOM_utf32le() throws { let bom = Data([0xff, 0xfe]) - let plist = bom + "\n\n\nhello\n".data(using: String._Encoding.utf16LittleEndian)! + let plist = bom + "\n\n\nhello\n".data(using: .utf16LittleEndian)! let result = try PropertyListDecoder().decode(String.self, from: plist) - XCTAssertEqual(result, "hello") + #expect(result == "hello") } -#endif - func test_plistWithBadUTF8() throws { + @Test func plistWithBadUTF8() throws { let data = testData(forResource: "bad_plist", withExtension: "bad")! - XCTAssertThrowsError(try PropertyListDecoder().decode([String].self, from: data)) - } + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode([String].self, from: data) +} } - func test_plistWithEscapedCharacters() throws { - let plist = "com.apple.security.temporary-exception.sbpl(allow mach-lookup (global-name-regex #"^[0-9]+$"))".data(using: String._Encoding.utf8)! + @Test func plistWithEscapedCharacters() throws { + let plist = "com.apple.security.temporary-exception.sbpl(allow mach-lookup (global-name-regex #"^[0-9]+$"))".data(using: .utf8)! let result = try PropertyListDecoder().decode([String:String].self, from: plist) - XCTAssertEqual(result, ["com.apple.security.temporary-exception.sbpl" : "(allow mach-lookup (global-name-regex #\"^[0-9]+$\"))"]) + #expect(result == ["com.apple.security.temporary-exception.sbpl" : "(allow mach-lookup (global-name-regex #\"^[0-9]+$\"))"]) } #if FOUNDATION_FRAMEWORK // OpenStep format is not supported in Essentials - func test_returnRightFormatFromParse() throws { - let plist = "{ CFBundleDevelopmentRegion = en; }".data(using: String._Encoding.utf8)! + @Test func returnRightFormatFromParse() throws { + let plist = "{ CFBundleDevelopmentRegion = en; }".data(using: .utf8)! var format : PropertyListDecoder.PropertyListFormat = .binary let _ = try PropertyListDecoder().decode([String:String].self, from: plist, format: &format) - XCTAssertEqual(format, .openStep) + #expect(format == .openStep) } #endif - func test_decodingEmoji() throws { - let plist = "emoji🚘".data(using: String._Encoding.utf8)! + @Test func decodingEmoji() throws { + let plist = "emoji🚘".data(using: .utf8)! let result = try PropertyListDecoder().decode([String:String].self, from: plist) let expected = "\u{0001F698}" - XCTAssertEqual(expected, result["emoji"]) + #expect(expected == result["emoji"]) } - func test_decodingTooManyCharactersError() throws { + @Test func decodingTooManyCharactersError() throws { // Try a plist with too many characters to be a unicode escape sequence - let plist = "emoji".data(using: String._Encoding.utf8)! - - XCTAssertThrowsError(try PropertyListDecoder().decode([String:String].self, from: plist)) + let plist = "emoji".data(using: .utf8)! + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode([String:String].self, from: plist) + } // Try a plist with an invalid unicode escape sequence - let plist2 = "emoji".data(using: String._Encoding.utf8)! + let plist2 = "emoji".data(using: .utf8)! - XCTAssertThrowsError(try PropertyListDecoder().decode([String:String].self, from: plist2)) + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode([String:String].self, from: plist2) + } } - func test_roundTripEmoji() throws { + @Test func roundTripEmoji() throws { let strings = ["🚘", "👩🏻‍❤️‍👨🏿", "🏋🏽‍♂️🕺🏼🥌"] _testRoundTrip(of: strings, in: .xml) _testRoundTrip(of: strings, in: .binary) } - func test_roundTripEscapedStrings() { + @Test func roundTripEscapedStrings() { let strings = ["&", "<", ">"] _testRoundTrip(of: strings, in: .xml) } - func test_unterminatedComment() { - let plist = "".data(using: String._Encoding.utf8)! - XCTAssertThrowsError(try PropertyListDecoder().decode([String].self, from: plist)) - } + @Test func unterminatedComment() { + let plist = "".data(using: .utf8)! + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode([String].self, from: plist) +} } - func test_incompleteOpenTag() { - let plist = ".allNils) let testEmptyDict = try PropertyListDecoder().decode(DecodeIfPresentAllTypes.self, from: emptyDictEncoding) - XCTAssertEqual(testEmptyDict, .allNils) + #expect(testEmptyDict == .allNils) let allNullDictEncoding = try encoder.encode(DecodeIfPresentAllTypes.allNils) let testAllNullDict = try PropertyListDecoder().decode(DecodeIfPresentAllTypes.self, from: allNullDictEncoding) - XCTAssertEqual(testAllNullDict, .allNils) + #expect(testAllNullDict == .allNils) let allOnesDictEncoding = try encoder.encode(DecodeIfPresentAllTypes.allOnes) let testAllOnesDict = try PropertyListDecoder().decode(DecodeIfPresentAllTypes.self, from: allOnesDictEncoding) - XCTAssertEqual(testAllOnesDict, .allOnes) + #expect(testAllOnesDict == .allOnes) let emptyArrayEncoding = try encoder.encode(DecodeIfPresentAllTypes.allNils) let testEmptyArray = try PropertyListDecoder().decode(DecodeIfPresentAllTypes.self, from: emptyArrayEncoding) - XCTAssertEqual(testEmptyArray, .allNils) + #expect(testEmptyArray == .allNils) let allNullArrayEncoding = try encoder.encode(DecodeIfPresentAllTypes.allNils) let testAllNullArray = try PropertyListDecoder().decode(DecodeIfPresentAllTypes.self, from: allNullArrayEncoding) - XCTAssertEqual(testAllNullArray, .allNils) + #expect(testAllNullArray == .allNils) let allOnesArrayEncoding = try encoder.encode(DecodeIfPresentAllTypes.allOnes) let testAllOnesArray = try PropertyListDecoder().decode(DecodeIfPresentAllTypes.self, from: allOnesArrayEncoding) - XCTAssertEqual(testAllOnesArray, .allOnes) + #expect(testAllOnesArray == .allOnes) } } - func test_garbageCharactersAfterXMLTagName() throws { - let garbage = "barfoo".data(using: String._Encoding.utf8)! - - XCTAssertThrowsError(try PropertyListDecoder().decode([String:String].self, from: garbage)) + @Test func garbageCharactersAfterXMLTagName() throws { + let garbage = "barfoo".data(using: .utf8)! + #expect(throws: (any Error).self) { + try PropertyListDecoder().decode([String:String].self, from: garbage) + } // Historical behavior allows for whitespace to immediately follow tag names - let acceptable = "barfoo".data(using: String._Encoding.utf8)! + let acceptable = "barfoo".data(using: .utf8)! - XCTAssertEqual(try PropertyListDecoder().decode([String:String].self, from: acceptable), ["bar":"foo"]) + #expect(try PropertyListDecoder().decode([String:String].self, from: acceptable) == ["bar":"foo"]) } } // MARK: - Helper Global Functions -func XCTAssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) { +func AssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) { if lhs.count != rhs.count { - XCTFail("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)") + Issue.record("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)") return } @@ -1570,21 +1594,21 @@ func XCTAssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: Strin switch (key1.intValue, key2.intValue) { case (.none, .none): break case (.some(let i1), .none): - XCTFail("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") + Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") return case (.none, .some(let i2)): - XCTFail("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") + Issue.record("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") return case (.some(let i1), .some(let i2)): guard i1 == i2 else { - XCTFail("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))") + Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))") return } break } - XCTAssertEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')") + #expect(key1.stringValue == key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')") } } @@ -1927,13 +1951,13 @@ private struct NestedContainersTestType : Encodable { func encode(to encoder: Encoder) throws { if self.testSuperEncoder { var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) - XCTAssertEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.") + AssertEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") + AssertEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.") let superEncoder = topLevelContainer.superEncoder(forKey: .a) - XCTAssertEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.") + AssertEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.") + AssertEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.") _testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a]) } else { _testNestedContainers(in: encoder, baseCodingPath: []) @@ -1941,57 +1965,57 @@ private struct NestedContainersTestType : Encodable { } func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey]) { - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.") // codingPath should not change upon fetching a non-nested container. var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self) - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.") // Nested Keyed Container do { // Nested container for key should have a new key pushed on. var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a) - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + AssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.") // Inserting a keyed container should not change existing coding paths. let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one) - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") - XCTAssertEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + AssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") + AssertEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.") // Inserting an unkeyed container should not change existing coding paths. let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two) - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") - XCTAssertEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.") + AssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.") + AssertEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.") } // Nested Unkeyed Container do { // Nested container for key should have a new key pushed on. var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .b) - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + AssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "New second-level keyed container had unexpected codingPath.") // Appending a keyed container should not change existing coding paths. let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self) - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") - XCTAssertEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], "New third-level keyed container had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + AssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") + AssertEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 0)], "New third-level keyed container had unexpected codingPath.") // Appending an unkeyed container should not change existing coding paths. let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer() - XCTAssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") - XCTAssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") - XCTAssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") - XCTAssertEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], "New third-level unkeyed container had unexpected codingPath.") + AssertEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.") + AssertEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.") + AssertEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.b], "Second-level unkeyed container's codingPath changed.") + AssertEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.b, _TestKey(index: 1)], "New third-level unkeyed container had unexpected codingPath.") } } } From eced6e060f48e725d42001a5e1ba09793ff3b7d6 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Tue, 17 Jun 2025 16:24:17 -0700 Subject: [PATCH 3/3] Gather source location from callers to AssertEqualPaths --- .../PropertyListEncoderTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift b/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift index 4dda0e1f4..9ac492da9 100644 --- a/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift +++ b/Tests/FoundationEssentialsTests/PropertyListEncoderTests.swift @@ -1584,9 +1584,9 @@ data1 = <7465 // MARK: - Helper Global Functions -func AssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) { +func AssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String, sourceLocation: SourceLocation = #_sourceLocation) { if lhs.count != rhs.count { - Issue.record("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)") + Issue.record("\(prefix) [CodingKey].count mismatch: \(lhs.count) != \(rhs.count)", sourceLocation: sourceLocation) return } @@ -1594,21 +1594,21 @@ func AssertEqualPaths(_ lhs: [CodingKey], _ rhs: [CodingKey], _ prefix: String) switch (key1.intValue, key2.intValue) { case (.none, .none): break case (.some(let i1), .none): - Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil") + Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil", sourceLocation: sourceLocation) return case (.none, .some(let i2)): - Issue.record("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))") + Issue.record("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))", sourceLocation: sourceLocation) return case (.some(let i1), .some(let i2)): guard i1 == i2 else { - Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))") + Issue.record("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))", sourceLocation: sourceLocation) return } break } - #expect(key1.stringValue == key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')") + #expect(key1.stringValue == key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')", sourceLocation: sourceLocation) } }