diff --git a/Sources/XMLCoder/Box/KeyedBox.swift b/Sources/XMLCoder/Box/KeyedBox.swift index a6aa1691..b9743b76 100644 --- a/Sources/XMLCoder/Box/KeyedBox.swift +++ b/Sources/XMLCoder/Box/KeyedBox.swift @@ -56,10 +56,9 @@ extension KeyedStorage: ExpressibleByDictionaryLiteral { } } -extension KeyedStorage: CustomStringConvertible - where Key: Comparable { +extension KeyedStorage: CustomStringConvertible { var description: String { - return "[\(buffer)]" + return "\(buffer)" } } @@ -110,6 +109,6 @@ extension KeyedBox: Box { extension KeyedBox: CustomStringConvertible { var description: String { - return "\(elements)" + return "{attributes: \(attributes), elements: \(elements)}" } } diff --git a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift index 73f97cb7..bc3069a1 100644 --- a/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLKeyedDecodingContainer.swift @@ -222,10 +222,15 @@ struct _XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol let value: T? = try decoder.unbox(entry) - if value == nil, - let type = type as? AnyArray.Type, - type.elementType is AnyOptional.Type { - return [nil] as! T + if value == nil { + if let type = type as? AnyArray.Type, + type.elementType is AnyOptional.Type, + let result = [nil] as? T { + return result + } else if let type = type as? AnyOptional.Type, + let result = type.init() as? T { + return result + } } guard let unwrapped = value else { diff --git a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift index 26154701..adf42f62 100644 --- a/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift +++ b/Sources/XMLCoder/Decoder/XMLUnkeyedDecodingContainer.swift @@ -160,7 +160,20 @@ struct _XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { decoder.codingPath.append(_XMLKey(index: currentIndex)) defer { self.decoder.codingPath.removeLast() } - let box = container[self.currentIndex] + let box: Box + + // work around unkeyed box wrapped as single element of keyed box + if let type = type as? AnyArray.Type, + let keyedBox = container[self.currentIndex] as? KeyedBox, + keyedBox.attributes.count == 0, + keyedBox.elements.count == 1, + let firstKey = keyedBox.elements.keys.first, + let unkeyedBox = keyedBox.elements[firstKey] { + box = unkeyedBox + } else { + box = container[self.currentIndex] + } + let value = try decode(decoder, box) defer { currentIndex += 1 } diff --git a/Tests/XMLCoderTests/BenchmarkTests.swift b/Tests/XMLCoderTests/BenchmarkTests.swift new file mode 100644 index 00000000..ec9cd75f --- /dev/null +++ b/Tests/XMLCoderTests/BenchmarkTests.swift @@ -0,0 +1,243 @@ +import XCTest +@testable import XMLCoder + +class BenchmarkTests: XCTestCase { + struct Container: Codable { + let unkeyed: [T] + let keyed: [String: T] + } + + func container(with value: T) -> Container { + // unkeyed.count + keyed.count = self.count: + let count = self.count / 2 + + let unkeyed = [T](repeating: value, count: count) + let keyed = Dictionary(uniqueKeysWithValues: unkeyed.enumerated().map { index, value in + ("key_\(index)", value) + }) + + return Container(unkeyed: unkeyed, keyed: keyed) + } + + func encodedContainer(of _: T.Type, with value: T) throws -> Data { + let container = self.container(with: value) + + let encoder = XMLEncoder() + encoder.outputFormatting = .prettyPrinted + + return try encoder.encode(container, withRootKey: "container") + } + + func testEncoding(with value: T, _ closure: (() -> ()) -> ()) throws { + try testEncoding(of: T.self, with: value, closure) + } + + func testEncoding(of _: T.Type, with value: T, _ closure: (() -> ()) -> ()) throws { + let container: Container = self.container(with: value) + + let encoder = XMLEncoder() + + var caughtError: Error? + + closure { + do { + _ = try encoder.encode(container, withRootKey: "container") + } catch { + caughtError = error + } + } + + if let error = caughtError { + throw error + } + } + + func testDecoding(with value: T, _ closure: (() -> ()) -> ()) throws { + try testDecoding(of: T.self, with: value, closure) + } + + func testDecoding(of _: T.Type, with value: T, _ closure: (() -> ()) -> ()) throws { + let data: Data = try encodedContainer(of: T.self, with: value) + + let decoder = XMLDecoder() + + var caughtError: Error? + + closure { + do { + _ = try decoder.decode(Container.self, from: data) + } catch { + caughtError = error + } + } + + if let error = caughtError { + throw error + } + } + + let count: Int = 1000 + + let null: Bool? = nil + let bool: Bool = true + let int: Int = -42 + let uint: UInt = 42 + let float: Float = 42.0 + let decimal: Decimal = 42.0 + let date: Date = Date() + let data: Data = Data(base64Encoded: "bG9yZW0gaXBzdW0=")! + let url: URL = URL(string: "http://example.com")! + let array: [Int] = [1, 2, 3, 4, 5] + let dictionary: [String: Int] = ["key_1": 1, "key_2": 2, "key_3": 3, "key_4": 4, "key_5": 5] + + func testEncodeNulls() throws { + try testEncoding(with: null) { closure in + self.measure { closure() } + } + } + + func testDecodeNulls() throws { + try testDecoding(with: null) { closure in + self.measure { closure() } + } + } + + func testEncodeBools() throws { + try testEncoding(with: bool) { closure in + self.measure { closure() } + } + } + + func testDecodeBools() throws { + try testDecoding(with: bool) { closure in + self.measure { closure() } + } + } + + func testEncodeInts() throws { + try testEncoding(with: int) { closure in + self.measure { closure() } + } + } + + func testDecodeInts() throws { + try testDecoding(with: int) { closure in + self.measure { closure() } + } + } + + func testEncodeUInts() throws { + try testEncoding(with: uint) { closure in + self.measure { closure() } + } + } + + func testDecodeUInts() throws { + try testDecoding(with: uint) { closure in + self.measure { closure() } + } + } + + func testEncodeFloats() throws { + try testEncoding(with: float) { closure in + self.measure { closure() } + } + } + + func testDecodeFloats() throws { + try testDecoding(with: float) { closure in + self.measure { closure() } + } + } + + func testEncodeDecimals() throws { + try testEncoding(with: decimal) { closure in + self.measure { closure() } + } + } + + func testDecodeDecimals() throws { + try testDecoding(with: decimal) { closure in + self.measure { closure() } + } + } + + func testEncodeDates() throws { + try testEncoding(with: date) { closure in + self.measure { closure() } + } + } + + func testDecodeDates() throws { + try testDecoding(with: date) { closure in + self.measure { closure() } + } + } + + func testEncodeDatas() throws { + try testEncoding(with: data) { closure in + self.measure { closure() } + } + } + + func testDecodeDatas() throws { + try testDecoding(with: data) { closure in + self.measure { closure() } + } + } + + func testEncodeURLs() throws { + try testEncoding(with: url) { closure in + self.measure { closure() } + } + } + + func testDecodeURLs() throws { + try testDecoding(with: url) { closure in + self.measure { closure() } + } + } + + func testEncodeArrays() throws { + try testEncoding(with: array) { closure in + self.measure { closure() } + } + } + + func testDecodeArrays() throws { + try testDecoding(with: array) { closure in + self.measure { closure() } + } + } + + func testEncodeDictionaries() throws { + try testEncoding(with: dictionary) { closure in + self.measure { closure() } + } + } + + func testDecodeDictionaries() throws { + try testDecoding(with: dictionary) { closure in + self.measure { closure() } + } + } + + static var allTests = [ + ("testEncodeNulls", testEncodeNulls), + ("testDecodeNulls", testDecodeNulls), + ("testEncodeBools", testEncodeBools), + ("testDecodeBools", testDecodeBools), + ("testEncodeInts", testEncodeInts), + ("testDecodeInts", testDecodeInts), + ("testEncodeUInts", testEncodeUInts), + ("testDecodeUInts", testDecodeUInts), + ("testEncodeFloats", testEncodeFloats), + ("testDecodeFloats", testDecodeFloats), + ("testEncodeDecimals", testEncodeDecimals), + ("testDecodeDecimals", testDecodeDecimals), + ("testEncodeArrays", testEncodeArrays), + ("testDecodeArrays", testDecodeArrays), + ("testEncodeDictionaries", testEncodeDictionaries), + ("testDecodeDictionaries", testDecodeDictionaries), + ] +} diff --git a/XMLCoder.xcodeproj/project.pbxproj b/XMLCoder.xcodeproj/project.pbxproj index 31c05922..edcd3010 100644 --- a/XMLCoder.xcodeproj/project.pbxproj +++ b/XMLCoder.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ BF63EF0821CD7AF8001D38C5 /* URLBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */; }; BF63EF0A21CD7C1A001D38C5 /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0921CD7C1A001D38C5 /* URLTests.swift */; }; BF63EF0C21CD7F28001D38C5 /* EmptyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF0B21CD7F28001D38C5 /* EmptyTests.swift */; }; + BF63EF1E21CEC99B001D38C5 /* BenchmarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */; }; BF63EF6721D0F874001D38C5 /* XMLKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF6621D0F874001D38C5 /* XMLKeyTests.swift */; }; BF63EF6921D0FDB5001D38C5 /* XMLHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF6821D0FDB5001D38C5 /* XMLHeaderTests.swift */; }; BF63EF6B21D10284001D38C5 /* XMLElementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF63EF6A21D10284001D38C5 /* XMLElementTests.swift */; }; @@ -126,6 +127,7 @@ BF63EF0721CD7AF8001D38C5 /* URLBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLBoxTests.swift; sourceTree = ""; }; BF63EF0921CD7C1A001D38C5 /* URLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTests.swift; sourceTree = ""; }; BF63EF0B21CD7F28001D38C5 /* EmptyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTests.swift; sourceTree = ""; }; + BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BenchmarkTests.swift; sourceTree = ""; }; BF63EF6621D0F874001D38C5 /* XMLKeyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XMLKeyTests.swift; sourceTree = ""; }; BF63EF6821D0FDB5001D38C5 /* XMLHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLHeaderTests.swift; sourceTree = ""; }; BF63EF6A21D10284001D38C5 /* XMLElementTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XMLElementTests.swift; sourceTree = ""; }; @@ -342,6 +344,7 @@ OBJ_36 /* PlantTest.swift */, OBJ_37 /* RJITest.swift */, OBJ_38 /* RelationshipsTest.swift */, + BF63EF1D21CEC99A001D38C5 /* BenchmarkTests.swift */, D1FC040421C7EF8200065B43 /* RJISample.swift */, A61DCCD621DF8DB300C0A19D /* ClassTests.swift */, ); @@ -551,6 +554,7 @@ OBJ_82 /* CDCatalog.swift in Sources */, BF63EF0021CCDED2001D38C5 /* XMLStackParserTests.swift in Sources */, BF9457CC21CBB516005ACFDE /* NullBoxTests.swift in Sources */, + BF63EF1E21CEC99B001D38C5 /* BenchmarkTests.swift in Sources */, BF9457CF21CBB516005ACFDE /* IntBoxTests.swift in Sources */, BF9457E021CBB68A005ACFDE /* DataBoxTests.swift in Sources */, BF9457F521CBB6BC005ACFDE /* DecimalTests.swift in Sources */, diff --git a/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist b/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist index f027834d..bc1ddc1c 100644 --- a/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist +++ b/XMLCoder.xcodeproj/xcshareddata/xcbaselines/XMLCoder::XMLCoderTests.xcbaseline/76E090BF-7AFE-4988-A06A-3C423396A4A4.plist @@ -4,6 +4,229 @@ classNames + BenchmarkTests + + testDecodeArrays() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.076639 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeBools() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.0131 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeDatas() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.0143 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeDates() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.013922 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeDecimals() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.011251 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeDictionaries() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.08847 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeFloats() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.015645 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeInts() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.014091 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeNulls() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.0093493 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeUInts() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.014785 + baselineIntegrationDisplayName + Local Baseline + + + testDecodeURLs() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.014802 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeArrays() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.076162 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeBools() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.015558 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeDatas() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.017226 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeDates() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.019835 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeDecimals() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.014639 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeDictionaries() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.092163 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeFloats() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.016656 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeInts() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.015619 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeNulls() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.0071232 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeUInts() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.015903 + baselineIntegrationDisplayName + Local Baseline + + + testEncodeURLs() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.016821 + baselineIntegrationDisplayName + Local Baseline + + + RJITest testBenchmarkRSS()