diff --git a/codegen/Package.swift b/codegen/Package.swift index 054b671dba6..d4cea34610c 100644 --- a/codegen/Package.swift +++ b/codegen/Package.swift @@ -37,6 +37,8 @@ extension Target.Dependency { static var smithyWaitersAPI: Self { .product(name: "SmithyWaitersAPI", package: "smithy-swift") } static var smithyTestUtils: Self { .product(name: "SmithyTestUtil", package: "smithy-swift") } static var smithyStreams: Self { .product(name: "SmithyStreams", package: "smithy-swift") } + static var smithyReadWrite: Self { .product(name: "SmithyReadWrite", package: "smithy-swift") } + static var smithyJSON: Self { .product(name: "SmithyJSON", package: "smithy-swift") } } // MARK: - Base Package @@ -94,7 +96,8 @@ private var protocolTestTargets: [Target] { .init(name: "RPCEventStream", sourcePath: "\(baseDirLocal)/RPCEventStream", buildOnly: true), .init(name: "Waiters", sourcePath: "\(baseDirLocal)/Waiters", testPath: "../codegen/protocol-test-codegen-local/Tests"), .init(name: "StringArrayEndpointParam", sourcePath: "\(baseDirLocal)/StringArrayEndpointParam"), - .init(name: "RPCV2CBORTestSDK", sourcePath: "\(baseDir)/smithy-rpcv2-cbor") + .init(name: "RPCV2CBORTestSDK", sourcePath: "\(baseDir)/smithy-rpcv2-cbor"), + .init(name: "Performance", sourcePath: "\(baseDirLocal)/Performance", testPath: "../codegen/protocol-test-codegen-local/Tests"), ] return protocolTests.flatMap { protocolTest in let target = Target.target( @@ -115,11 +118,14 @@ private var protocolTestTargets: [Target] { .smithyChecksumsAPI, .smithyChecksums, .smithyWaitersAPI, + .smithyReadWrite, + .smithyJSON, .awsSDKCommon, .awsSDKIdentity, .awsSDKHTTPAuth, .awsSDKEventStreamsAuth, .awsSDKChecksums, + ], path: "\(protocolTest.sourcePath)/swift-codegen/Sources/\(protocolTest.name)" ) diff --git a/codegen/protocol-test-codegen-local/Tests/swift-codegen/Tests/PerformanceTests/PerformanceTests.swift b/codegen/protocol-test-codegen-local/Tests/swift-codegen/Tests/PerformanceTests/PerformanceTests.swift new file mode 100644 index 00000000000..fe30e30aced --- /dev/null +++ b/codegen/protocol-test-codegen-local/Tests/swift-codegen/Tests/PerformanceTests/PerformanceTests.swift @@ -0,0 +1,199 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable import Performance +@_spi(SmithyReadWrite) import SmithyReadWrite +@_spi(SmithyReadWrite) import SmithyJSON + +typealias TestStructure = PerformanceClientTypes.TestStructure + +final class PerformanceTests: XCTestCase { + + static var array10: [TestStructure] = [] + static var data10: Data = Data() + static var array100: [TestStructure] = [] + static var data100: Data = Data() + static var array1000: [TestStructure] = [] + static var data1000: Data = Data() + + override static func setUp() { + (Self.array10, Self.data10) = try! generateData(n: 10) + (Self.array100, Self.data100) = try! generateData(n: 100) + (Self.array1000, Self.data1000) = try! generateData(n: 1000) + } + + // MARK: - Schema performance tests + + func test_schema_10() throws { + try run_test_schema(original: Self.array10, data: Self.data10) + } + + func test_schema_100() throws { + try run_test_schema(original: Self.array100, data: Self.data100) + } + + func test_schema_1000() throws { + try run_test_schema(original: Self.array1000, data: Self.data1000) + } + + func run_test_schema(original: [TestStructure], data: Data, file: StaticString = #file, line: UInt = #line) throws { + var dup = [TestStructure]() + measure { + do { + let reader = try Reader.from(data: data) + dup = try reader.readListNonNull(schema: schema__namespace_aws_smithy_swift_performance__name_TestStructureList) + } catch { + XCTFail("Threw error: \(error)", file: file, line: line) + } + } + XCTAssert(original == dup) + } + + // MARK: - ReadWrite performance tests + + func test_readWrite_10() throws { + try run_test_readWrite(original: Self.array10, data: Self.data10) + } + + func test_readWrite_100() throws { + try run_test_readWrite(original: Self.array100, data: Self.data100) + } + + func test_readWrite_1000() throws { + try run_test_readWrite(original: Self.array1000, data: Self.data1000) + } + + func run_test_readWrite(original: [TestStructure], data: Data, file: StaticString = #file, line: UInt = #line) throws { + var dup = [TestStructure]() + measure { + do { + let reader = try Reader.from(data: data) + dup = try reader.readList(memberReadingClosure: TestStructure.read(from:), memberNodeInfo: "member", isFlattened: false) + } catch { + XCTFail("Threw error: \(error)", file: file, line: line) + } + } + XCTAssert(original == dup) + } + + // MARK: - Swift Decodable performance tests + + func test_decodable_10() throws { + try run_test_decodable(original: Self.array10, data: Self.data10) + } + + func test_decodable_100() throws { + try run_test_decodable(original: Self.array100, data: Self.data100) + } + + func test_decodable_1000() throws { + try run_test_decodable(original: Self.array1000, data: Self.data1000) + } + + func run_test_decodable(original: [TestStructure], data: Data, file: StaticString = #file, line: UInt = #line) throws { + var dup = [TestStructure]() + measure { + do { + dup = try JSONDecoder().decode([TestStructure].self, from: data) + } catch { + XCTFail("Threw error: \(error)", file: file, line: line) + } + } + XCTAssertEqual(original, dup) + } + + // MARK: - Test data generation + + private static func generateData(n: Int) throws -> ([TestStructure], Data) { + let array = (0.. TestStructure { + guard reader.hasContent else { throw ReaderError.requiredValueNotPresent } + var value = TestStructure() + value.int = try reader["int"].readIfPresent() + value.string = try reader["string"].readIfPresent() + value.bool = try reader["bool"].readIfPresent() + value.listOfInts = try reader["ListOfInts"].readListIfPresent(memberReadingClosure: ReadingClosures.readInt(from:), memberNodeInfo: "member", isFlattened: false) + value.mapOfDoubles = try reader["MapOfDoubles"].readMapIfPresent(valueReadingClosure: ReadingClosures.readDouble(from:), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false) + return value + } +} + +extension TestStructure: Codable { + + public init(from decoder: any Decoder) throws { + self.init() + let container = try decoder.container(keyedBy: CodingKeys.self) + self.int = try container.decodeIfPresent(Int.self, forKey: .int) + self.string = try container.decodeIfPresent(String.self, forKey: .string) + self.bool = try container.decodeIfPresent(Bool.self, forKey: .bool) + self.listOfInts = try container.decodeIfPresent([Int].self, forKey: .listOfInts) + self.mapOfDoubles = try container.decodeIfPresent([String: Double].self, forKey: .mapOfDoubles) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(int, forKey: .int) + try container.encodeIfPresent(string, forKey: .string) + try container.encodeIfPresent(bool, forKey: .bool) + try container.encodeIfPresent(listOfInts, forKey: .listOfInts) + try container.encodeIfPresent(mapOfDoubles, forKey: .mapOfDoubles) + } + + enum CodingKeys: String, CodingKey { + case int + case string + case bool + case listOfInts = "ListOfInts" + case mapOfDoubles = "MapOfDoubles" + } +} + +extension TestStructure: Equatable { + + public static func ==(lhs: PerformanceClientTypes.TestStructure, rhs: PerformanceClientTypes.TestStructure) -> Bool { + if lhs.int != rhs.int { return false } + if lhs.string != rhs.string { return false } + if lhs.bool != rhs.bool { return false } + if lhs.listOfInts != rhs.listOfInts { return false } + if lhs.mapOfDoubles != rhs.mapOfDoubles { return false } + return true + } +} diff --git a/codegen/protocol-test-codegen-local/build.gradle.kts b/codegen/protocol-test-codegen-local/build.gradle.kts index 4669fc32daa..9d088732568 100644 --- a/codegen/protocol-test-codegen-local/build.gradle.kts +++ b/codegen/protocol-test-codegen-local/build.gradle.kts @@ -51,7 +51,11 @@ val codegenTests = listOf( CodegenTest( "aws.endpointtests.stringarray#EndpointStringArray", "StringArrayEndpointParam" - ) + ), + CodegenTest( + "aws.smithy.swift.performance#Performance", + "Performance" + ), ) fun generateSmithyBuild(tests: List): String { diff --git a/codegen/protocol-test-codegen-local/model/performance-tests.smithy b/codegen/protocol-test-codegen-local/model/performance-tests.smithy new file mode 100644 index 00000000000..db67ed61169 --- /dev/null +++ b/codegen/protocol-test-codegen-local/model/performance-tests.smithy @@ -0,0 +1,49 @@ +$version: "2.0" + +namespace aws.smithy.swift.performance + +use aws.protocols#restJson1 +use aws.api#service + +// A service which has a GET operation with waiters defined upon it. +// The acceptor in each waiter serves as subject for unit testing, +// to ensure that the logic in code-generated acceptors works as +// expected. +@service(sdkId: "Performance") +@restJson1 +service Performance { + version: "2022-11-30", + operations: [GetWidget] +} + +@http(uri: "/widget", method: "POST") +operation GetWidget { + input: IO, + output: IO + errors: [] +} + +structure IO { + list: TestStructureList +} + +list TestStructureList { + member: TestStructure +} + +structure TestStructure { + int: Integer + string: String + bool: Boolean + ListOfInts: ListOfIntegers + MapOfDoubles: MapOfDoubles +} + +list ListOfIntegers { + member: Integer +} + +map MapOfDoubles { + key: String + value: Double +} diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1HttpResponseBindingErrorGeneratableTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1HttpResponseBindingErrorGeneratableTests.kt index 3e7ccde2613..8bde1f6428e 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1HttpResponseBindingErrorGeneratableTests.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsrestjson/AWSRestJson1HttpResponseBindingErrorGeneratableTests.kt @@ -23,6 +23,7 @@ enum GreetingWithErrorsOutputError { static func httpError(from httpResponse: SmithyHTTPAPI.HTTPResponse) async throws -> Swift.Error { let data = try await httpResponse.data() let responseReader = try SmithyJSON.Reader.from(data: data) + responseReader.respectsJSONName = true let baseError = try AWSClientRuntime.RestJSONError(httpResponse: httpResponse, responseReader: responseReader, noErrorWrapping: false) if let error = baseError.customError() { return error } if let error = try httpServiceError(baseError: baseError) { return error } diff --git a/scripts/protogen.sh b/scripts/protogen.sh index 57c5e5b6c5b..572944cfafc 100755 --- a/scripts/protogen.sh +++ b/scripts/protogen.sh @@ -43,6 +43,7 @@ rm -f codegen/protocol-test-codegen-local/build/smithyprojections/protocol-test- rm -f codegen/protocol-test-codegen-local/build/smithyprojections/protocol-test-codegen-local/EventStream/swift-codegen/Package.swift rm -f codegen/protocol-test-codegen-local/build/smithyprojections/protocol-test-codegen-local/RPCEventStream/swift-codegen/Package.swift rm -f codegen/protocol-test-codegen-local/build/smithyprojections/protocol-test-codegen-local/Waiters/swift-codegen/Package.swift +rm -f codegen/protocol-test-codegen-local/build/smithyprojections/protocol-test-codegen-local/Performance/swift-codegen/Package.swift # If on Mac, reopen Xcode to the refreshed tests if [ -x "$(command -v osascript)" ]; then