From 1919b3239c6319b4f75198560fb5b858a17c7d0b Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 16 Jun 2023 18:20:03 +0100 Subject: [PATCH] Make all tests async (#545) * make Credential Provider tests async * Make all AWSClient tests async * Make endpoint, service, middleware logging tests async * Make paginate, payload and waiter tests async * Last non async call --- .../SotoCoreTests/AWSClientTests+async.swift | 223 -------------- Tests/SotoCoreTests/AWSClientTests.swift | 258 ++++++++-------- .../SotoCoreTests/AWSServiceTests+async.swift | 72 ----- Tests/SotoCoreTests/AWSServiceTests.swift | 35 +++ .../ConfigFileCredentialProviderTests.swift | 59 ++-- .../Credential/ConfigFileLoaderTests.swift | 50 ++-- .../CredentialProviderTests+async.swift | 42 --- .../Credential/CredentialProviderTests.swift | 45 ++- .../MetaDataCredentialProviderTests.swift | 64 ++-- .../RotatingCredentialProviderTests.swift | 87 ++---- ...ntimeSelectorCredentialProviderTests.swift | 156 +++++----- .../Credential/STSAssumeRoleTests.swift | 5 +- .../EndpointDiscoveryTests+async.swift | 225 -------------- .../EndpointDiscoveryTests.swift | 94 +++--- Tests/SotoCoreTests/LoggingTests.swift | 63 ++-- Tests/SotoCoreTests/MiddlewareTests.swift | 37 +-- Tests/SotoCoreTests/PaginateTests+async.swift | 239 --------------- Tests/SotoCoreTests/PaginateTests.swift | 276 +++++++----------- Tests/SotoCoreTests/PayloadTests.swift | 24 +- Tests/SotoCoreTests/WaiterTests+async.swift | 79 ----- Tests/SotoCoreTests/WaiterTests.swift | 64 ++-- 21 files changed, 625 insertions(+), 1572 deletions(-) delete mode 100644 Tests/SotoCoreTests/AWSClientTests+async.swift delete mode 100644 Tests/SotoCoreTests/AWSServiceTests+async.swift delete mode 100644 Tests/SotoCoreTests/Credential/CredentialProviderTests+async.swift delete mode 100644 Tests/SotoCoreTests/EndpointDiscoveryTests+async.swift delete mode 100644 Tests/SotoCoreTests/PaginateTests+async.swift delete mode 100644 Tests/SotoCoreTests/WaiterTests+async.swift diff --git a/Tests/SotoCoreTests/AWSClientTests+async.swift b/Tests/SotoCoreTests/AWSClientTests+async.swift deleted file mode 100644 index 5e200466d..000000000 --- a/Tests/SotoCoreTests/AWSClientTests+async.swift +++ /dev/null @@ -1,223 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Soto for AWS open source project -// -// Copyright (c) 2017-2022 the Soto project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Soto project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import AsyncHTTPClient -import Dispatch -import Logging -import NIOCore -import NIOFoundationCompat -import NIOHTTP1 -import NIOPosix -@testable import SotoCore -import SotoTestUtils -import SotoXML -import XCTest - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -final class AWSClientAsyncTests: XCTestCase { - func testGetCredential() async throws { - struct MyCredentialProvider: AsyncCredentialProvider { - func getCredential(on eventLoop: EventLoop, logger: Logger) async throws -> Credential { - return StaticCredential(accessKeyId: "key", secretAccessKey: "secret") - } - } - let client = createAWSClient(credentialProvider: .custom { _ in MyCredentialProvider() }) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let credentialForSignature = try await client.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger) - XCTAssertEqual(credentialForSignature.accessKeyId, "key") - XCTAssertEqual(credentialForSignature.secretAccessKey, "secret") - } - - func testClientNoInputNoOutput() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let awsServer = AWSTestServer(serviceProtocol: .json) - defer { XCTAssertNoThrow(try awsServer.stop()) } - let config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: awsServer.address) - let client = createAWSClient(credentialProvider: .empty, middlewares: [AWSLoggingMiddleware()]) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - - async let asyncOutput: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) - - try awsServer.processRaw { _ in - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response) - } - - try await asyncOutput - } - - func testClientWithInputNoOutput() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - enum InputEnum: String, Codable { - case first - case second - } - struct Input: AWSEncodableShape & Decodable { - let e: InputEnum - let i: [Int64] - } - - let awsServer = AWSTestServer(serviceProtocol: .json) - defer { XCTAssertNoThrow(try awsServer.stop()) } - let config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: awsServer.address) - let client = createAWSClient(credentialProvider: .empty, middlewares: [AWSLoggingMiddleware()]) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let input = Input(e: .second, i: [1, 2, 4, 8]) - - async let asyncOutput: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) - - try awsServer.processRaw { request in - let receivedInput = try JSONDecoder().decode(Input.self, from: request.body) - XCTAssertEqual(receivedInput.e, .second) - XCTAssertEqual(receivedInput.i, [1, 2, 4, 8]) - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response) - } - - try await asyncOutput - } - - func testClientNoInputWithOutput() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - struct Output: AWSDecodableShape & Encodable { - let s: String - let i: Int64 - } - let awsServer = AWSTestServer(serviceProtocol: .json) - defer { XCTAssertNoThrow(try awsServer.stop()) } - - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - let config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: awsServer.address) - let client = createAWSClient( - credentialProvider: .empty, - httpClientProvider: .createNewWithEventLoopGroup(eventLoopGroup) - ) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - async let asyncOutput: Output = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) - - try awsServer.processRaw { _ in - let output = Output(s: "TestOutputString", i: 547) - let byteBuffer = try JSONEncoder().encodeAsByteBuffer(output, allocator: ByteBufferAllocator()) - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: byteBuffer) - return .result(response) - } - - let output = try await asyncOutput - XCTAssertEqual(output.s, "TestOutputString") - XCTAssertEqual(output.i, 547) - } - - func testRequestStreaming(config: AWSServiceConfig, client: AWSClient, server: AWSTestServer, bufferSize: Int, blockSize: Int) async throws { - actor ByteBufferStream: AsyncSequence { - typealias Element = ByteBuffer - - var byteBuffer: ByteBuffer - let blockSize: Int - - init(byteBuffer: ByteBuffer, blockSize: Int) { - self.byteBuffer = byteBuffer - self.blockSize = blockSize - } - - nonisolated func makeAsyncIterator() -> AsyncIterator { - return AsyncIterator(stream: self) - } - - func readSlice() -> ByteBuffer? { - let size = Swift.min(self.byteBuffer.readableBytes, self.blockSize) - if size > 0 { - return self.byteBuffer.readSlice(length: size) - } - return nil - } - - struct AsyncIterator: AsyncIteratorProtocol { - mutating func next() async throws -> ByteBuffer? { - return await self.stream.readSlice() - } - - let stream: ByteBufferStream - } - } - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - struct Input: AWSEncodableShape & AWSShapeWithPayload { - static var _payloadPath: String = "payload" - static var _options: AWSShapeOptions = [.allowStreaming, .rawPayload] - let payload: AWSPayload - private enum CodingKeys: CodingKey {} - } - let data = createRandomBuffer(45, 9182, size: bufferSize) - var byteBuffer = ByteBufferAllocator().buffer(capacity: data.count) - byteBuffer.writeBytes(data) - let stream = ByteBufferStream(byteBuffer: byteBuffer, blockSize: blockSize) - let input = Input(payload: .asyncSequence(stream, size: bufferSize)) - - async let response: () = client.execute( - operation: "test", - path: "/", - httpMethod: .POST, - serviceConfig: config, - input: input, - logger: TestEnvironment.logger - ) - - try? server.processRaw { request in - let bytes = request.body.getBytes(at: 0, length: request.body.readableBytes) - XCTAssertEqual(bytes, data) - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response) - } - - _ = try await response - } - - func testRequestStreaming() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let awsServer = AWSTestServer(serviceProtocol: .json) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - let config = createServiceConfig(endpoint: awsServer.address) - let client = createAWSClient(credentialProvider: .empty, httpClientProvider: .shared(httpClient)) - defer { - XCTAssertNoThrow(try awsServer.stop()) - XCTAssertNoThrow(try client.syncShutdown()) - XCTAssertNoThrow(try httpClient.syncShutdown()) - } - - try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 16 * 1024) - try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 17 * 1024) - try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 18 * 1024, blockSize: 47 * 1024) - } - - func testShutdown() async throws { - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - - let client = createAWSClient(httpClientProvider: .shared(httpClient)) - try await client.shutdown() - let client2 = createAWSClient(httpClientProvider: .createNew) - try await client2.shutdown() - - try await httpClient.shutdown() - } -} diff --git a/Tests/SotoCoreTests/AWSClientTests.swift b/Tests/SotoCoreTests/AWSClientTests.swift index a5827fc6d..edda569c4 100644 --- a/Tests/SotoCoreTests/AWSClientTests.swift +++ b/Tests/SotoCoreTests/AWSClientTests.swift @@ -27,31 +27,27 @@ import SotoXML import XCTest class AWSClientTests: XCTestCase { - func testGetCredential() { + func testGetCredential() async throws { let client = createAWSClient(credentialProvider: .static(accessKeyId: "key", secretAccessKey: "secret")) defer { XCTAssertNoThrow(try client.syncShutdown()) } - do { - let credentialForSignature = try client.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).wait() - XCTAssertEqual(credentialForSignature.accessKeyId, "key") - XCTAssertEqual(credentialForSignature.secretAccessKey, "secret") - } catch { - XCTFail(error.localizedDescription) - } + let credentialForSignature = try await client.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).get() + XCTAssertEqual(credentialForSignature.accessKeyId, "key") + XCTAssertEqual(credentialForSignature.secretAccessKey, "secret") } // this test only really works on Linux as it requires the MetaDataService. On mac it will just pass automatically - func testExpiredCredential() { - let client = createAWSClient() + func testExpiredCredential() async throws { + let client = createAWSClient(credentialProvider: .selector(.ec2, .ecs)) defer { XCTAssertNoThrow(try client.syncShutdown()) } do { - let credentials = try client.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).wait() - print(credentials) + _ = try await client.getCredential(on: client.eventLoopGroup.next(), logger: TestEnvironment.logger).get() + XCTFail("Should not get here") } catch let error as CredentialProviderError where error == .noProvider { // credentials request should fail. One possible error is a connectTimerout } catch { @@ -59,40 +55,23 @@ class AWSClientTests: XCTestCase { } } - func testShutdown() { + func testShutdown() async throws { let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } let client = createAWSClient(httpClientProvider: .shared(httpClient)) - let promise: EventLoopPromise = httpClient.eventLoopGroup.next().makePromise() - client.shutdown { error in - if let error = error { - promise.completeWith(.failure(error)) - } else { - promise.completeWith(.success(())) - } - } - XCTAssertNoThrow(try promise.futureResult.wait()) + try await client.shutdown() } - func testShutdownWithEventLoop() { + func testShutdownWithEventLoop() async throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - let eventLoop = eventLoopGroup.next() let client = createAWSClient(httpClientProvider: .createNewWithEventLoopGroup(eventLoopGroup)) - let promise: EventLoopPromise = eventLoop.makePromise() - client.shutdown { error in - if let error = error { - promise.completeWith(.failure(error)) - } else { - promise.completeWith(.success(())) - } - } - XCTAssertNoThrow(try promise.futureResult.wait()) + try await client.shutdown() } - func testHeadersAreWritten() { + func testHeadersAreWritten() async throws { struct Input: AWSEncodableShape { let content: String } @@ -115,7 +94,7 @@ class AWSClientTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } - let response: EventLoopFuture = client.execute( + async let responseTask: AWSTestServer.HTTPBinResponse = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -125,19 +104,19 @@ class AWSClientTests: XCTestCase { ) XCTAssertNoThrow(try awsServer.httpBin()) - var httpBinResponse: AWSTestServer.HTTPBinResponse? - XCTAssertNoThrow(httpBinResponse = try response.wait()) - let httpHeaders = httpBinResponse.map { HTTPHeaders($0.headers.map { ($0, $1) }) } - - XCTAssertEqual(httpHeaders?["content-length"].first, "18") - XCTAssertEqual(httpHeaders?["content-type"].first, "application/x-amz-json-1.1") - XCTAssertNotNil(httpHeaders?["authorization"].first) - XCTAssertNotNil(httpHeaders?["x-amz-date"].first) - XCTAssertEqual(httpHeaders?["user-agent"].first, "Soto/6.0") - XCTAssertEqual(httpHeaders?["host"].first, "localhost:\(awsServer.serverPort)") + + let httpBinResponse = try await responseTask + let httpHeaders = HTTPHeaders(httpBinResponse.headers.map { ($0, $1) }) + + XCTAssertEqual(httpHeaders["content-length"].first, "18") + XCTAssertEqual(httpHeaders["content-type"].first, "application/x-amz-json-1.1") + XCTAssertNotNil(httpHeaders["authorization"].first) + XCTAssertNotNil(httpHeaders["x-amz-date"].first) + XCTAssertEqual(httpHeaders["user-agent"].first, "Soto/6.0") + XCTAssertEqual(httpHeaders["host"].first, "localhost:\(awsServer.serverPort)") } - func testClientNoInputNoOutput() { + func testClientNoInputNoOutput() async throws { do { let awsServer = AWSTestServer(serviceProtocol: .json) let config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: awsServer.address) @@ -146,20 +125,26 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try awsServer.stop()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute( + operation: "test", + path: "/", + httpMethod: .POST, + serviceConfig: config, + logger: TestEnvironment.logger + ) try awsServer.processRaw { _ in let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) return .result(response) } - try response.wait() + try await responseTask } catch { XCTFail("Unexpected error: \(error)") } } - func testClientWithInputNoOutput() { + func testClientWithInputNoOutput() async throws { enum InputEnum: String, Codable { case first case second @@ -178,7 +163,14 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } let input = Input(e: .second, i: [1, 2, 4, 8]) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute( + operation: "test", + path: "/", + httpMethod: .POST, + serviceConfig: config, + input: input, + logger: TestEnvironment.logger + ) try awsServer.processRaw { request in let receivedInput = try JSONDecoder().decode(Input.self, from: request.body) @@ -188,13 +180,13 @@ class AWSClientTests: XCTestCase { return .result(response) } - try response.wait() + try await responseTask } catch { XCTFail("Unexpected error: \(error)") } } - func testClientNoInputWithOutput() { + func testClientNoInputWithOutput() async throws { struct Output: AWSDecodableShape & Encodable { let s: String let i: Int64 @@ -212,7 +204,13 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try awsServer.stop()) } - let response: EventLoopFuture = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + async let response: Output = client.execute( + operation: "test", + path: "/", + httpMethod: .POST, + serviceConfig: config, + logger: TestEnvironment.logger + ) try awsServer.processRaw { _ in let output = Output(s: "TestOutputString", i: 547) @@ -221,7 +219,7 @@ class AWSClientTests: XCTestCase { return .result(response) } - let output = try response.wait() + let output = try await response XCTAssertEqual(output.s, "TestOutputString") XCTAssertEqual(output.i, 547) @@ -230,7 +228,7 @@ class AWSClientTests: XCTestCase { } } - func testRequestStreaming(config: AWSServiceConfig, client: AWSClient, server: AWSTestServer, bufferSize: Int, blockSize: Int) throws { + func testRequestStreaming(config: AWSServiceConfig, client: AWSClient, server: AWSTestServer, bufferSize: Int, blockSize: Int) async throws { struct Input: AWSEncodableShape & AWSShapeWithPayload { static var _payloadPath: String = "payload" static var _options: AWSShapeOptions = [.allowStreaming, .rawPayload] @@ -251,7 +249,7 @@ class AWSClientTests: XCTestCase { return eventLoop.makeSucceededFuture(.byteBuffer(buffer)) } let input = Input(payload: payload) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) try? server.processRaw { request in let bytes = request.body.getBytes(at: 0, length: request.body.readableBytes) @@ -260,10 +258,10 @@ class AWSClientTests: XCTestCase { return .result(response) } - try response.wait() + try await responseTask } - func testRequestStreaming() { + func testRequestStreaming() async throws { let awsServer = AWSTestServer(serviceProtocol: .json) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) let config = createServiceConfig(endpoint: awsServer.address) @@ -274,12 +272,12 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try httpClient.syncShutdown()) } - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 16 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 17 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 18 * 1024, blockSize: 47 * 1024)) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 16 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 17 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 18 * 1024, blockSize: 47 * 1024) } - func testRequestS3Streaming() { + func testRequestS3Streaming() async throws { let awsServer = AWSTestServer(serviceProtocol: .json) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) let config = createServiceConfig(service: "s3", endpoint: awsServer.address) @@ -290,18 +288,18 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try httpClient.syncShutdown()) } - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 16 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 192 * 1024, blockSize: 128 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 81 * 1024, blockSize: 16 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: S3ChunkedStreamReader.bufferSize)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 130 * 1024, blockSize: S3ChunkedStreamReader.bufferSize)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 17 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 68 * 1024, blockSize: 67 * 1024)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 65537, blockSize: 65537)) - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 65552, blockSize: 65552)) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 16 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 192 * 1024, blockSize: 128 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 81 * 1024, blockSize: 16 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: S3ChunkedStreamReader.bufferSize) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 130 * 1024, blockSize: S3ChunkedStreamReader.bufferSize) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 128 * 1024, blockSize: 17 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 68 * 1024, blockSize: 67 * 1024) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 65537, blockSize: 65537) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 65552, blockSize: 65552) } - func testRequestStreamingAvoidStackOverflow() { + func testRequestStreamingAvoidStackOverflow() async throws { let awsServer = AWSTestServer(serviceProtocol: .json) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) let config = createServiceConfig(service: "s3", endpoint: awsServer.address) @@ -312,10 +310,10 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try httpClient.syncShutdown()) } - XCTAssertNoThrow(try self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 16 * 1024, blockSize: 8)) + try await self.testRequestStreaming(config: config, client: client, server: awsServer, bufferSize: 16 * 1024, blockSize: 8) } - func testRequestStreamingWithPayload(_ payload: AWSPayload) throws { + func testRequestStreamingWithPayload(_ payload: AWSPayload) async throws { struct Input: AWSEncodableShape & AWSShapeWithPayload { static var _payloadPath: String = "payload" static var _options: AWSShapeOptions = [.allowStreaming] @@ -334,26 +332,26 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try httpClient.syncShutdown()) } let input = Input(payload: payload) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) - try response.wait() + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + try await responseTask } - func testRequestStreamingTooMuchData() { + func testRequestStreamingTooMuchData() async throws { // set up stream of 8 bytes but supply more than that let payload = AWSPayload.stream(size: 8) { eventLoop in var buffer = ByteBufferAllocator().buffer(capacity: 0) buffer.writeString("String longer than 8 bytes") return eventLoop.makeSucceededFuture(.byteBuffer(buffer)) } - XCTAssertThrowsError(try self.testRequestStreamingWithPayload(payload)) { error in - guard let error = error as? AWSClient.ClientError, error == .tooMuchData else { - XCTFail() - return - } + do { + try await self.testRequestStreamingWithPayload(payload) + XCTFail("Should not get here") + } catch { + XCTAssertEqual(error as? AWSClient.ClientError, .tooMuchData) } } - func testRequestStreamingNotEnoughData() { + func testRequestStreamingNotEnoughData() async throws { var byteBuffer = ByteBufferAllocator().buffer(staticString: "Buffer") let payload = AWSPayload.stream(size: byteBuffer.readableBytes + 1) { eventLoop in let size = byteBuffer.readableBytes @@ -363,15 +361,15 @@ class AWSClientTests: XCTestCase { let buffer = byteBuffer.readSlice(length: size)! return eventLoop.makeSucceededFuture(.byteBuffer(buffer)) } - XCTAssertThrowsError(try self.testRequestStreamingWithPayload(payload)) { error in - guard let error = error as? AWSClient.ClientError, error == .notEnoughData else { - XCTFail() - return - } + do { + try await self.testRequestStreamingWithPayload(payload) + XCTFail("Should not get here") + } catch { + XCTAssertEqual(error as? AWSClient.ClientError, .notEnoughData) } } - func testRequestStreamingFile() { + func testRequestStreamingFile() async throws { struct Input: AWSEncodableShape & AWSShapeWithPayload { static var _payloadPath: String = "payload" static var _options: AWSShapeOptions = [.allowStreaming] @@ -401,14 +399,14 @@ class AWSClientTests: XCTestCase { let threadPool = NIOThreadPool(numberOfThreads: 3) threadPool.start() let fileIO = NonBlockingFileIO(threadPool: threadPool) - let fileHandle = try fileIO.openFile(path: filename, mode: .read, eventLoop: httpClient.eventLoopGroup.next()).wait() + let fileHandle = try await fileIO.openFile(path: filename, mode: .read, eventLoop: httpClient.eventLoopGroup.next()).get() defer { XCTAssertNoThrow(try fileHandle.close()) XCTAssertNoThrow(try threadPool.syncShutdownGracefully()) } let input = Input(payload: .fileHandle(fileHandle, size: bufferSize, fileIO: fileIO) { size in print(size) }) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) try awsServer.processRaw { request in XCTAssertNil(request.headers["transfer-encoding"]) @@ -419,14 +417,14 @@ class AWSClientTests: XCTestCase { return .result(response) } - try response.wait() + try await responseTask } catch let error as AWSClient.ClientError where error == .tooMuchData { } catch { XCTFail("Unexpected error: \(error)") } } - func testRequestChunkedStreaming() { + func testRequestChunkedStreaming() async throws { struct Input: AWSEncodableShape & AWSShapeWithPayload { static var _payloadPath: String = "payload" static var _options: AWSShapeOptions = [.allowStreaming, .allowChunkedStreaming, .rawPayload] @@ -460,7 +458,7 @@ class AWSClientTests: XCTestCase { } } let input = Input(payload: payload) - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, input: input, logger: TestEnvironment.logger) try awsServer.processRaw { request in let bytes = request.body.getBytes(at: 0, length: request.body.readableBytes) @@ -469,13 +467,13 @@ class AWSClientTests: XCTestCase { return .result(response) } - try response.wait() + try await responseTask } catch { XCTFail("Unexpected error: \(error)") } } - func testProvideHTTPClient() { + func testProvideHTTPClient() async { do { // By default AsyncHTTPClient will follow redirects. This test creates an HTTP client that doesn't follow redirects and // provides it to AWSClient @@ -489,14 +487,14 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try httpClient.syncShutdown()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) try awsServer.processRaw { _ in let response = AWSTestServer.Response(httpStatus: .temporaryRedirect, headers: ["Location": awsServer.address], body: nil) return .result(response) } - try response.wait() + try await responseTask XCTFail("Shouldn't get here as the provided client doesn't follow redirects") } catch let error as AWSRawError { XCTAssertEqual(error.context.message, "Unhandled Error") @@ -505,7 +503,7 @@ class AWSClientTests: XCTestCase { } } - func testServerError() { + func testServerError() async { do { let httpClient = AsyncHTTPClient.HTTPClient(eventLoopGroupProvider: .createNew) let awsServer = AWSTestServer(serviceProtocol: .json) @@ -516,7 +514,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try httpClient.syncShutdown()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) var count = 0 try awsServer.processRaw { _ in @@ -528,7 +526,7 @@ class AWSClientTests: XCTestCase { } } - try response.wait() + try await responseTask } catch let error as AWSServerError { switch error { case .internalFailure: @@ -541,7 +539,7 @@ class AWSClientTests: XCTestCase { } } - func testClientRetry() { + func testClientRetry() async throws { struct Output: AWSDecodableShape, Encodable { let s: String } @@ -555,7 +553,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try httpClient.syncShutdown()) } - let response: EventLoopFuture = client.execute( + async let responseTask: Output = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -576,7 +574,7 @@ class AWSClientTests: XCTestCase { } } - let output = try response.wait() + let output = try await responseTask XCTAssertEqual(output.s, "TestOutputString") } catch { @@ -584,7 +582,7 @@ class AWSClientTests: XCTestCase { } } - func testCustomRetryPolicy() { + func testCustomRetryPolicy() async { final class TestRetryPolicy: RetryPolicy { static let maxRetries: Int = 3 let attempt = ManagedAtomic(0) @@ -609,7 +607,7 @@ class AWSClientTests: XCTestCase { let config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: serverAddress) let client = createAWSClient(credentialProvider: .empty, retryPolicy: .init(retryPolicy: retryPolicy), httpClientProvider: .shared(httpClient)) defer { XCTAssertNoThrow(try client.syncShutdown()) } - let response: EventLoopFuture = client.execute( + async let responseTask: Void = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -627,7 +625,7 @@ class AWSClientTests: XCTestCase { } } - try response.wait() + try await responseTask } catch let error as AWSServerError where error == .serviceUnavailable { XCTAssertEqual(retryPolicy.attempt.load(ordering: .relaxed), TestRetryPolicy.maxRetries) } catch { @@ -635,7 +633,7 @@ class AWSClientTests: XCTestCase { } } - func testClientRetryFail() { + func testClientRetryFail() async { struct Output: AWSDecodableShape, Encodable { let s: String } @@ -649,7 +647,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) XCTAssertNoThrow(try httpClient.syncShutdown()) } - let response: EventLoopFuture = client.execute( + async let responseTask: Output = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -661,7 +659,7 @@ class AWSClientTests: XCTestCase { return .error(.accessDenied, continueProcessing: false) } - let output = try response.wait() + let output = try await responseTask XCTAssertEqual(output.s, "TestOutputString") } catch let error as AWSClientError where error == AWSClientError.accessDenied { @@ -672,7 +670,7 @@ class AWSClientTests: XCTestCase { } /// verify we are not calling the Retry handler when streaming a request - func testDontRetryStreamingRequests() { + func testDontRetryStreamingRequests() async { final class TestRetryPolicy: RetryPolicy { func getRetryWaitTime(error: Error, attempt: Int) -> RetryStatus? { XCTFail("This should not be called as streaming has disabled retries") @@ -698,7 +696,7 @@ class AWSClientTests: XCTestCase { } let payload = AWSPayload.stream { eventLoop in return eventLoop.makeSucceededFuture(.end) } let input = Input(payload: payload) - let response: EventLoopFuture = client.execute( + async let responseTask: Void = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -710,14 +708,14 @@ class AWSClientTests: XCTestCase { return .error(.accessDenied, continueProcessing: false) } - try response.wait() + try await responseTask } catch let error as AWSClientError where error == .accessDenied { } catch { XCTFail("Unexpected error: \(error)") } } - func testClientResponseEventLoop() { + func testClientResponseEventLoop() async { do { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 5) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) @@ -731,7 +729,7 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } let eventLoop = client.eventLoopGroup.next() - let response: EventLoopFuture = client.execute( + async let responseTask: Void = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -743,15 +741,14 @@ class AWSClientTests: XCTestCase { try awsServer.processRaw { _ in return .result(.ok) } - XCTAssertTrue(eventLoop === response.eventLoop) - try response.wait() + try await responseTask } catch { XCTFail("Unexpected error: \(error)") } } - func testStreamingResponse() { + func testStreamingResponse() async { struct Input: AWSEncodableShape {} struct Output: AWSDecodableShape & Encodable { static let _encoding = [AWSMemberEncoding(label: "test", location: .header("test"))] @@ -771,8 +768,8 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) XCTAssertNoThrow(try awsServer.stop()) } - var count = 0 - let response: EventLoopFuture = client.execute( + let countAtomic = ManagedAtomic(0) + async let responseTask: Output = client.execute( operation: "test", path: "/", httpMethod: .GET, @@ -781,10 +778,10 @@ class AWSClientTests: XCTestCase { logger: TestEnvironment.logger ) { (payload: ByteBuffer, eventLoop: EventLoop) in let payloadSize = payload.readableBytes + let count = countAtomic.loadThenWrappingIncrement(by: payloadSize, ordering: .relaxed) let slice = Data(data[count..<(count + payloadSize)]) let payloadData = payload.getData(at: 0, length: payload.readableBytes) XCTAssertEqual(slice, payloadData) - count += payloadSize return eventLoop.makeSucceededFuture(()) } @@ -795,15 +792,15 @@ class AWSClientTests: XCTestCase { return .result(response) } - let result = try response.wait() + let result = try await responseTask XCTAssertEqual(result.test, "TestHeader") - XCTAssertEqual(count, 128 * 1024) + XCTAssertEqual(countAtomic.load(ordering: .relaxed), 128 * 1024) } catch { XCTFail("Unexpected error: \(error)") } } - func testStreamingDelegateFinished() { + func testStreamingDelegateFinished() async throws { struct Input: AWSEncodableShape {} struct Output: AWSDecodableShape & Encodable { static let _encoding = [AWSMemberEncoding(label: "test", location: .header("test"))] @@ -823,9 +820,8 @@ class AWSClientTests: XCTestCase { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) XCTAssertNoThrow(try awsServer.stop()) } - var count = 0 - let lock = NIOLock() - let response: EventLoopFuture = client.execute( + let countAtomic = ManagedAtomic(0) + async let responseTask: Output = client.execute( operation: "test", path: "/", httpMethod: .GET, @@ -833,9 +829,9 @@ class AWSClientTests: XCTestCase { input: Input(), logger: TestEnvironment.logger ) { (_: ByteBuffer, eventLoop: EventLoop) in - lock.withLock { count += 1 } + countAtomic.wrappingIncrement(by: 1, ordering: .relaxed) return eventLoop.scheduleTask(in: .milliseconds(200)) { - lock.withLock { count -= 1 } + countAtomic.wrappingDecrement(by: 1, ordering: .relaxed) }.futureResult } @@ -846,7 +842,7 @@ class AWSClientTests: XCTestCase { return .result(response) }) - XCTAssertNoThrow(_ = try response.wait()) - XCTAssertEqual(count, 0) + _ = try await responseTask + XCTAssertEqual(countAtomic.load(ordering: .relaxed), 0) } } diff --git a/Tests/SotoCoreTests/AWSServiceTests+async.swift b/Tests/SotoCoreTests/AWSServiceTests+async.swift deleted file mode 100644 index 8ca8828e5..000000000 --- a/Tests/SotoCoreTests/AWSServiceTests+async.swift +++ /dev/null @@ -1,72 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Soto for AWS open source project -// -// Copyright (c) 2017-2021 the Soto project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Soto project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -@testable import SotoCore -import SotoTestUtils -import XCTest - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -final class AWSServiceAsyncTests: XCTestCase { - struct TestService: AWSService { - var client: AWSClient - var config: AWSServiceConfig - - /// init - init(client: AWSClient, config: AWSServiceConfig) { - self.client = client - self.config = config - } - - /// patch init - init(from: Self, patch: AWSServiceConfig.Patch) { - self.client = from.client - self.config = from.config.with(patch: patch) - } - } - - func testSignURL() async throws { - let client = createAWSClient(credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar")) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let serviceConfig = createServiceConfig() - let service = TestService(client: client, config: serviceConfig) - let url = URL(string: "https://test.amazonaws.com?test2=true&space=sp%20ace&percent=addi+tion")! - let signedURL = try await service.signURL(url: url, httpMethod: .GET, expires: .minutes(15)) - // remove signed query params - let query = try XCTUnwrap(signedURL.query) - let queryItems = query - .split(separator: "&") - .compactMap { - guard !$0.hasPrefix("X-Amz") else { return nil } - return String($0) - } - .joined(separator: "&") - XCTAssertEqual(queryItems, "percent=addi%2Btion&space=sp%20ace&test2=true") - } - - func testSignHeaders() async throws { - let client = createAWSClient(credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar")) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let serviceConfig = createServiceConfig() - let service = TestService(client: client, config: serviceConfig) - let url = URL(string: "https://test.amazonaws.com?test2=true&space=sp%20ace&percent=addi+tion")! - let headers = try await service.signHeaders( - url: url, - httpMethod: .GET, - headers: ["Content-Type": "application/json"], - body: .string("Test payload") - ) - // remove signed query params - XCTAssertNotNil(headers["Authorization"].first) - } -} diff --git a/Tests/SotoCoreTests/AWSServiceTests.swift b/Tests/SotoCoreTests/AWSServiceTests.swift index 2cd348b5a..812f4a2db 100644 --- a/Tests/SotoCoreTests/AWSServiceTests.swift +++ b/Tests/SotoCoreTests/AWSServiceTests.swift @@ -129,4 +129,39 @@ class AWSServiceTests: XCTestCase { XCTAssertEqual(service.region, service2.region) XCTAssertEqual(service.endpoint, service2.endpoint) } + + func testSignURL() async throws { + let client = createAWSClient(credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar")) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let serviceConfig = createServiceConfig() + let service = TestService(client: client, config: serviceConfig) + let url = URL(string: "https://test.amazonaws.com?test2=true&space=sp%20ace&percent=addi+tion")! + let signedURL = try await service.signURL(url: url, httpMethod: .GET, expires: .minutes(15)) + // remove signed query params + let query = try XCTUnwrap(signedURL.query) + let queryItems = query + .split(separator: "&") + .compactMap { + guard !$0.hasPrefix("X-Amz") else { return nil } + return String($0) + } + .joined(separator: "&") + XCTAssertEqual(queryItems, "percent=addi%2Btion&space=sp%20ace&test2=true") + } + + func testSignHeaders() async throws { + let client = createAWSClient(credentialProvider: .static(accessKeyId: "foo", secretAccessKey: "bar")) + defer { XCTAssertNoThrow(try client.syncShutdown()) } + let serviceConfig = createServiceConfig() + let service = TestService(client: client, config: serviceConfig) + let url = URL(string: "https://test.amazonaws.com?test2=true&space=sp%20ace&percent=addi+tion")! + let headers = try await service.signHeaders( + url: url, + httpMethod: .GET, + headers: ["Content-Type": "application/json"], + body: .string("Test payload") + ) + // remove signed query params + XCTAssertNotNil(headers["Authorization"].first) + } } diff --git a/Tests/SotoCoreTests/Credential/ConfigFileCredentialProviderTests.swift b/Tests/SotoCoreTests/Credential/ConfigFileCredentialProviderTests.swift index aed094626..74d0e9756 100644 --- a/Tests/SotoCoreTests/Credential/ConfigFileCredentialProviderTests.swift +++ b/Tests/SotoCoreTests/Credential/ConfigFileCredentialProviderTests.swift @@ -31,11 +31,11 @@ class ConfigFileCredentialProviderTests: XCTestCase { return (.init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init()), eventLoopGroup, httpClient) } - func testCredentialProviderStatic() { + func testCredentialProviderStatic() async throws { let credentials = ConfigFileLoader.SharedCredentials.staticCredential(credential: StaticCredential(accessKeyId: "foo", secretAccessKey: "bar")) let (context, eventLoopGroup, httpClient) = self.makeContext() - let provider = try? ConfigFileCredentialProvider.credentialProvider( + let provider = try ConfigFileCredentialProvider.credentialProvider( from: credentials, context: context, endpoint: nil @@ -43,12 +43,12 @@ class ConfigFileCredentialProviderTests: XCTestCase { XCTAssertEqual((provider as? StaticCredential)?.accessKeyId, "foo") XCTAssertEqual((provider as? StaticCredential)?.secretAccessKey, "bar") - XCTAssertNoThrow(try provider?.shutdown(on: context.eventLoop).wait()) - XCTAssertNoThrow(try httpClient.syncShutdown()) + try await provider.shutdown(on: context.eventLoop).get() + try await httpClient.shutdown() XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - func testCredentialProviderSTSAssumeRole() { + func testCredentialProviderSTSAssumeRole() async throws { let credentials = ConfigFileLoader.SharedCredentials.assumeRole( roleArn: "arn", sessionName: "baz", @@ -57,7 +57,7 @@ class ConfigFileCredentialProviderTests: XCTestCase { ) let (context, eventLoopGroup, httpClient) = self.makeContext() - let provider = try? ConfigFileCredentialProvider.credentialProvider( + let provider = try ConfigFileCredentialProvider.credentialProvider( from: credentials, context: context, endpoint: nil @@ -65,14 +65,14 @@ class ConfigFileCredentialProviderTests: XCTestCase { XCTAssertTrue(provider is STSAssumeRoleCredentialProvider) XCTAssertEqual((provider as? STSAssumeRoleCredentialProvider)?.request.roleArn, "arn") - XCTAssertNoThrow(try provider?.shutdown(on: context.eventLoop).wait()) - XCTAssertNoThrow(try httpClient.syncShutdown()) + try await provider.shutdown(on: context.eventLoop).get() + try await httpClient.shutdown() XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } // MARK: - Config File Credentials Provider - func testConfigFileSuccess() { + func testConfigFileSuccess() async throws { let credentials = """ [default] aws_access_key_id = AWSACCESSKEYID @@ -92,13 +92,12 @@ class ConfigFileCredentialProviderTests: XCTestCase { let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init())) - var credential: Credential? - XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) - XCTAssertEqual(credential?.accessKeyId, "AWSACCESSKEYID") - XCTAssertEqual(credential?.secretAccessKey, "AWSSECRETACCESSKEY") + let credential: Credential = try await provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() + XCTAssertEqual(credential.accessKeyId, "AWSACCESSKEYID") + XCTAssertEqual(credential.secretAccessKey, "AWSSECRETACCESSKEY") } - func testAWSProfileConfigFile() { + func testAWSProfileConfigFile() async throws { let credentials = """ [test-profile] aws_access_key_id = TESTPROFILE-AWSACCESSKEYID @@ -121,13 +120,12 @@ class ConfigFileCredentialProviderTests: XCTestCase { let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init())) - var credential: Credential? - XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) - XCTAssertEqual(credential?.accessKeyId, "TESTPROFILE-AWSACCESSKEYID") - XCTAssertEqual(credential?.secretAccessKey, "TESTPROFILE-AWSSECRETACCESSKEY") + let credential: Credential = try await provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() + XCTAssertEqual(credential.accessKeyId, "TESTPROFILE-AWSACCESSKEYID") + XCTAssertEqual(credential.secretAccessKey, "TESTPROFILE-AWSSECRETACCESSKEY") } - func testConfigFileNotAvailable() { + func testConfigFileNotAvailable() async { let filename = #function let filenameURL = URL(fileURLWithPath: filename) @@ -140,13 +138,15 @@ class ConfigFileCredentialProviderTests: XCTestCase { let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init())) - XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) { error in - print("\(error)") + do { + _ = try await provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() + XCTFail("Should throw error") + } catch { XCTAssertEqual(error as? CredentialProviderError, .noProvider) } } - func testCredentialProviderSourceEnvironment() throws { + func testCredentialProviderSourceEnvironment() async throws { let accessKey = "AKIAIOSFODNN7EXAMPLE" let secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" let profile = "marketingadmin" @@ -174,16 +174,16 @@ class ConfigFileCredentialProviderTests: XCTestCase { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - let sharedCredentials = try ConfigFileLoader.loadSharedCredentials( + let sharedCredentials = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: filename, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() switch sharedCredentials { case .assumeRole(let aRoleArn, _, _, let sourceCredentialProvider): - let credentials = try sourceCredentialProvider.createProvider(context: context).getCredential(on: context.eventLoop, logger: context.logger).wait() + let credentials = try await sourceCredentialProvider.createProvider(context: context).getCredential(on: context.eventLoop, logger: context.logger).get() XCTAssertEqual(credentials.accessKeyId, accessKey) XCTAssertEqual(credentials.secretAccessKey, secretKey) XCTAssertEqual(aRoleArn, roleArn) @@ -199,7 +199,7 @@ class ConfigFileCredentialProviderTests: XCTestCase { // MARK: - Role ARN Credential - func testRoleARNSourceProfile() throws { + func testRoleARNSourceProfile() async throws { let profile = "user1" // Prepare mock STSAssumeRole credentials @@ -254,10 +254,11 @@ class ConfigFileCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } // Retrieve credentials - let futureCredentials = client.credentialProvider.getCredential( + async let futureCredentials: Credential = client.credentialProvider.getCredential( on: client.eventLoopGroup.next(), logger: TestEnvironment.logger - ) + ).get() + try testServer.processRaw { _ in let output = STSAssumeRoleResponse(credentials: stsCredentials) let xml = try XMLEncoder().encode(output) @@ -265,7 +266,7 @@ class ConfigFileCredentialProviderTests: XCTestCase { let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: byteBuffer) return .result(response) } - let credentials = try futureCredentials.wait() + let credentials = try await futureCredentials // Verify credentials match those returned from STS Assume Role operation XCTAssertEqual(credentials.accessKeyId, stsCredentials.accessKeyId) diff --git a/Tests/SotoCoreTests/Credential/ConfigFileLoaderTests.swift b/Tests/SotoCoreTests/Credential/ConfigFileLoaderTests.swift index 4ea1f143a..ebce90ac0 100644 --- a/Tests/SotoCoreTests/Credential/ConfigFileLoaderTests.swift +++ b/Tests/SotoCoreTests/Credential/ConfigFileLoaderTests.swift @@ -37,7 +37,7 @@ class ConfigFileLoadersTests: XCTestCase { return filepath } - func testLoadFileJustCredentials() throws { + func testLoadFileJustCredentials() async throws { let accessKey = "AKIAIOSFODNN7EXAMPLE" let secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" let profile = ConfigFileLoader.defaultProfile @@ -56,12 +56,12 @@ class ConfigFileLoadersTests: XCTestCase { try? eventLoopGroup.syncShutdownGracefully() } - let sharedCredentials = try ConfigFileLoader.loadSharedCredentials( + let sharedCredentials = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() switch sharedCredentials { case .staticCredential(let credentials): @@ -72,7 +72,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileCredentialsAndConfig() throws { + func testLoadFileCredentialsAndConfig() async throws { let accessKey = "AKIAIOSFODNN7EXAMPLE" let secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" let profile = "marketingadmin" @@ -104,18 +104,18 @@ class ConfigFileLoadersTests: XCTestCase { try? eventLoopGroup.syncShutdownGracefully() } - let sharedCredentials = try ConfigFileLoader.loadSharedCredentials( + let sharedCredentials = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: configPath, profile: profile, context: context - ).wait() + ).get() defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } switch sharedCredentials { case .assumeRole(let aRoleArn, let aSessionName, let region, let sourceCredentialProvider): - let credentials = try sourceCredentialProvider.createProvider(context: context).getCredential(on: context.eventLoop, logger: context.logger).wait() + let credentials = try await sourceCredentialProvider.createProvider(context: context).getCredential(on: context.eventLoop, logger: context.logger).get() XCTAssertEqual(credentials.accessKeyId, accessKey) XCTAssertEqual(credentials.secretAccessKey, secretKey) XCTAssertEqual(aRoleArn, roleArn) @@ -126,7 +126,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileConfigNotFound() throws { + func testLoadFileConfigNotFound() async throws { let profile = "marketingadmin" let roleArn = "arn:aws:iam::123456789012:role/marketingadminrole" let credentialsFile = """ @@ -144,12 +144,12 @@ class ConfigFileLoadersTests: XCTestCase { try? eventLoopGroup.syncShutdownGracefully() } - let sharedCredentials = try ConfigFileLoader.loadSharedCredentials( + let sharedCredentials = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "non-existing-file-path", profile: profile, context: context - ).wait() + ).get() switch sharedCredentials { case .assumeRole(let aRoleArn, _, _, let source): @@ -162,7 +162,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileMissingAccessKey() throws { + func testLoadFileMissingAccessKey() async throws { let secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" let profile = ConfigFileLoader.defaultProfile let credentialsFile = """ @@ -180,12 +180,12 @@ class ConfigFileLoadersTests: XCTestCase { } do { - _ = try ConfigFileLoader.loadSharedCredentials( + _ = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() } catch ConfigFileLoader.ConfigFileError.missingAccessKeyId { // Pass } catch { @@ -193,7 +193,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileMissingSecretKey() throws { + func testLoadFileMissingSecretKey() async throws { let accessKey = "AKIAIOSFODNN7EXAMPLE" let profile = ConfigFileLoader.defaultProfile let credentialsFile = """ @@ -211,12 +211,12 @@ class ConfigFileLoadersTests: XCTestCase { } do { - _ = try ConfigFileLoader.loadSharedCredentials( + _ = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() } catch ConfigFileLoader.ConfigFileError.missingSecretAccessKey { // Pass } catch { @@ -224,7 +224,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileMissingSourceAccessKey() throws { + func testLoadFileMissingSourceAccessKey() async throws { let secretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" let profile = "marketingadmin" let sourceProfile = ConfigFileLoader.defaultProfile @@ -247,12 +247,12 @@ class ConfigFileLoadersTests: XCTestCase { } do { - _ = try ConfigFileLoader.loadSharedCredentials( + _ = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() } catch ConfigFileLoader.ConfigFileError.missingAccessKeyId { // Pass } catch { @@ -260,7 +260,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileMissingSourceSecretKey() throws { + func testLoadFileMissingSourceSecretKey() async throws { let accessKey = "AKIAIOSFODNN7EXAMPLE" let profile = "marketingadmin" let sourceProfile = ConfigFileLoader.defaultProfile @@ -283,12 +283,12 @@ class ConfigFileLoadersTests: XCTestCase { } do { - _ = try ConfigFileLoader.loadSharedCredentials( + _ = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() } catch ConfigFileLoader.ConfigFileError.missingSecretAccessKey { // Pass } catch { @@ -296,7 +296,7 @@ class ConfigFileLoadersTests: XCTestCase { } } - func testLoadFileRoleArnOnly() throws { + func testLoadFileRoleArnOnly() async throws { let profile = "marketingadmin" let roleArn = "arn:aws:iam::123456789012:role/marketingadminrole" let credentialsFile = """ @@ -314,12 +314,12 @@ class ConfigFileLoadersTests: XCTestCase { } do { - _ = try ConfigFileLoader.loadSharedCredentials( + _ = try await ConfigFileLoader.loadSharedCredentials( credentialsFilePath: credentialsPath, configFilePath: "/dev/null", profile: profile, context: context - ).wait() + ).get() } catch ConfigFileLoader.ConfigFileError.invalidCredentialFile { // Pass } catch { diff --git a/Tests/SotoCoreTests/Credential/CredentialProviderTests+async.swift b/Tests/SotoCoreTests/Credential/CredentialProviderTests+async.swift deleted file mode 100644 index 455d61540..000000000 --- a/Tests/SotoCoreTests/Credential/CredentialProviderTests+async.swift +++ /dev/null @@ -1,42 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Soto for AWS open source project -// -// Copyright (c) 2017-2021 the Soto project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Soto project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import NIOCore -import SotoCore -import SotoTestUtils -import XCTest - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -class AsyncCredentialProviderTests: XCTestCase { - func testAsyncCredentialProvider() { - struct TestAsyncProvider: AsyncCredentialProvider { - func getCredential(on eventLoop: EventLoop, logger: Logger) async throws -> Credential { - return StaticCredential(accessKeyId: "abc", secretAccessKey: "123", sessionToken: "xyz") - } - } - let client = AWSClient( - credentialProvider: .custom { _ in TestAsyncProvider() }, - httpClientProvider: .createNew - ) - defer { XCTAssertNoThrow(try client.syncShutdown()) } - let credentialsFuture = client.credentialProvider.getCredential( - on: client.eventLoopGroup.next(), - logger: TestEnvironment.logger - ).map { credential in - XCTAssertEqual(credential.accessKeyId, "abc") - XCTAssertEqual(credential.secretAccessKey, "123") - } - XCTAssertNoThrow(try credentialsFuture.wait()) - } -} diff --git a/Tests/SotoCoreTests/Credential/CredentialProviderTests.swift b/Tests/SotoCoreTests/Credential/CredentialProviderTests.swift index 0ec2c8095..b8a71e3c8 100644 --- a/Tests/SotoCoreTests/Credential/CredentialProviderTests.swift +++ b/Tests/SotoCoreTests/Credential/CredentialProviderTests.swift @@ -22,28 +22,26 @@ import SotoTestUtils import XCTest final class CredentialProviderTests: XCTestCase { - func testCredentialProvider() { - let cred = StaticCredential(accessKeyId: "abc", secretAccessKey: "123", sessionToken: "xyz") + func testCredentialProvider() async throws { + let provider = StaticCredential(accessKeyId: "abc", secretAccessKey: "123", sessionToken: "xyz") let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } let loop = group.next() - var returned: Credential? - XCTAssertNoThrow(returned = try cred.getCredential(on: loop, logger: TestEnvironment.logger).wait()) - - XCTAssertEqual(returned as? StaticCredential, cred) + let credential = try await provider.getCredential(on: loop, logger: TestEnvironment.logger).get() + XCTAssertEqual(credential as? StaticCredential, provider) } // make sure getCredential in client CredentialProvider doesnt get called more than once - func testDeferredCredentialProvider() { - final class MyCredentialProvider: CredentialProvider { + func testDeferredCredentialProvider() async throws { + final class MyCredentialProvider: AsyncCredentialProvider { let credentialProviderCalled = ManagedAtomic(0) init() {} - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { + func getCredential(on eventLoop: EventLoop, logger: Logger) async throws -> Credential { self.credentialProviderCalled.wrappingIncrement(ordering: .sequentiallyConsistent) - return eventLoop.makeSucceededFuture(StaticCredential(accessKeyId: "ACCESSKEYID", secretAccessKey: "SECRETACCESSKET")) + return StaticCredential(accessKeyId: "ACCESSKEYID", secretAccessKey: "SECRETACCESSKET") } } let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -54,12 +52,12 @@ final class CredentialProviderTests: XCTestCase { let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init()) let myCredentialProvider = MyCredentialProvider() let deferredProvider = DeferredCredentialProvider(context: context, provider: myCredentialProvider) - XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) - XCTAssertNoThrow(_ = try deferredProvider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) + _ = try await deferredProvider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() + _ = try await deferredProvider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() XCTAssertEqual(myCredentialProvider.credentialProviderCalled.load(ordering: .sequentiallyConsistent), 1) } - func testConfigFileSuccess() { + func testConfigFileSuccess() async throws { let credentials = """ [default] aws_access_key_id = AWSACCESSKEYID @@ -79,13 +77,12 @@ final class CredentialProviderTests: XCTestCase { let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init())) - var credential: Credential? - XCTAssertNoThrow(credential = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) - XCTAssertEqual(credential?.accessKeyId, "AWSACCESSKEYID") - XCTAssertEqual(credential?.secretAccessKey, "AWSSECRETACCESSKEY") + let credential = try await provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() + XCTAssertEqual(credential.accessKeyId, "AWSACCESSKEYID") + XCTAssertEqual(credential.secretAccessKey, "AWSSECRETACCESSKEY") } - func testConfigFileNotAvailable() { + func testConfigFileNotAvailable() async throws { let filename = "credentials_not_existing" let filenameURL = URL(fileURLWithPath: filename) @@ -98,13 +95,15 @@ final class CredentialProviderTests: XCTestCase { let provider = factory.createProvider(context: .init(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init())) - XCTAssertThrowsError(_ = try provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).wait()) { error in - print("\(error)") + do { + _ = try await provider.getCredential(on: eventLoop, logger: TestEnvironment.logger).get() + XCTFail("Should provide credential") + } catch { XCTAssertEqual(error as? CredentialProviderError, .noProvider) } } - func testCredentialSelectorShutdown() { + func testCredentialSelectorShutdown() async throws { final class TestCredentialProvider: CredentialProvider { let hasShutdown = ManagedAtomic(false) init() {} @@ -125,9 +124,9 @@ final class CredentialProviderTests: XCTestCase { let eventLoop = eventLoopGroup.next() let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: eventLoop, logger: TestEnvironment.logger, options: .init()) let testCredentialProvider = TestCredentialProvider() - let deferredProvider = DeferredCredentialProvider(context: context, provider: testCredentialProvider) - XCTAssertNoThrow(try deferredProvider.shutdown(on: eventLoopGroup.next()).wait()) + let deferredProvider = DeferredCredentialProvider(context: context, provider: testCredentialProvider) + try await deferredProvider.shutdown(on: eventLoopGroup.next()).get() XCTAssertEqual(testCredentialProvider.hasShutdown.load(ordering: .sequentiallyConsistent), true) } } diff --git a/Tests/SotoCoreTests/Credential/MetaDataCredentialProviderTests.swift b/Tests/SotoCoreTests/Credential/MetaDataCredentialProviderTests.swift index 47036a00c..5e92540e1 100644 --- a/Tests/SotoCoreTests/Credential/MetaDataCredentialProviderTests.swift +++ b/Tests/SotoCoreTests/Credential/MetaDataCredentialProviderTests.swift @@ -22,7 +22,7 @@ import XCTest class MetaDataCredentialProviderTests: XCTestCase { // MARK: - ECSMetaDataClient - - func testECSMetaDataClient() { + func testECSMetaDataClient() async throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } @@ -37,18 +37,17 @@ class MetaDataCredentialProviderTests: XCTestCase { defer { Environment.unset(name: ECSMetaDataClient.RelativeURIEnvironmentName) } let client = ECSMetaDataClient(httpClient: httpClient, host: testServer.address) - let future = client!.getMetaData(on: loop, logger: TestEnvironment.logger) + async let metaDataTask = client!.getMetaData(on: loop, logger: TestEnvironment.logger).get() + // run fake server XCTAssertNoThrow(try testServer.ecsMetadataServer(path: path)) - var metaData: ECSMetaDataClient.MetaData? - XCTAssertNoThrow(metaData = try future.wait()) - - XCTAssertEqual(metaData?.accessKeyId, AWSTestServer.ECSMetaData.default.accessKeyId) - XCTAssertEqual(metaData?.secretAccessKey, AWSTestServer.ECSMetaData.default.secretAccessKey) - XCTAssertEqual(metaData?.token, AWSTestServer.ECSMetaData.default.token) - XCTAssertEqual(metaData?.expiration.description, AWSTestServer.ECSMetaData.default.expiration.description) - XCTAssertEqual(metaData?.roleArn, AWSTestServer.ECSMetaData.default.roleArn) + let metaData = try await metaDataTask + XCTAssertEqual(metaData.accessKeyId, AWSTestServer.ECSMetaData.default.accessKeyId) + XCTAssertEqual(metaData.secretAccessKey, AWSTestServer.ECSMetaData.default.secretAccessKey) + XCTAssertEqual(metaData.token, AWSTestServer.ECSMetaData.default.token) + XCTAssertEqual(metaData.expiration.description, AWSTestServer.ECSMetaData.default.expiration.description) + XCTAssertEqual(metaData.roleArn, AWSTestServer.ECSMetaData.default.roleArn) } func testECSMetaDataClientDefaultHost() { @@ -71,7 +70,7 @@ class MetaDataCredentialProviderTests: XCTestCase { // MARK: - InstanceMetaDataClient - - func testEC2InstanceMetaDataClientUsingVersion2() { + func testEC2InstanceMetaDataClientUsingVersion2() async throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } @@ -86,23 +85,22 @@ class MetaDataCredentialProviderTests: XCTestCase { defer { Environment.unset(name: ECSMetaDataClient.RelativeURIEnvironmentName) } let client = InstanceMetaDataClient(httpClient: httpClient, host: testServer.address) - let future = client.getMetaData(on: loop, logger: TestEnvironment.logger) - + async let metaDataTask = client.getMetaData(on: loop, logger: TestEnvironment.logger).get() + // run fake server XCTAssertNoThrow(try testServer.ec2MetadataServer(version: .v2)) - var metaData: InstanceMetaDataClient.MetaData? - XCTAssertNoThrow(metaData = try future.wait()) + let metaData = try await metaDataTask - XCTAssertEqual(metaData?.accessKeyId, AWSTestServer.EC2InstanceMetaData.default.accessKeyId) - XCTAssertEqual(metaData?.secretAccessKey, AWSTestServer.EC2InstanceMetaData.default.secretAccessKey) - XCTAssertEqual(metaData?.token, AWSTestServer.EC2InstanceMetaData.default.token) - XCTAssertEqual(metaData?.expiration.description, AWSTestServer.EC2InstanceMetaData.default.expiration.description) - XCTAssertEqual(metaData?.code, AWSTestServer.EC2InstanceMetaData.default.code) - XCTAssertEqual(metaData?.lastUpdated, AWSTestServer.EC2InstanceMetaData.default.lastUpdated) - XCTAssertEqual(metaData?.type, AWSTestServer.EC2InstanceMetaData.default.type) + XCTAssertEqual(metaData.accessKeyId, AWSTestServer.EC2InstanceMetaData.default.accessKeyId) + XCTAssertEqual(metaData.secretAccessKey, AWSTestServer.EC2InstanceMetaData.default.secretAccessKey) + XCTAssertEqual(metaData.token, AWSTestServer.EC2InstanceMetaData.default.token) + XCTAssertEqual(metaData.expiration.description, AWSTestServer.EC2InstanceMetaData.default.expiration.description) + XCTAssertEqual(metaData.code, AWSTestServer.EC2InstanceMetaData.default.code) + XCTAssertEqual(metaData.lastUpdated, AWSTestServer.EC2InstanceMetaData.default.lastUpdated) + XCTAssertEqual(metaData.type, AWSTestServer.EC2InstanceMetaData.default.type) } - func testEC2InstanceMetaDataClientUsingVersion1() { + func testEC2InstanceMetaDataClientUsingVersion1() async throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } @@ -113,20 +111,20 @@ class MetaDataCredentialProviderTests: XCTestCase { defer { XCTAssertNoThrow(try testServer.stop()) } let client = InstanceMetaDataClient(httpClient: httpClient, host: testServer.address) - let future = client.getMetaData(on: loop, logger: TestEnvironment.logger) + async let metaDataTask = client.getMetaData(on: loop, logger: TestEnvironment.logger).get() + // run fake server XCTAssertNoThrow(try testServer.ec2MetadataServer(version: .v1)) - var metaData: InstanceMetaDataClient.MetaData? - XCTAssertNoThrow(metaData = try future.wait()) + let metaData = try await metaDataTask - XCTAssertEqual(metaData?.accessKeyId, AWSTestServer.EC2InstanceMetaData.default.accessKeyId) - XCTAssertEqual(metaData?.secretAccessKey, AWSTestServer.EC2InstanceMetaData.default.secretAccessKey) - XCTAssertEqual(metaData?.token, AWSTestServer.EC2InstanceMetaData.default.token) - XCTAssertEqual(metaData?.expiration.description, AWSTestServer.EC2InstanceMetaData.default.expiration.description) - XCTAssertEqual(metaData?.code, AWSTestServer.EC2InstanceMetaData.default.code) - XCTAssertEqual(metaData?.lastUpdated, AWSTestServer.EC2InstanceMetaData.default.lastUpdated) - XCTAssertEqual(metaData?.type, AWSTestServer.EC2InstanceMetaData.default.type) + XCTAssertEqual(metaData.accessKeyId, AWSTestServer.EC2InstanceMetaData.default.accessKeyId) + XCTAssertEqual(metaData.secretAccessKey, AWSTestServer.EC2InstanceMetaData.default.secretAccessKey) + XCTAssertEqual(metaData.token, AWSTestServer.EC2InstanceMetaData.default.token) + XCTAssertEqual(metaData.expiration.description, AWSTestServer.EC2InstanceMetaData.default.expiration.description) + XCTAssertEqual(metaData.code, AWSTestServer.EC2InstanceMetaData.default.code) + XCTAssertEqual(metaData.lastUpdated, AWSTestServer.EC2InstanceMetaData.default.lastUpdated) + XCTAssertEqual(metaData.type, AWSTestServer.EC2InstanceMetaData.default.type) } func testEC2UInstanceMetaDataClientDefaultHost() { diff --git a/Tests/SotoCoreTests/Credential/RotatingCredentialProviderTests.swift b/Tests/SotoCoreTests/Credential/RotatingCredentialProviderTests.swift index 43d14b122..48c10f2fd 100644 --- a/Tests/SotoCoreTests/Credential/RotatingCredentialProviderTests.swift +++ b/Tests/SotoCoreTests/Credential/RotatingCredentialProviderTests.swift @@ -27,22 +27,20 @@ import SotoTestUtils import XCTest class RotatingCredentialProviderTests: XCTestCase { - final class MetaDataTestClient: CredentialProvider { - typealias TestCallback = @Sendable (EventLoop) -> EventLoopFuture + final class RotatingCredentialTestClient: AsyncCredentialProvider { + typealias TestCallback = @Sendable () -> ExpiringCredential let callback: TestCallback init(_ callback: @escaping TestCallback) { self.callback = callback } - func getCredential(on eventLoop: EventLoop, logger: Logger) -> EventLoopFuture { - eventLoop.flatSubmit { - return self.callback(eventLoop).map { $0 } - } + func getCredential(on eventLoop: EventLoop, logger: Logger) async throws -> Credential { + self.callback() } } - func testGetCredentialAndReuseIfStillValid() { + func testGetCredentialAndReuseIfStillValid() async throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } let httpClient = HTTPClient(eventLoopGroupProvider: .shared(group)) @@ -57,34 +55,34 @@ class RotatingCredentialProviderTests: XCTestCase { ) let count = ManagedAtomic(0) - let client = MetaDataTestClient { + let client = RotatingCredentialTestClient { count.wrappingIncrement(ordering: .sequentiallyConsistent) - return $0.makeSucceededFuture(cred) + return cred } let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: Logger(label: "soto"), options: .init()) let provider = RotatingCredentialProvider(context: context, provider: client) // get credentials for first time - var returned: Credential? - XCTAssertNoThrow(returned = try provider.getCredential(on: loop, logger: Logger(label: "soto")).wait()) + var returned = try await provider.getCredential(on: loop, logger: Logger(label: "soto")).get() - XCTAssertEqual(returned?.accessKeyId, cred.accessKeyId) - XCTAssertEqual(returned?.secretAccessKey, cred.secretAccessKey) - XCTAssertEqual(returned?.sessionToken, cred.sessionToken) + XCTAssertEqual(returned.accessKeyId, cred.accessKeyId) + XCTAssertEqual(returned.secretAccessKey, cred.secretAccessKey) + XCTAssertEqual(returned.sessionToken, cred.sessionToken) XCTAssertEqual((returned as? TestExpiringCredential)?.expiration, cred.expiration) // get credentials a second time, callback must not be hit - XCTAssertNoThrow(returned = try provider.getCredential(on: loop, logger: Logger(label: "soto")).wait()) - XCTAssertEqual(returned?.accessKeyId, cred.accessKeyId) - XCTAssertEqual(returned?.secretAccessKey, cred.secretAccessKey) - XCTAssertEqual(returned?.sessionToken, cred.sessionToken) + returned = try await provider.getCredential(on: loop, logger: Logger(label: "soto")).get() + + XCTAssertEqual(returned.accessKeyId, cred.accessKeyId) + XCTAssertEqual(returned.secretAccessKey, cred.secretAccessKey) + XCTAssertEqual(returned.sessionToken, cred.sessionToken) XCTAssertEqual((returned as? TestExpiringCredential)?.expiration, cred.expiration) // ensure callback was only hit once XCTAssertEqual(count.load(ordering: .sequentiallyConsistent), 1) } - func testGetCredentialHighlyConcurrent() { + func testGetCredentialHighlyConcurrent() async throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } let httpClient = HTTPClient(eventLoopGroupProvider: .shared(group)) @@ -98,56 +96,36 @@ class RotatingCredentialProviderTests: XCTestCase { expiration: Date(timeIntervalSinceNow: 60 * 5) ) - let promise = loop.makePromise(of: ExpiringCredential.self) - let count = ManagedAtomic(0) let count2 = ManagedAtomic(0) - let client = MetaDataTestClient { _ in + let client = RotatingCredentialTestClient { count.wrappingIncrement(ordering: .sequentiallyConsistent) - return promise.futureResult + return cred } let context = CredentialProviderFactory.Context(httpClient: httpClient, eventLoop: loop, logger: TestEnvironment.logger, options: .init()) let provider = RotatingCredentialProvider(context: context, provider: client) - var resultFutures = [EventLoopFuture]() - var setupFutures = [EventLoopFuture]() let iterations = 500 - for _ in 0.. = loop.flatSubmit { - // this should be executed right away - defer { - setupPromise.succeed(()) - } - - return provider.getCredential(on: loop, logger: TestEnvironment.logger).map { returned in + try await withThrowingTaskGroup(of: Void.self) { group in + for _ in 0.. EventLoopFuture { - self.getEndpointsCalledCount.wrappingIncrement(ordering: .sequentiallyConsistent) - return eventLoop.scheduleTask(in: .milliseconds(200)) { - return AWSEndpoints(endpoints: [.init(address: self.endpointToDiscover, cachePeriodInMinutes: 60)]) - }.futureResult - } - - public func test(_ input: TestRequest, logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) async throws { - return try await self.client.execute( - operation: "Test", - path: "/test", - httpMethod: .GET, - serviceConfig: self.config, - input: input, - endpointDiscovery: .init(storage: self.endpointStorage, discover: self.getEndpoints, required: true), - logger: logger, - on: eventLoop - ) - } - - public func getEndpointsDontCache(logger: Logger, on eventLoop: EventLoop) -> EventLoopFuture { - self.getEndpointsCalledCount.wrappingIncrement(ordering: .sequentiallyConsistent) - return eventLoop.scheduleTask(in: .milliseconds(200)) { - return AWSEndpoints(endpoints: [.init(address: self.endpointToDiscover, cachePeriodInMinutes: 0)]) - }.futureResult - } - - public func testDontCache(logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) async throws { - return try await self.client.execute( - operation: "Test", - path: "/test", - httpMethod: .GET, - serviceConfig: self.config, - endpointDiscovery: .init(storage: self.endpointStorage, discover: self.getEndpointsDontCache, required: true), - logger: logger, - on: eventLoop - ) - } - - public func testNotRequired(_ input: TestRequest, logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) async throws { - return try await self.client.execute( - operation: "Test", - path: "/test", - httpMethod: .GET, - serviceConfig: self.config, - input: input, - endpointDiscovery: .init(storage: self.endpointStorage, discover: self.getEndpoints, required: false), - logger: logger, - on: eventLoop - ) - } - } - - func testCachingEndpointDiscovery() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let awsServer = AWSTestServer(serviceProtocol: .restjson) - let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) - defer { - XCTAssertNoThrow(try client.syncShutdown()) - XCTAssertNoThrow(try awsServer.stop()) - } - let service = Service(client: client, endpointToDiscover: awsServer.address).with(middlewares: TestEnvironment.middlewares) - - async let response1: () = service.test(.init(), logger: TestEnvironment.logger) - try awsServer.processRaw { request in - TestEnvironment.logger.info("\(request)") - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response, continueProcessing: false) - } - try await response1 - async let response2: () = service.test(.init(), logger: TestEnvironment.logger) - try awsServer.processRaw { request in - TestEnvironment.logger.info("\(request)") - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response, continueProcessing: false) - } - try await response2 - XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 1) - } - - func testConcurrentEndpointDiscovery() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let awsServer = AWSTestServer(serviceProtocol: .json) - let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) - defer { - XCTAssertNoThrow(try client.syncShutdown()) - XCTAssertNoThrow(try awsServer.stop()) - } - let service = Service(client: client, endpointToDiscover: awsServer.address).with(middlewares: TestEnvironment.middlewares) - - async let response1: () = service.test(.init(), logger: TestEnvironment.logger) - async let response2: () = service.test(.init(), logger: TestEnvironment.logger) - - var count = 0 - try awsServer.processRaw { request in - TestEnvironment.logger.info("\(request)") - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - count += 1 - if count > 1 { - return .result(response, continueProcessing: false) - } else { - return .result(response, continueProcessing: true) - } - } - - try await response1 - try await response2 - XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 1) - } - - func testDontCacheEndpoint() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let awsServer = AWSTestServer(serviceProtocol: .json) - let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) - defer { - XCTAssertNoThrow(try client.syncShutdown()) - XCTAssertNoThrow(try awsServer.stop()) - } - let service = Service(client: client, endpointToDiscover: awsServer.address).with(middlewares: TestEnvironment.middlewares) - - async let response1: () = service.testDontCache(logger: TestEnvironment.logger) - try awsServer.processRaw { request in - TestEnvironment.logger.info("\(request)") - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response, continueProcessing: false) - } - try await response1 - async let response2: () = service.testDontCache(logger: TestEnvironment.logger) - try awsServer.processRaw { request in - TestEnvironment.logger.info("\(request)") - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response, continueProcessing: false) - } - try await response2 - XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 2) - } - - func testDisableEndpointDiscovery() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let awsServer = AWSTestServer(serviceProtocol: .json) - let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) - defer { - XCTAssertNoThrow(try client.syncShutdown()) - XCTAssertNoThrow(try awsServer.stop()) - } - let service = Service(client: client, endpoint: awsServer.address) - .with(middlewares: TestEnvironment.middlewares) - - async let response: () = service.testNotRequired(.init(), logger: TestEnvironment.logger) - - try awsServer.processRaw { request in - TestEnvironment.logger.info("\(request)") - let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - return .result(response, continueProcessing: false) - } - - try await response - XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 0) - } -} diff --git a/Tests/SotoCoreTests/EndpointDiscoveryTests.swift b/Tests/SotoCoreTests/EndpointDiscoveryTests.swift index f7037b467..1d8b2cf7a 100644 --- a/Tests/SotoCoreTests/EndpointDiscoveryTests.swift +++ b/Tests/SotoCoreTests/EndpointDiscoveryTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Soto for AWS open source project // -// Copyright (c) 2017-2022 the Soto project authors +// Copyright (c) 2021-2022 the Soto project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -18,7 +18,8 @@ import SotoCore import SotoTestUtils import XCTest -class EndpointDiscoveryTests: XCTestCase { +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +final class EndpointDiscoveryTests: XCTestCase { final class Service: AWSService { let client: AWSClient let config: AWSServiceConfig @@ -61,8 +62,8 @@ class EndpointDiscoveryTests: XCTestCase { }.futureResult } - @discardableResult public func test(_ input: TestRequest, logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) -> EventLoopFuture { - return self.client.execute( + public func test(_ input: TestRequest, logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) async throws { + return try await self.client.execute( operation: "Test", path: "/test", httpMethod: .GET, @@ -81,8 +82,8 @@ class EndpointDiscoveryTests: XCTestCase { }.futureResult } - @discardableResult public func testDontCache(logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) -> EventLoopFuture { - return self.client.execute( + public func testDontCache(logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) async throws { + return try await self.client.execute( operation: "Test", path: "/test", httpMethod: .GET, @@ -93,8 +94,8 @@ class EndpointDiscoveryTests: XCTestCase { ) } - @discardableResult public func testNotRequired(_ input: TestRequest, logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) -> EventLoopFuture { - return self.client.execute( + public func testNotRequired(_ input: TestRequest, logger: Logger = AWSClient.loggingDisabled, on eventLoop: EventLoop? = nil) async throws { + return try await self.client.execute( operation: "Test", path: "/test", httpMethod: .GET, @@ -107,7 +108,10 @@ class EndpointDiscoveryTests: XCTestCase { } } - func testCachingEndpointDiscovery() throws { + func testCachingEndpointDiscovery() async throws { + #if os(iOS) // iOS async tests are failing in GitHub CI at the moment + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + #endif let awsServer = AWSTestServer(serviceProtocol: .restjson) let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) defer { @@ -115,27 +119,28 @@ class EndpointDiscoveryTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } let service = Service(client: client, endpointToDiscover: awsServer.address).with(middlewares: TestEnvironment.middlewares) - let response = service.test(.init(), logger: TestEnvironment.logger).flatMap { _ in - service.test(.init(), logger: TestEnvironment.logger) - } - var count = 0 + async let response1: () = service.test(.init(), logger: TestEnvironment.logger) try awsServer.processRaw { request in TestEnvironment.logger.info("\(request)") let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - count += 1 - if count > 1 { - return .result(response, continueProcessing: false) - } else { - return .result(response, continueProcessing: true) - } + return .result(response, continueProcessing: false) } - - try response.wait() + try await response1 + async let response2: () = service.test(.init(), logger: TestEnvironment.logger) + try awsServer.processRaw { request in + TestEnvironment.logger.info("\(request)") + let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) + return .result(response, continueProcessing: false) + } + try await response2 XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 1) } - func testConcurrentEndpointDiscovery() throws { + func testConcurrentEndpointDiscovery() async throws { + #if os(iOS) // iOS async tests are failing in GitHub CI at the moment + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + #endif let awsServer = AWSTestServer(serviceProtocol: .json) let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) defer { @@ -143,8 +148,9 @@ class EndpointDiscoveryTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } let service = Service(client: client, endpointToDiscover: awsServer.address).with(middlewares: TestEnvironment.middlewares) - let response1 = service.test(.init(), logger: TestEnvironment.logger) - let response2 = service.test(.init(), logger: TestEnvironment.logger) + + async let response1: () = service.test(.init(), logger: TestEnvironment.logger) + async let response2: () = service.test(.init(), logger: TestEnvironment.logger) var count = 0 try awsServer.processRaw { request in @@ -158,11 +164,15 @@ class EndpointDiscoveryTests: XCTestCase { } } - _ = try response1.and(response2).wait() + try await response1 + try await response2 XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 1) } - func testDontCacheEndpoint() throws { + func testDontCacheEndpoint() async throws { + #if os(iOS) // iOS async tests are failing in GitHub CI at the moment + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + #endif let awsServer = AWSTestServer(serviceProtocol: .json) let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) defer { @@ -170,27 +180,28 @@ class EndpointDiscoveryTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } let service = Service(client: client, endpointToDiscover: awsServer.address).with(middlewares: TestEnvironment.middlewares) - let response = service.testDontCache(logger: TestEnvironment.logger).flatMap { _ in - service.testDontCache(logger: TestEnvironment.logger) - } - var count = 0 + async let response1: () = service.testDontCache(logger: TestEnvironment.logger) try awsServer.processRaw { request in TestEnvironment.logger.info("\(request)") let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) - count += 1 - if count > 1 { - return .result(response, continueProcessing: false) - } else { - return .result(response, continueProcessing: true) - } + return .result(response, continueProcessing: false) } - - try response.wait() + try await response1 + async let response2: () = service.testDontCache(logger: TestEnvironment.logger) + try awsServer.processRaw { request in + TestEnvironment.logger.info("\(request)") + let response = AWSTestServer.Response(httpStatus: .ok, headers: [:], body: nil) + return .result(response, continueProcessing: false) + } + try await response2 XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 2) } - func testDisableEndpointDiscovery() throws { + func testDisableEndpointDiscovery() async throws { + #if os(iOS) // iOS async tests are failing in GitHub CI at the moment + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + #endif let awsServer = AWSTestServer(serviceProtocol: .json) let client = AWSClient(credentialProvider: .empty, httpClientProvider: .createNew) defer { @@ -199,7 +210,8 @@ class EndpointDiscoveryTests: XCTestCase { } let service = Service(client: client, endpoint: awsServer.address) .with(middlewares: TestEnvironment.middlewares) - let response = service.testNotRequired(.init(), logger: TestEnvironment.logger) + + async let response: () = service.testNotRequired(.init(), logger: TestEnvironment.logger) try awsServer.processRaw { request in TestEnvironment.logger.info("\(request)") @@ -207,7 +219,7 @@ class EndpointDiscoveryTests: XCTestCase { return .result(response, continueProcessing: false) } - try response.wait() + try await response XCTAssertEqual(service.getEndpointsCalledCount.load(ordering: .sequentiallyConsistent), 0) } } diff --git a/Tests/SotoCoreTests/LoggingTests.swift b/Tests/SotoCoreTests/LoggingTests.swift index 8ebc0eec9..0b2acec29 100644 --- a/Tests/SotoCoreTests/LoggingTests.swift +++ b/Tests/SotoCoreTests/LoggingTests.swift @@ -19,7 +19,7 @@ import SotoTestUtils import XCTest class LoggingTests: XCTestCase { - func testRequestIdIncrements() { + func testRequestIdIncrements() async throws { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) let server = AWSTestServer(serviceProtocol: .json) @@ -35,8 +35,8 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) - let response2 = client.execute(operation: "test2", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let responseTask: Void = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let response2Task: Void = client.execute(operation: "test2", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) var count = 0 XCTAssertNoThrow(try server.processRaw { _ in @@ -49,8 +49,8 @@ class LoggingTests: XCTestCase { return result }) - XCTAssertNoThrow(_ = try response.wait()) - XCTAssertNoThrow(_ = try response2.wait()) + try await responseTask + try await response2Task let requestId1 = logCollection.filter(metadata: "aws-operation", with: "test1").first?.metadata["aws-request-id"] let requestId2 = logCollection.filter(metadata: "aws-operation", with: "test2").first?.metadata["aws-request-id"] XCTAssertNotNil(requestId1) @@ -58,10 +58,11 @@ class LoggingTests: XCTestCase { XCTAssertNotEqual(requestId1, requestId2) } - func testAWSRequestResponse() throws { + func testAWSRequestResponse() async throws { let logCollection = LoggingCollector.Logs() var logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) logger.logLevel = .trace + let traceLogger = logger let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -76,13 +77,13 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let responseTask: Void = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: traceLogger) XCTAssertNoThrow(try server.processRaw { _ in return .result(.ok, continueProcessing: false) }) - XCTAssertNoThrow(_ = try response.wait()) + try await responseTask let requestEntry = try XCTUnwrap(logCollection.filter(message: "AWS Request").first) XCTAssertEqual(requestEntry.level, .debug) XCTAssertEqual(requestEntry.metadata["aws-operation"], "TestOperation") @@ -93,7 +94,7 @@ class LoggingTests: XCTestCase { XCTAssertEqual(responseEntry.metadata["aws-service"], "test-service") } - func testAWSError() { + func testAWSError() async throws { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) let server = AWSTestServer(serviceProtocol: .json) @@ -110,18 +111,18 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "test", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) XCTAssertNoThrow(try server.processRaw { _ in return .error(.accessDenied, continueProcessing: false) }) - XCTAssertThrowsError(_ = try response.wait()) + try? await responseTask XCTAssertEqual(logCollection.filter(metadata: "aws-error-code", with: "AccessDenied").first?.message, "AWS Error") XCTAssertEqual(logCollection.filter(metadata: "aws-error-code", with: "AccessDenied").first?.level, .info) } - func testRetryRequest() { + func testRetryRequest() async throws { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) let server = AWSTestServer(serviceProtocol: .json) @@ -137,7 +138,7 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let responseTask: Void = client.execute(operation: "test1", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) var count = 0 XCTAssertNoThrow(try server.processRaw { _ in @@ -150,31 +151,34 @@ class LoggingTests: XCTestCase { return result }) - XCTAssertNoThrow(_ = try response.wait()) + try await responseTask XCTAssertEqual(logCollection.filter(metadata: "aws-retry-time").first?.message, "Retrying request") XCTAssertEqual(logCollection.filter(metadata: "aws-retry-time").first?.level, .trace) } - func testNoCredentialProvider() { + func testNoCredentialProvider() async throws { let logCollection = LoggingCollector.Logs() let logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection, logLevel: .trace) }) let client = createAWSClient(credentialProvider: .selector(.custom { _ in return NullCredentialProvider() })) defer { XCTAssertNoThrow(try client.syncShutdown()) } let serviceConfig = createServiceConfig() - XCTAssertThrowsError(try client.execute( - operation: "Test", - path: "/", - httpMethod: .GET, - serviceConfig: serviceConfig, - logger: logger - ).wait()) + do { + try await client.execute( + operation: "Test", + path: "/", + httpMethod: .GET, + serviceConfig: serviceConfig, + logger: logger + ) + } catch {} XCTAssertNotNil(logCollection.filter(metadata: "aws-error-message", with: "No credential provider found").first) } - func testRequestLogLevel() throws { + func testRequestLogLevel() async throws { let logCollection = LoggingCollector.Logs() var logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) logger.logLevel = .trace + let traceLogger = logger let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -190,24 +194,25 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let responseTask: Void = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: traceLogger) XCTAssertNoThrow(try server.processRaw { _ in return .result(.ok, continueProcessing: false) }) - XCTAssertNoThrow(_ = try response.wait()) + try await responseTask let requestEntry = try XCTUnwrap(logCollection.filter(message: "AWS Request").first) XCTAssertEqual(requestEntry.level, .trace) } - func testLoggingMiddleware() throws { + func testLoggingMiddleware() async throws { struct Output: AWSDecodableShape & Encodable { let s: String } let logCollection = LoggingCollector.Logs() var logger = Logger(label: "LoggingTests", factory: { _ in LoggingCollector(logCollection) }) logger.logLevel = .trace + let traceLogger = logger let server = AWSTestServer(serviceProtocol: .json) defer { XCTAssertNoThrow(try server.stop()) } let client = AWSClient( @@ -223,7 +228,7 @@ class LoggingTests: XCTestCase { endpoint: server.address ) - let response: EventLoopFuture = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: logger) + async let responseTask: Output = client.execute(operation: "TestOperation", path: "/", httpMethod: .GET, serviceConfig: config, logger: traceLogger) XCTAssertNoThrow(try server.processRaw { _ in let output = Output(s: "TestOutputString") @@ -232,7 +237,7 @@ class LoggingTests: XCTestCase { return .result(response) }) - XCTAssertNoThrow(_ = try response.wait()) + _ = try await responseTask XCTAssertNotNil(logCollection.filter { $0.message.hasPrefix("Request") }.first) XCTAssertNotNil(logCollection.filter { $0.message.hasPrefix("Response") }.first) } @@ -254,7 +259,7 @@ struct LoggingCollector: LogHandler { private var lock = NIOLock() private var logs: [Entry] = [] - var allEntries: [Entry] { return self.lock.withLock { logs } } + var allEntries: [Entry] { return self.lock.withLock { self.logs } } func append(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?) { self.lock.withLock { diff --git a/Tests/SotoCoreTests/MiddlewareTests.swift b/Tests/SotoCoreTests/MiddlewareTests.swift index 70d349cee..0cb30585d 100644 --- a/Tests/SotoCoreTests/MiddlewareTests.swift +++ b/Tests/SotoCoreTests/MiddlewareTests.swift @@ -33,7 +33,7 @@ class MiddlewareTests: XCTestCase { serviceOptions: AWSServiceConfig.Options = [], uri: String = "/", test: (AWSRequest) -> Void - ) { + ) async throws { let client = createAWSClient(credentialProvider: .empty) let config = createServiceConfig( region: .useast1, @@ -41,16 +41,17 @@ class MiddlewareTests: XCTestCase { middlewares: [middleware, CatchRequestMiddleware()], options: serviceOptions ) - let response = client.execute(operation: "test", path: uri, httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) - XCTAssertThrowsError(try response.wait()) { error in - if let error = error as? CatchRequestError { - test(error.request) - } + do { + _ = try await client.execute(operation: "test", path: uri, httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + XCTFail("Should not get here") + } catch { + let error = try XCTUnwrap(error as? CatchRequestError) + test(error.request) } - try? client.syncShutdown() + try client.syncShutdown() } - func testMiddlewareAppliedOnce() { + func testMiddlewareAppliedOnce() async throws { struct URLAppendMiddleware: AWSServiceMiddleware { func chain(request: AWSRequest, context: AWSMiddlewareContext) throws -> AWSRequest { var request = request @@ -67,48 +68,48 @@ class MiddlewareTests: XCTestCase { XCTAssertNoThrow(try awsServer.stop()) } - let response = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) + async let responseTask: Void = client.execute(operation: "test", path: "/", httpMethod: .POST, serviceConfig: config, logger: TestEnvironment.logger) XCTAssertNoThrow(try awsServer.processRaw { request in XCTAssertEqual(request.uri, "/test") return .result(AWSTestServer.Response.ok) }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testEditHeaderMiddlewareAddHeader() { + func testEditHeaderMiddlewareAddHeader() async throws { // Test add header let middleware = AWSEditHeadersMiddleware( .add(name: "testAdd", value: "testValue"), .add(name: "user-agent", value: "testEditHeaderMiddleware") ) - self.testMiddleware(middleware) { request in + try await self.testMiddleware(middleware) { request in XCTAssertEqual(request.httpHeaders["testAdd"].first, "testValue") XCTAssertEqual(request.httpHeaders["user-agent"].joined(separator: ","), "Soto/6.0,testEditHeaderMiddleware") } } - func testEditHeaderMiddlewareReplaceHeader() { + func testEditHeaderMiddlewareReplaceHeader() async throws { // Test replace header let middleware = AWSEditHeadersMiddleware( .replace(name: "user-agent", value: "testEditHeaderMiddleware") ) - self.testMiddleware(middleware) { request in + try await self.testMiddleware(middleware) { request in XCTAssertEqual(request.httpHeaders["user-agent"].first, "testEditHeaderMiddleware") } } - func testS3MiddlewareVirtualAddress() { + func testS3MiddlewareVirtualAddress() async throws { // Test virual address - self.testMiddleware(S3Middleware(), uri: "/bucket/file") { request in + try await self.testMiddleware(S3Middleware(), uri: "/bucket/file") { request in XCTAssertEqual(request.url.absoluteString, "https://bucket.service.us-east-1.amazonaws.com/file") } } - func testS3MiddlewareAccelerateEndpoint() { + func testS3MiddlewareAccelerateEndpoint() async throws { // Test virual address - self.testMiddleware( + try await self.testMiddleware( S3Middleware(), serviceName: "s3", serviceOptions: .s3UseTransferAcceleratedEndpoint, diff --git a/Tests/SotoCoreTests/PaginateTests+async.swift b/Tests/SotoCoreTests/PaginateTests+async.swift deleted file mode 100644 index c4b43ab04..000000000 --- a/Tests/SotoCoreTests/PaginateTests+async.swift +++ /dev/null @@ -1,239 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Soto for AWS open source project -// -// Copyright (c) 2017-2022 the Soto project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Soto project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import AsyncHTTPClient -import NIOCore -import NIOPosix -@testable import SotoCore -import SotoTestUtils -import XCTest - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -final class PaginateAsyncTests: XCTestCase, @unchecked Sendable { - enum Error: Swift.Error { - case didntFindToken - } - - var awsServer: AWSTestServer! - var eventLoopGroup: EventLoopGroup! - var httpClient: HTTPClient! - var client: AWSClient! - var config: AWSServiceConfig! - - override func setUp() { - // create server and client - self.awsServer = AWSTestServer(serviceProtocol: .json) - self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 3) - self.httpClient = AsyncHTTPClient.HTTPClient(eventLoopGroupProvider: .shared(self.eventLoopGroup)) - self.config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: self.awsServer.address) - self.client = createAWSClient(credentialProvider: .empty, retryPolicy: .noRetry, httpClientProvider: .shared(self.httpClient)) - } - - override func tearDown() { - XCTAssertNoThrow(try self.awsServer.stop()) - XCTAssertNoThrow(try self.client.syncShutdown()) - XCTAssertNoThrow(try self.httpClient.syncShutdown()) - XCTAssertNoThrow(try self.eventLoopGroup.syncShutdownGracefully()) - } - - // test structures/functions - struct CounterInput: AWSEncodableShape, AWSPaginateToken, Decodable { - let inputToken: Int? - let pageSize: Int - - init(inputToken: Int?, pageSize: Int) { - self.inputToken = inputToken - self.pageSize = pageSize - } - - func usingPaginationToken(_ token: Int) -> CounterInput { - return .init(inputToken: token, pageSize: self.pageSize) - } - } - - // conform to Encodable so server can encode these - struct CounterOutput: AWSDecodableShape, Encodable { - let array: [Int] - let outputToken: Int? - } - - func counter(_ input: CounterInput, logger: Logger, on eventLoop: EventLoop?) async throws -> CounterOutput { - return try await self.client.execute( - operation: "TestOperation", - path: "/", - httpMethod: .POST, - serviceConfig: self.config, - input: input, - logger: logger, - on: eventLoop - ) - } - - func asyncCounterPaginator(_ input: CounterInput) -> AWSClient.PaginatorSequence { - return .init( - input: input, - command: self.counter, - inputKey: \CounterInput.inputToken, - outputKey: \CounterOutput.outputToken, - logger: TestEnvironment.logger - ) - } - - func stringList(_ input: StringListInput, logger: Logger, on eventLoop: EventLoop? = nil) async throws -> StringListOutput { - return try await self.client.execute( - operation: "TestOperation", - path: "/", - httpMethod: .POST, - serviceConfig: self.config, - input: input, - logger: logger, - on: eventLoop - ) - } - - func asyncStringListPaginator(_ input: StringListInput) -> AWSClient.PaginatorSequence { - .init( - input: input, - command: self.stringList, - inputKey: \StringListInput.inputToken, - outputKey: \StringListOutput.outputToken, - logger: TestEnvironment.logger - ) - } - - // test structures/functions - struct StringListInput: AWSEncodableShape, AWSPaginateToken, Decodable { - let inputToken: String? - let pageSize: Int - - init(inputToken: String?, pageSize: Int) { - self.inputToken = inputToken - self.pageSize = pageSize - } - - func usingPaginationToken(_ token: String) -> StringListInput { - return .init(inputToken: token, pageSize: self.pageSize) - } - } - - // conform to Encodable so server can encode these - struct StringListOutput: AWSDecodableShape, Encodable { - let array: [String] - let outputToken: String? - } - - // create list of unique strings - let stringList = Set("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".split(separator: " ").map { String($0) }).map { $0 } - - func stringListServerProcess(_ input: StringListInput) throws -> AWSTestServer.Result { - // send part of array of numbers based on input startIndex and pageSize - var startIndex = 0 - if let inputToken = input.inputToken { - guard let stringIndex = stringList.firstIndex(of: inputToken) else { throw Error.didntFindToken } - startIndex = stringIndex - } - let endIndex = min(startIndex + input.pageSize, self.stringList.count) - var array: [String] = [] - for i in startIndex.. [Int] in - group.addTask { - return try await self.asyncCounterPaginator(input).reduce([]) { return $0 + $1.array } - } - try self.awsServer.process { (input: CounterInput) throws -> AWSTestServer.Result in - // send part of array of numbers based on input startIndex and pageSize - let startIndex = input.inputToken ?? 0 - let endIndex = min(startIndex + input.pageSize, arraySize) - var array: [Int] = [] - for i in startIndex.. [String] in - group.addTask { - let paginator = self.asyncStringListPaginator(input) - return try await paginator.reduce([]) { $0 + $1.array } - } - try self.awsServer.process(self.stringListServerProcess) - return try await group.next()! - } - // verify contents of array - XCTAssertEqual(finalArray.count, self.stringList.count) - for i in 0.. [String] in - group.addTask { - let paginator = self.asyncStringListPaginator(input) - return try await paginator.reduce([]) { $0 + $1.array } - } - try self.awsServer.process { (_: StringListInput) -> AWSTestServer.Result in - return .error(.badRequest) - } - return try await group.next()! - } - } catch { - XCTAssertEqual((error as? AWSResponseError)?.errorCode, "BadRequest") - } - } -} diff --git a/Tests/SotoCoreTests/PaginateTests.swift b/Tests/SotoCoreTests/PaginateTests.swift index 746825117..bec58b055 100644 --- a/Tests/SotoCoreTests/PaginateTests.swift +++ b/Tests/SotoCoreTests/PaginateTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Soto for AWS open source project // -// Copyright (c) 2017-2021 the Soto project authors +// Copyright (c) 2017-2022 the Soto project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,13 +13,15 @@ //===----------------------------------------------------------------------===// import AsyncHTTPClient +import Atomics import NIOCore import NIOPosix @testable import SotoCore import SotoTestUtils import XCTest -class PaginateTests: XCTestCase { +@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) +final class PaginateAsyncTests: XCTestCase, @unchecked Sendable { enum Error: Swift.Error { case didntFindToken } @@ -67,8 +69,8 @@ class PaginateTests: XCTestCase { let outputToken: Int? } - func counter(_ input: CounterInput, logger: Logger, on eventLoop: EventLoop?) -> EventLoopFuture { - return self.client.execute( + func counter(_ input: CounterInput, logger: Logger, on eventLoop: EventLoop?) async throws -> CounterOutput { + return try await self.client.execute( operation: "TestOperation", path: "/", httpMethod: .POST, @@ -79,49 +81,36 @@ class PaginateTests: XCTestCase { ) } - func counterPaginator(_ input: CounterInput, onPage: @escaping (CounterOutput, EventLoop) -> EventLoopFuture) -> EventLoopFuture { - return self.client.paginate( + func asyncCounterPaginator(_ input: CounterInput) -> AWSClient.PaginatorSequence { + return .init( input: input, command: self.counter, - tokenKey: \CounterOutput.outputToken, - logger: TestEnvironment.logger, - onPage: onPage + inputKey: \CounterInput.inputToken, + outputKey: \CounterOutput.outputToken, + logger: TestEnvironment.logger ) } - func testIntegerTokenPaginate() throws { - // paginate input - var finalArray: [Int] = [] - let input = CounterInput(inputToken: nil, pageSize: 4) - let future = self.counterPaginator(input) { result, eventloop in - // collate results into array - finalArray.append(contentsOf: result.array) - return eventloop.makeSucceededFuture(true) - } - - let arraySize = 23 - // aws server process - XCTAssertNoThrow(try self.awsServer.process { (input: CounterInput) throws -> AWSTestServer.Result in - // send part of array of numbers based on input startIndex and pageSize - let startIndex = input.inputToken ?? 0 - let endIndex = min(startIndex + input.pageSize, arraySize) - var array: [Int] = [] - for i in startIndex.. StringListOutput { + return try await self.client.execute( + operation: "TestOperation", + path: "/", + httpMethod: .POST, + serviceConfig: self.config, + input: input, + logger: logger, + on: eventLoop + ) + } - // verify contents of array - XCTAssertEqual(finalArray.count, arraySize) - for i in 0.. AWSClient.PaginatorSequence { + .init( + input: input, + command: self.stringList, + inputKey: \StringListInput.inputToken, + outputKey: \StringListOutput.outputToken, + logger: TestEnvironment.logger + ) } // test structures/functions @@ -145,61 +134,6 @@ class PaginateTests: XCTestCase { let outputToken: String? } - // conform to Encodable so server can encode these - struct StringList2Output: AWSDecodableShape, Encodable { - let array: [String] - let outputToken: String? - } - - func stringList(_ input: StringListInput, logger: Logger, on eventLoop: EventLoop? = nil) -> EventLoopFuture { - return self.client.execute( - operation: "TestOperation", - path: "/", - httpMethod: .POST, - serviceConfig: self.config, - input: input, - logger: logger, - on: eventLoop - ) - } - - func stringListPaginator(_ input: StringListInput, on eventLoop: EventLoop? = nil, onPage: @escaping (StringListOutput, EventLoop) -> EventLoopFuture) -> EventLoopFuture { - return self.client.paginate( - input: input, - command: self.stringList, - inputKey: \StringListInput.inputToken, - outputKey: \StringListOutput.outputToken, - logger: TestEnvironment.logger, - on: eventLoop, - onPage: onPage - ) - } - - func stringList2(_ input: StringListInput, logger: Logger, on eventLoop: EventLoop? = nil) -> EventLoopFuture { - return self.client.execute( - operation: "TestOperation", - path: "/", - httpMethod: .POST, - serviceConfig: self.config, - input: input, - logger: logger, - on: eventLoop - ) - } - - func stringListPaginator(_ input: StringListInput, _ initialValue: Result, on eventLoop: EventLoop? = nil, onPage: @escaping (Result, StringList2Output, EventLoop) -> EventLoopFuture<(Bool, Result)>) -> EventLoopFuture { - return self.client.paginate( - input: input, - initialValue: initialValue, - command: self.stringList2, - inputKey: \StringListInput.inputToken, - outputKey: \StringList2Output.outputToken, - logger: TestEnvironment.logger, - on: eventLoop, - onPage: onPage - ) - } - // create list of unique strings let stringList = Set("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".split(separator: " ").map { String($0) }).map { $0 } @@ -227,44 +161,54 @@ class PaginateTests: XCTestCase { return .result(output, continueProcessing: continueProcessing) } - func testStringTokenPaginate() throws { + func testAsyncIntegerTokenPaginate() async throws { + #if os(iOS) // iOS async tests are failing in GitHub CI at the moment + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + #endif // paginate input - var finalArray: [String] = [] - let input = StringListInput(inputToken: nil, pageSize: 5) - let future = self.stringListPaginator(input) { result, eventloop in - // collate results into array - finalArray.append(contentsOf: result.array) - return eventloop.makeSucceededFuture(true) - } - - // aws server process - XCTAssertNoThrow(try self.awsServer.process(self.stringListServerProcess)) + let input = CounterInput(inputToken: nil, pageSize: 4) + let arraySize = 23 - // wait for response - XCTAssertNoThrow(try future.wait()) + let finalArray = try await withThrowingTaskGroup(of: [Int].self) { group -> [Int] in + group.addTask { + return try await self.asyncCounterPaginator(input).reduce([]) { return $0 + $1.array } + } + try self.awsServer.process { (input: CounterInput) throws -> AWSTestServer.Result in + // send part of array of numbers based on input startIndex and pageSize + let startIndex = input.inputToken ?? 0 + let endIndex = min(startIndex + input.pageSize, arraySize) + var array: [Int] = [] + for i in startIndex.. [String] in + group.addTask { + let paginator = self.asyncStringListPaginator(input) + return try await paginator.reduce([]) { $0 + $1.array } + } + try self.awsServer.process(self.stringListServerProcess) + return try await group.next()! } - - // aws server process - XCTAssertNoThrow(try self.awsServer.process(self.stringListServerProcess)) - - // wait for response - var array: [String]? - XCTAssertNoThrow(array = try future.wait()) - let finalArray = try XCTUnwrap(array) // verify contents of array XCTAssertEqual(finalArray.count, self.stringList.count) for i in 0.. AWSTestServer.Result in - return .error(.badRequest) - }) - - // wait for response - XCTAssertThrowsError(try future.wait()) { error in + do { + _ = try await withThrowingTaskGroup(of: [String].self) { group -> [String] in + group.addTask { + let paginator = self.asyncStringListPaginator(input) + return try await paginator.reduce([]) { $0 + $1.array } + } + try self.awsServer.process { (_: StringListInput) -> AWSTestServer.Result in + return .error(.badRequest) + } + return try await group.next()! + } + } catch { XCTAssertEqual((error as? AWSResponseError)?.errorCode, "BadRequest") } } - func testPaginateErrorAfterFirstRequest() throws { + func testAsyncPaginateErrorAfterFirstRequest() async throws { + #if os(iOS) // iOS async tests are failing in GitHub CI at the moment + guard ProcessInfo.processInfo.environment["CI"] == nil else { return } + #endif // paginate input let input = StringListInput(inputToken: nil, pageSize: 5) - let future = self.stringListPaginator(input) { _, eventloop in - return eventloop.makeSucceededFuture(true) - } - - // aws server process - var count = 0 - XCTAssertNoThrow(try self.awsServer.process { (request: StringListInput) -> AWSTestServer.Result in - if count > 0 { - return .error(.badRequest, continueProcessing: false) - } else { - count += 1 - return try stringListServerProcess(request) + do { + let count = ManagedAtomic(0) + + _ = try await withThrowingTaskGroup(of: [String].self) { group -> [String] in + group.addTask { + let paginator = self.asyncStringListPaginator(input) + return try await paginator.reduce([]) { $0 + $1.array } + } + try self.awsServer.process { (request: StringListInput) -> AWSTestServer.Result in + if count.loadThenWrappingIncrement(by: 1, ordering: .relaxed) > 0 { + return .error(.badRequest, continueProcessing: false) + } else { + return try self.stringListServerProcess(request) + } + } + return try await group.next()! } - }) - - // wait for response - XCTAssertThrowsError(try future.wait()) { error in + } catch { XCTAssertEqual((error as? AWSResponseError)?.errorCode, "BadRequest") } } - - func testPaginateEventLoop() throws { - // paginate input - let clientEventLoop = self.client.eventLoopGroup.next() - let input = StringListInput(inputToken: nil, pageSize: 5) - let future = self.stringListPaginator(input, on: clientEventLoop) { _, eventloop in - XCTAssertTrue(clientEventLoop.inEventLoop) - XCTAssertTrue(clientEventLoop === eventloop) - return eventloop.makeSucceededFuture(true) - } - - // aws server process - XCTAssertNoThrow(try self.awsServer.process(self.stringListServerProcess)) - // wait for response - XCTAssertNoThrow(try future.wait()) - } } diff --git a/Tests/SotoCoreTests/PayloadTests.swift b/Tests/SotoCoreTests/PayloadTests.swift index 8b76c29f3..3564c14d8 100644 --- a/Tests/SotoCoreTests/PayloadTests.swift +++ b/Tests/SotoCoreTests/PayloadTests.swift @@ -18,7 +18,7 @@ import SotoTestUtils import XCTest class PayloadTests: XCTestCase { - func testRequestPayload(_ payload: AWSPayload, expectedResult: String) { + func testRequestPayload(_ payload: AWSPayload, expectedResult: String) async { struct DataPayload: AWSEncodableShape & AWSShapeWithPayload { static var _payloadPath: String = "data" let data: AWSPayload @@ -34,7 +34,7 @@ class PayloadTests: XCTestCase { XCTAssertNoThrow(try client.syncShutdown()) } let input = DataPayload(data: payload) - let response = client.execute( + async let responseTask: Void = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -48,28 +48,28 @@ class PayloadTests: XCTestCase { return .result(.ok) } - try response.wait() + try await responseTask try awsServer.stop() } catch { XCTFail("Unexpected error: \(error)") } } - func testDataRequestPayload() { - self.testRequestPayload(.data(Data("testDataPayload".utf8)), expectedResult: "testDataPayload") + func testDataRequestPayload() async { + await self.testRequestPayload(.data(Data("testDataPayload".utf8)), expectedResult: "testDataPayload") } - func testStringRequestPayload() { - self.testRequestPayload(.string("testStringPayload"), expectedResult: "testStringPayload") + func testStringRequestPayload() async { + await self.testRequestPayload(.string("testStringPayload"), expectedResult: "testStringPayload") } - func testByteBufferRequestPayload() { + func testByteBufferRequestPayload() async { var byteBuffer = ByteBufferAllocator().buffer(capacity: 32) byteBuffer.writeString("testByteBufferPayload") - self.testRequestPayload(.byteBuffer(byteBuffer), expectedResult: "testByteBufferPayload") + await self.testRequestPayload(.byteBuffer(byteBuffer), expectedResult: "testByteBufferPayload") } - func testResponsePayload() { + func testResponsePayload() async { struct Output: AWSDecodableShape, AWSShapeWithPayload { static let _payloadPath: String = "payload" static let _options: AWSShapeOptions = .rawPayload @@ -82,7 +82,7 @@ class PayloadTests: XCTestCase { defer { XCTAssertNoThrow(try client.syncShutdown()) } - let response: EventLoopFuture = client.execute( + async let responseTask: Output = client.execute( operation: "test", path: "/", httpMethod: .POST, @@ -97,7 +97,7 @@ class PayloadTests: XCTestCase { return .result(response) } - let output = try response.wait() + let output = try await responseTask XCTAssertEqual(output.payload.asString(), "testResponsePayload") // XCTAssertEqual(output.i, 547) diff --git a/Tests/SotoCoreTests/WaiterTests+async.swift b/Tests/SotoCoreTests/WaiterTests+async.swift deleted file mode 100644 index 586918beb..000000000 --- a/Tests/SotoCoreTests/WaiterTests+async.swift +++ /dev/null @@ -1,79 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Soto for AWS open source project -// -// Copyright (c) 2017-2022 the Soto project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of Soto project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -@testable import SotoCore -import SotoTestUtils -import XCTest - -@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) -final class WaiterAsyncTests: XCTestCase, @unchecked Sendable { - var awsServer: AWSTestServer! - var config: AWSServiceConfig! - var client: AWSClient! - - override func setUp() { - self.awsServer = AWSTestServer(serviceProtocol: .json) - self.config = createServiceConfig(serviceProtocol: .json(version: "1.1"), endpoint: self.awsServer.address) - self.client = createAWSClient(credentialProvider: .empty, middlewares: [AWSLoggingMiddleware()]) - } - - override func tearDown() { - XCTAssertNoThrow(try self.client.syncShutdown()) - XCTAssertNoThrow(try self.awsServer.stop()) - } - - struct Input: AWSEncodableShape & Decodable {} - - struct ArrayOutput: AWSDecodableShape & Encodable { - struct Element: AWSDecodableShape & Encodable, ExpressibleByBooleanLiteral { - let status: Bool - init(booleanLiteral: Bool) { - self.status = booleanLiteral - } - - init(_ status: Bool) { - self.status = status - } - } - - let array: [Element] - } - - func arrayOperation(input: Input, logger: Logger, eventLoop: EventLoop?) -> EventLoopFuture { - self.client.execute(operation: "Basic", path: "/", httpMethod: .POST, serviceConfig: self.config, input: input, logger: logger, on: eventLoop) - } - - func testJMESPathWaiter() async throws { - #if os(iOS) // iOS async tests are failing in GitHub CI at the moment - guard ProcessInfo.processInfo.environment["CI"] == nil else { return } - #endif - let waiter = AWSClient.Waiter( - acceptors: [ - .init(state: .success, matcher: try! JMESPathMatcher("array[*].status", expected: [true, true, true])), - ], - minDelayTime: .seconds(2), - command: self.arrayOperation - ) - let input = Input() - async let asyncWait: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) - - var i = 0 - try self.awsServer.process { (_: Input) -> AWSTestServer.Result in - i += 1 - return .result(ArrayOutput(array: [.init(i >= 3), .init(i >= 2), .init(i >= 1)]), continueProcessing: i < 3) - } - - try await asyncWait - } -} diff --git a/Tests/SotoCoreTests/WaiterTests.swift b/Tests/SotoCoreTests/WaiterTests.swift index fa0104e7e..835ba74e6 100644 --- a/Tests/SotoCoreTests/WaiterTests.swift +++ b/Tests/SotoCoreTests/WaiterTests.swift @@ -78,7 +78,7 @@ class WaiterTests: XCTestCase { self.client.execute(operation: "Basic", path: "/", httpMethod: .POST, serviceConfig: self.config, input: input, logger: logger, on: eventLoop) } - func testJMESPathWaiter() { + func testJMESPathWaiter() async throws { let waiter = AWSClient.Waiter( acceptors: [ .init(state: .success, matcher: try! JMESPathMatcher("array[*].status", expected: [true, true, true])), @@ -87,7 +87,7 @@ class WaiterTests: XCTestCase { command: self.arrayOperation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -95,10 +95,10 @@ class WaiterTests: XCTestCase { return .result(ArrayOutput(array: [.init(i >= 3), .init(i >= 2), .init(i >= 1)]), continueProcessing: i < 3) }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testJMESPathWaiterWithString() { + func testJMESPathWaiterWithString() async throws { struct StringOutput: AWSDecodableShape & Encodable { let s: String } @@ -113,7 +113,7 @@ class WaiterTests: XCTestCase { command: operation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -125,10 +125,10 @@ class WaiterTests: XCTestCase { } }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testJMESPathWaiterWithEnum() { + func testJMESPathWaiterWithEnum() async throws { enum YesNo: String, AWSDecodableShape & Encodable & CustomStringConvertible { case yes = "YES" case no = "NO" @@ -148,7 +148,7 @@ class WaiterTests: XCTestCase { command: operation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -160,10 +160,10 @@ class WaiterTests: XCTestCase { } }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testJMESAnyPathWaiter() { + func testJMESAnyPathWaiter() async throws { let waiter = AWSClient.Waiter( acceptors: [ .init(state: .success, matcher: try! JMESAnyPathMatcher("array[*].status", expected: true)), @@ -172,7 +172,7 @@ class WaiterTests: XCTestCase { command: self.arrayOperation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -184,10 +184,10 @@ class WaiterTests: XCTestCase { } }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testJMESAllPathWaiter() { + func testJMESAllPathWaiter() async throws { let waiter = AWSClient.Waiter( acceptors: [ .init(state: .success, matcher: try! JMESAllPathMatcher("array[*].status", expected: true)), @@ -196,7 +196,7 @@ class WaiterTests: XCTestCase { command: self.arrayOperation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -208,10 +208,10 @@ class WaiterTests: XCTestCase { } }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testJMESPathPropertyValueWaiter() { + func testJMESPathPropertyValueWaiter() async throws { struct ArrayOutput: AWSDecodableShape & Encodable { @CustomCoding> var array: [Bool] } @@ -234,7 +234,7 @@ class WaiterTests: XCTestCase { command: arrayOperation ) let input = Input(test: "Input") - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -242,10 +242,10 @@ class WaiterTests: XCTestCase { return .result(ArrayOutput(array: [.init(i >= 3), .init(i >= 2), .init(i >= 1)]), continueProcessing: i < 3) }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testTimeoutWaiter() { + func testTimeoutWaiter() async throws { let waiter = AWSClient.Waiter( acceptors: [ .init(state: .success, matcher: try! JMESPathMatcher("i", expected: 3)), @@ -255,7 +255,7 @@ class WaiterTests: XCTestCase { command: self.operation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, maxWaitTime: .seconds(4), logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, maxWaitTime: .seconds(4), logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -263,17 +263,15 @@ class WaiterTests: XCTestCase { return .result(Output(i: i), continueProcessing: i < 2) }) - XCTAssertThrowsError(try response.wait()) { error in - switch error { - case let error as AWSClient.ClientError where error == .waiterTimeout: - break - default: - XCTFail("\(error)") - } + do { + try await responseTask + XCTFail("Should not get here") + } catch { + XCTAssertEqual(error as? AWSClient.ClientError, .waiterTimeout) } } - func testErrorWaiter() { + func testErrorWaiter() async throws { let waiter = AWSClient.Waiter( acceptors: [ .init(state: .retry, matcher: AWSErrorCodeMatcher("AccessDenied")), @@ -283,7 +281,7 @@ class WaiterTests: XCTestCase { command: self.operation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -295,10 +293,10 @@ class WaiterTests: XCTestCase { } }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } - func testErrorStatusWaiter() { + func testErrorStatusWaiter() async throws { let waiter = AWSClient.Waiter( acceptors: [ .init(state: .retry, matcher: AWSErrorStatusMatcher(404)), @@ -308,7 +306,7 @@ class WaiterTests: XCTestCase { command: self.operation ) let input = Input() - let response = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) + async let responseTask: Void = self.client.waitUntil(input, waiter: waiter, logger: TestEnvironment.logger) var i = 0 XCTAssertNoThrow(try self.awsServer.process { (_: Input) -> AWSTestServer.Result in @@ -320,6 +318,6 @@ class WaiterTests: XCTestCase { } }) - XCTAssertNoThrow(try response.wait()) + try await responseTask } }