diff --git a/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift b/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift index 0cedf7c29..4ad30d200 100644 --- a/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift +++ b/Source/AwsCommonRuntimeKit/auth/credentials/Credentials.swift @@ -4,7 +4,9 @@ import AwsCAuth import Foundation -public final class Credentials { +// We can't mutate this class after initialization. Swift can not verify the sendability due to OpaquePointer, +// So mark it unchecked Sendable +public final class Credentials: @unchecked Sendable { let rawValue: OpaquePointer diff --git a/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift b/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift index 5d9b4b9e9..4c278e2b5 100644 --- a/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift +++ b/Source/AwsCommonRuntimeKit/auth/credentials/CredentialsProvider.swift @@ -10,7 +10,9 @@ public protocol CredentialsProviding { func getCredentials() async throws -> Credentials } -public class CredentialsProvider: CredentialsProviding { +// We can't mutate this class after initialization. Swift can not verify the sendability due to pointer, +// So mark it unchecked Sendable +public class CredentialsProvider: CredentialsProviding, @unchecked Sendable { let rawValue: UnsafeMutablePointer @@ -590,7 +592,7 @@ private func onGetCredentials(credentials: OpaquePointer?, // We need to share this pointer to C in a task block but Swift compiler complains // that Pointer does not conform to Sendable. Wrap the pointer in a @unchecked Sendable block // for Swift compiler to stop complaining. -struct SendablePointer: @unchecked Sendable { +struct SendableRawPointer: @unchecked Sendable { let pointer: UnsafeMutableRawPointer } @@ -600,12 +602,13 @@ private func getCredentialsDelegateFn(_ delegatePtr: UnsafeMutableRawPointer!, Int32, UnsafeMutableRawPointer?) -> Void)!, _ userData: UnsafeMutableRawPointer!) -> Int32 { - let delegate = Unmanaged> - .fromOpaque(delegatePtr) - .takeUnretainedValue().contents - let userData = SendablePointer(pointer: userData) + let userData = SendableRawPointer(pointer: userData) + let delegatePtr = SendableRawPointer(pointer: delegatePtr) Task { do { + let delegate = Unmanaged> + .fromOpaque(delegatePtr.pointer) + .takeUnretainedValue().contents let credentials = try await delegate.getCredentials() callbackFn(credentials.rawValue, AWS_OP_SUCCESS, userData.pointer) } catch CommonRunTimeError.crtError(let crtError) { diff --git a/Source/AwsCommonRuntimeKit/auth/imds/IAMProfile.swift b/Source/AwsCommonRuntimeKit/auth/imds/IAMProfile.swift index 95a604d7d..3df71909d 100644 --- a/Source/AwsCommonRuntimeKit/auth/imds/IAMProfile.swift +++ b/Source/AwsCommonRuntimeKit/auth/imds/IAMProfile.swift @@ -4,7 +4,7 @@ import AwsCAuth import Foundation -public struct IAMProfile { +public struct IAMProfile: Sendable { public let lastUpdated: Date public let profileArn: String public let profileId: String diff --git a/Source/AwsCommonRuntimeKit/auth/imds/IMDSClient.swift b/Source/AwsCommonRuntimeKit/auth/imds/IMDSClient.swift index de3622a94..4e58ab73f 100644 --- a/Source/AwsCommonRuntimeKit/auth/imds/IMDSClient.swift +++ b/Source/AwsCommonRuntimeKit/auth/imds/IMDSClient.swift @@ -4,7 +4,9 @@ import AwsCAuth // swiftlint:disable type_body_length -public class IMDSClient { +// We can't mutate this class after initialization. Swift can not verify the sendability due to OpaquePointer, +// So mark it unchecked Sendable +public class IMDSClient: @unchecked Sendable { let rawValue: OpaquePointer /// Creates an IMDSClient that always uses IMDSv2 diff --git a/Source/AwsCommonRuntimeKit/auth/imds/IMDSInstanceInfo.swift b/Source/AwsCommonRuntimeKit/auth/imds/IMDSInstanceInfo.swift index e10fd4b2d..4837e1d56 100644 --- a/Source/AwsCommonRuntimeKit/auth/imds/IMDSInstanceInfo.swift +++ b/Source/AwsCommonRuntimeKit/auth/imds/IMDSInstanceInfo.swift @@ -4,7 +4,7 @@ import AwsCAuth import Foundation -public struct IMDSInstanceInfo { +public struct IMDSInstanceInfo: Sendable { public let marketPlaceProductCodes: [String] public let availabilityZone: String public let privateIp: String diff --git a/Source/AwsCommonRuntimeKit/auth/signing/Signer.swift b/Source/AwsCommonRuntimeKit/auth/signing/Signer.swift index e815f071a..e35d4250f 100644 --- a/Source/AwsCommonRuntimeKit/auth/signing/Signer.swift +++ b/Source/AwsCommonRuntimeKit/auth/signing/Signer.swift @@ -164,9 +164,11 @@ public class Signer { } } -class SignRequestCore { +// After signing, we mutate the request and resume the continuation, which may result in a thread change. +// We won't modify it after continuation.resume is called. So we can mark it @unchecked Sendable +class SignRequestCore: @unchecked Sendable { let request: HTTPRequestBase - var continuation: CheckedContinuation + let continuation: CheckedContinuation let shouldSignHeader: ((String) -> Bool)? init(request: HTTPRequestBase, continuation: CheckedContinuation, @@ -207,6 +209,7 @@ private func onRequestSigningComplete(signingResult: UnsafeMutablePointer?, @@ -220,9 +223,10 @@ private func onSigningComplete(signingResult: UnsafeMutablePointer! + let signature = AWSString("signature") guard aws_signing_result_get_property( signingResult!, - g_aws_signature_property_name, + signature.rawValue, &awsStringPointer) == AWS_OP_SUCCESS else { chunkSignerCore.continuation.resume(throwing: CommonRunTimeError.crtError(.makeFromLastError())) return diff --git a/Source/AwsCommonRuntimeKit/crt/Allocator.swift b/Source/AwsCommonRuntimeKit/crt/Allocator.swift index 1923ff779..63bc9cac6 100644 --- a/Source/AwsCommonRuntimeKit/crt/Allocator.swift +++ b/Source/AwsCommonRuntimeKit/crt/Allocator.swift @@ -2,11 +2,17 @@ // SPDX-License-Identifier: Apache-2.0. import AwsCCommon -/** - The default allocator. - You are probably looking to use `allocator` instead. +/* + * The default allocator. + * We need to declare `allocator` as mutable (`var`) instead of `let` because we override it with a tracing allocator in tests. This is not mutated anywhere else apart from the start of tests. + * Swift compiler doesn't let us compile this code in Swift 6 due to global shared mutable state without locks, and complains that this is not safe. Disable the safety here since we won't modify it. + * Remove the Ifdef once our minimum supported Swift version reaches 5.10 */ +#if swift(>=5.10) +nonisolated(unsafe) var allocator = aws_default_allocator()! +#else var allocator = aws_default_allocator()! +#endif /// An allocator is used to allocate memory on the heap. protocol Allocator { diff --git a/Source/AwsCommonRuntimeKit/crt/CommonRuntimeError.swift b/Source/AwsCommonRuntimeKit/crt/CommonRuntimeError.swift index c57b21820..258b020a4 100644 --- a/Source/AwsCommonRuntimeKit/crt/CommonRuntimeError.swift +++ b/Source/AwsCommonRuntimeKit/crt/CommonRuntimeError.swift @@ -8,7 +8,7 @@ public enum CommonRunTimeError: Error { case crtError(CRTError) } -public struct CRTError: Equatable { +public struct CRTError: Equatable, Sendable { public let code: Int32 public let message: String public let name: String diff --git a/Source/AwsCommonRuntimeKit/crt/Logger.swift b/Source/AwsCommonRuntimeKit/crt/Logger.swift index 03d24ba6b..1c25fd5af 100644 --- a/Source/AwsCommonRuntimeKit/crt/Logger.swift +++ b/Source/AwsCommonRuntimeKit/crt/Logger.swift @@ -10,7 +10,7 @@ public enum LogTarget { case filePath(String) } -public struct Logger { +public actor Logger { private static var logger: aws_logger? private static let lock = NSLock() @@ -55,7 +55,7 @@ public struct Logger { } } -public enum LogLevel { +public enum LogLevel: Sendable { case none case fatal case error diff --git a/Source/AwsCommonRuntimeKit/http/HTTP1Stream.swift b/Source/AwsCommonRuntimeKit/http/HTTP1Stream.swift index 089b3a8e3..5efca6efd 100644 --- a/Source/AwsCommonRuntimeKit/http/HTTP1Stream.swift +++ b/Source/AwsCommonRuntimeKit/http/HTTP1Stream.swift @@ -3,8 +3,10 @@ import AwsCHttp import Foundation +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. /// An HTTP1Stream represents a single HTTP/1.1 specific Http Request/Response. -public class HTTP1Stream: HTTPStream { +public class HTTP1Stream: HTTPStream, @unchecked Sendable { /// Stream keeps a reference to HttpConnection to keep it alive private let httpConnection: HTTPClientConnection diff --git a/Source/AwsCommonRuntimeKit/http/HTTP2ClientConnection.swift b/Source/AwsCommonRuntimeKit/http/HTTP2ClientConnection.swift index c4e2c1321..01265d6f1 100644 --- a/Source/AwsCommonRuntimeKit/http/HTTP2ClientConnection.swift +++ b/Source/AwsCommonRuntimeKit/http/HTTP2ClientConnection.swift @@ -5,7 +5,9 @@ import AwsCHttp import AwsCIo import Foundation -public class HTTP2ClientConnection: HTTPClientConnection { +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. +public class HTTP2ClientConnection: HTTPClientConnection, @unchecked Sendable { /// Creates a new http2 stream from the `HTTPRequestOptions` given. /// - Parameter requestOptions: An `HTTPRequestOptions` struct containing callbacks on diff --git a/Source/AwsCommonRuntimeKit/http/HTTP2Stream.swift b/Source/AwsCommonRuntimeKit/http/HTTP2Stream.swift index c51c24b14..260c754b4 100644 --- a/Source/AwsCommonRuntimeKit/http/HTTP2Stream.swift +++ b/Source/AwsCommonRuntimeKit/http/HTTP2Stream.swift @@ -4,8 +4,10 @@ import AwsCHttp import Foundation +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. /// An HTTP2Stream represents a single HTTP/2 specific HTTP Request/Response. -public class HTTP2Stream: HTTPStream { +public class HTTP2Stream: HTTPStream, @unchecked Sendable { private let httpConnection: HTTPClientConnection? // Called by Connection Manager diff --git a/Source/AwsCommonRuntimeKit/http/HTTP2StreamManager.swift b/Source/AwsCommonRuntimeKit/http/HTTP2StreamManager.swift index 34d4e3e8b..c827e8492 100644 --- a/Source/AwsCommonRuntimeKit/http/HTTP2StreamManager.swift +++ b/Source/AwsCommonRuntimeKit/http/HTTP2StreamManager.swift @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0. import AwsCHttp +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. /// Manages a Pool of HTTP/2 Streams. Creates and manages HTTP/2 connections under the hood. -public class HTTP2StreamManager { +public class HTTP2StreamManager: @unchecked Sendable { let rawValue: UnsafeMutablePointer public init(options: HTTP2StreamManagerOptions) throws { diff --git a/Source/AwsCommonRuntimeKit/http/HTTPClientConnection.swift b/Source/AwsCommonRuntimeKit/http/HTTPClientConnection.swift index a38643b2f..0cff7721c 100644 --- a/Source/AwsCommonRuntimeKit/http/HTTPClientConnection.swift +++ b/Source/AwsCommonRuntimeKit/http/HTTPClientConnection.swift @@ -6,7 +6,9 @@ import AwsCIo import Foundation // swiftlint:disable force_try -public class HTTPClientConnection { +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. +public class HTTPClientConnection: @unchecked Sendable { let rawValue: UnsafeMutablePointer /// This will keep the connection manager alive until connection is alive let manager: HTTPClientConnectionManager diff --git a/Source/AwsCommonRuntimeKit/http/HTTPStream.swift b/Source/AwsCommonRuntimeKit/http/HTTPStream.swift index 6ee20ce66..3c9fca30f 100644 --- a/Source/AwsCommonRuntimeKit/http/HTTPStream.swift +++ b/Source/AwsCommonRuntimeKit/http/HTTPStream.swift @@ -3,9 +3,11 @@ import AwsCHttp import Foundation +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. /// An base abstract class that represents a single Http Request/Response for both HTTP/1.1 and HTTP/2. /// Can be used to update the Window size, and get status code. -public class HTTPStream { +public class HTTPStream: @unchecked Sendable { let rawValue: UnsafeMutablePointer var callbackData: HTTPStreamCallbackCore diff --git a/Source/AwsCommonRuntimeKit/io/HostAddress.swift b/Source/AwsCommonRuntimeKit/io/HostAddress.swift index 1ce7646d1..907e072fa 100644 --- a/Source/AwsCommonRuntimeKit/io/HostAddress.swift +++ b/Source/AwsCommonRuntimeKit/io/HostAddress.swift @@ -4,7 +4,7 @@ import AwsCIo /// Represents a single HostAddress resolved by the Host Resolver -public struct HostAddress: CStruct { +public struct HostAddress: CStruct, Sendable { /// Address type is ipv4 or ipv6 public let addressType: HostAddressType diff --git a/Source/AwsCommonRuntimeKit/io/HostAddressType.swift b/Source/AwsCommonRuntimeKit/io/HostAddressType.swift index cc973853d..3140e6971 100644 --- a/Source/AwsCommonRuntimeKit/io/HostAddressType.swift +++ b/Source/AwsCommonRuntimeKit/io/HostAddressType.swift @@ -4,7 +4,7 @@ import AwsCIo /// Type of Host Address (ipv4 or ipv6) -public enum HostAddressType { +public enum HostAddressType: Sendable { case A case AAAA } diff --git a/Source/AwsCommonRuntimeKit/io/HostResolver.swift b/Source/AwsCommonRuntimeKit/io/HostResolver.swift index ca17a9cd8..72dcb21e6 100644 --- a/Source/AwsCommonRuntimeKit/io/HostResolver.swift +++ b/Source/AwsCommonRuntimeKit/io/HostResolver.swift @@ -23,8 +23,10 @@ public protocol HostResolverProtocol { func purgeCache() async } +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. /// CRT Host Resolver which performs async DNS lookups -public class HostResolver: HostResolverProtocol { +public class HostResolver: HostResolverProtocol, @unchecked Sendable { let rawValue: UnsafeMutablePointer let maxTTL: Int diff --git a/Source/AwsCommonRuntimeKit/io/retryer/RetryToken.swift b/Source/AwsCommonRuntimeKit/io/retryer/RetryToken.swift index 745c60d20..32300ab80 100644 --- a/Source/AwsCommonRuntimeKit/io/retryer/RetryToken.swift +++ b/Source/AwsCommonRuntimeKit/io/retryer/RetryToken.swift @@ -3,8 +3,10 @@ import AwsCIo +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. /// This is just a wrapper for aws_retry_token which user can not create themself but pass around once acquired. -public class RetryToken { +public class RetryToken: @unchecked Sendable { let rawValue: UnsafeMutablePointer init(rawValue: UnsafeMutablePointer) { diff --git a/Source/Elasticurl/CommandLine.swift b/Source/Elasticurl/CommandLine.swift index 4b2f3748f..e5b666dab 100644 --- a/Source/Elasticurl/CommandLine.swift +++ b/Source/Elasticurl/CommandLine.swift @@ -3,7 +3,6 @@ import Foundation import AwsCCommon -// swiftlint:disable trailing_whitespace struct CommandLineParser { /// A function to parse command line arguments @@ -27,8 +26,8 @@ struct CommandLineParser { break } if let char = UnicodeScalar(Int(opt)) { - if aws_cli_optarg != nil { - argumentsDict[String(char)] = String(cString: aws_cli_optarg) + if aws_get_cli_optarg() != nil { + argumentsDict[String(char)] = String(cString: aws_get_cli_optarg()) } else { // if argument doesnt have a value just mark it as present in the dictionary argumentsDict[String(char)] = true @@ -60,7 +59,9 @@ extension CLIHasArg: RawRepresentable, CaseIterable { } } -class AWSCLIOption { +// Swift cannot verify the sendability due to a pointer, and thread safety is handled in the C layer. +// So mark it as unchecked Sendable. +class AWSCLIOption: @unchecked Sendable { let rawValue: aws_cli_option let name: UnsafeMutablePointer init(name: String, hasArg: CLIHasArg, flag: UnsafeMutablePointer? = nil, val: String) { diff --git a/Source/Elasticurl/Elasticurl.swift b/Source/Elasticurl/Elasticurl.swift index bc782e230..3829dc7f4 100644 --- a/Source/Elasticurl/Elasticurl.swift +++ b/Source/Elasticurl/Elasticurl.swift @@ -6,7 +6,7 @@ import AwsCommonRuntimeKit import Foundation // swiftlint:disable cyclomatic_complexity function_body_length -struct Context { +struct Context: @unchecked Sendable { // args public var logLevel: LogLevel = .trace public var verb: String = "GET" @@ -29,9 +29,8 @@ struct Context { @main struct Elasticurl { private static let version = "0.1.0" - private static var context = Context() - static func parseArguments() { + static func parseArguments() -> Context { let optionString = "a:b:c:e:f:H:d:g:j:l:m:M:GPHiko:t:v:VwWh" let options = [ElasticurlOptions.caCert.rawValue, ElasticurlOptions.caPath.rawValue, @@ -63,6 +62,7 @@ struct Elasticurl { arguments: CommandLine.unsafeArgv, optionString: optionString, options: options) + var context = Context() if let caCert = argumentsDict["a"] as? String { context.caCert = caCert } @@ -176,6 +176,7 @@ struct Elasticurl { exit(-1) } context.url = url + return context } static func showHelp() { @@ -202,22 +203,24 @@ struct Elasticurl { print(" -h, --help: Display this message and quit.") } - static func createOutputFile() { + static func createOutputFile(context: Context) -> Context { + var context = context if let fileName = context.outputFileName { let fileManager = FileManager.default let path = FileManager.default.currentDirectoryPath + "/" + fileName fileManager.createFile(atPath: path, contents: nil, attributes: nil) context.outputStream = FileHandle(forWritingAtPath: fileName) ?? FileHandle.standardOutput } + return context } - static func writeData(data: Data) { + static func writeData(data: Data, context: Context) { context.outputStream.write(data) } static func main() async { - parseArguments() - createOutputFile() + var context = parseArguments() + context = createOutputFile(context: context) if let traceFile = context.traceFile { print("enable logging with trace file") try? Logger.initialize(target: .filePath(traceFile), level: context.logLevel) @@ -226,10 +229,10 @@ struct Elasticurl { try? Logger.initialize(target: .standardOutput, level: context.logLevel) } - await run() + await run(context) } - static func run() async { + static func run(_ context: Context) async { do { guard let host = context.url.host else { print("no proper host was parsed from the url. quitting.") @@ -291,7 +294,7 @@ struct Elasticurl { } let onBody: HTTPRequestOptions.OnIncomingBody = { bodyChunk in - writeData(data: bodyChunk) + writeData(data: bodyChunk, context: context) } let onComplete: HTTPRequestOptions.OnStreamComplete = { result in diff --git a/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift b/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift index ed5c5ee15..c0d45567d 100644 --- a/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift +++ b/Test/AwsCommonRuntimeKitTests/XCBaseTestCase.swift @@ -7,7 +7,7 @@ import AwsCCommon class XCBaseTestCase: XCTestCase { internal let tracingAllocator = TracingAllocator(tracingStacksOf: allocator) - + override func setUp() { super.setUp() // XCode currently lacks a way to enable logs exclusively for failed tests only. @@ -67,6 +67,15 @@ extension XCTestCase { #endif } + func awaitExpectation(_ expectations: [XCTestExpectation]) async { + // Remove the Ifdef once our minimum supported Swift version reaches 5.10 + #if swift(>=5.10) + await fulfillment(of: expectations, timeout: 5) + #else + wait(for: expectations, timeout: 5) + #endif + } + /// Return the environment variable value, or Skip the test if env var is not set. func getEnvironmentVarOrSkipTest(environmentVarName name: String) throws -> String { guard let result = ProcessInfo.processInfo.environment[name] else { diff --git a/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift b/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift index 2e12bdcc4..14a1bbf78 100644 --- a/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift +++ b/Test/AwsCommonRuntimeKitTests/auth/CredentialsProviderTests.swift @@ -66,7 +66,7 @@ class CredentialsProviderTests: XCBaseTestCase { XCTAssertNotNil(credentials) assertCredentials(credentials: credentials) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } // TODO: change this test to not pass accountId separately once the source function handles it @@ -81,7 +81,7 @@ class CredentialsProviderTests: XCBaseTestCase { XCTAssertNotNil(credentials) assertCredentials(credentials: credentials) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCredentialsProviderEnvThrow() async { @@ -92,7 +92,7 @@ class CredentialsProviderTests: XCBaseTestCase { } catch { exceptionWasThrown.fulfill() } - wait(for: [exceptionWasThrown], timeout: 15) + await awaitExpectation([exceptionWasThrown]) } func withEnvironmentCredentialsClosure(closure: () async throws -> T) async rethrows -> T { @@ -129,7 +129,7 @@ class CredentialsProviderTests: XCBaseTestCase { XCTAssertEqual("accessKey", credentials.getAccessKey()) XCTAssertEqual("secretKey", credentials.getSecret()) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCreateCredentialsProviderProcess() async throws { @@ -145,7 +145,7 @@ class CredentialsProviderTests: XCBaseTestCase { XCTAssertEqual("SecretAccessKey123", credentials.getSecret()) XCTAssertEqual("SessionToken123", credentials.getSessionToken()) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCreateCredentialsProviderSSO() async throws { @@ -162,7 +162,7 @@ class CredentialsProviderTests: XCBaseTestCase { // get credentials will fail in CI due to expired token, so do not assert on credentials. _ = try? await provider.getCredentials() } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCreateCredentialsProviderImds() async throws { @@ -170,7 +170,7 @@ class CredentialsProviderTests: XCBaseTestCase { _ = try CredentialsProvider(source: .imds(bootstrap: getClientBootstrap(), shutdownCallback: getShutdownCallback())) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCreateCredentialsProviderCache() async throws { @@ -184,7 +184,7 @@ class CredentialsProviderTests: XCBaseTestCase { XCTAssertNotNil(credentials) assertCredentials(credentials: credentials) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCreateAWSCredentialsProviderDefaultChain() async throws { @@ -202,7 +202,7 @@ class CredentialsProviderTests: XCBaseTestCase { assertCredentials(credentials: credentials) } } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCreateDestroyStsWebIdentityInvalidEnv() async throws { @@ -212,7 +212,7 @@ class CredentialsProviderTests: XCBaseTestCase { fileBasedConfiguration: FileBasedConfiguration())) ) } - + func testCreateDestroyStsWebIdentity() async throws { _ = try! CredentialsProvider(source: .stsWebIdentity( bootstrap: getClientBootstrap(), @@ -250,6 +250,6 @@ class CredentialsProviderTests: XCBaseTestCase { } catch { exceptionWasThrown.fulfill() } - wait(for: [exceptionWasThrown], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } } diff --git a/Test/AwsCommonRuntimeKitTests/crt/ShutDownCallbackOptionsTests.swift b/Test/AwsCommonRuntimeKitTests/crt/ShutDownCallbackOptionsTests.swift index 9b9e00ae6..01fea4c96 100644 --- a/Test/AwsCommonRuntimeKitTests/crt/ShutDownCallbackOptionsTests.swift +++ b/Test/AwsCommonRuntimeKitTests/crt/ShutDownCallbackOptionsTests.swift @@ -13,6 +13,6 @@ class ShutdownCallbackOptionsTests: XCBaseTestCase { shutdownWasCalled.fulfill() } } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } } diff --git a/Test/AwsCommonRuntimeKitTests/event-stream/EventStreamTests.swift b/Test/AwsCommonRuntimeKitTests/event-stream/EventStreamTests.swift index 18bfd7b9a..b8ef55be4 100644 --- a/Test/AwsCommonRuntimeKitTests/event-stream/EventStreamTests.swift +++ b/Test/AwsCommonRuntimeKitTests/event-stream/EventStreamTests.swift @@ -6,7 +6,6 @@ import AwsCEventStream @testable import AwsCommonRuntimeKit class EventStreamTests: XCBaseTestCase { - let semaphore = DispatchSemaphore(value: 0) func testEncodeDecodeHeaders() async throws { let onCompleteWasCalled = XCTestExpectation(description: "OnComplete was called") @@ -48,7 +47,7 @@ class EventStreamTests: XCBaseTestCase { }) try decoder.decode(data: encoded) XCTAssertTrue(headers.elementsEqual(decodedHeaders)) - wait(for: [onCompleteWasCalled], timeout: 1) + await awaitExpectation([onCompleteWasCalled]) } func testEncodeDecodePayload() async throws { @@ -76,7 +75,7 @@ class EventStreamTests: XCBaseTestCase { }) try decoder.decode(data: encoded) XCTAssertEqual(payload, decodedPayload) - wait(for: [onCompleteWasCalled], timeout: 1) + await awaitExpectation([onCompleteWasCalled]) } func testEncodeOutOfScope() async throws { @@ -114,7 +113,7 @@ class EventStreamTests: XCBaseTestCase { let expectedHeaders = [EventStreamHeader(name: "int16", value: .int32(value: 16))] XCTAssertTrue(expectedHeaders.elementsEqual(decodedHeaders)) - wait(for: [onCompleteWasCalled], timeout: 1) + await awaitExpectation([onCompleteWasCalled]) } func testDecodeByteByByte() async throws { @@ -150,7 +149,7 @@ class EventStreamTests: XCBaseTestCase { XCTAssertEqual(payload, decodedPayload) XCTAssertTrue(headers.elementsEqual(decodedHeaders)) - wait(for: [onCompleteWasCalled], timeout: 1) + await awaitExpectation([onCompleteWasCalled]) } func testEmpty() async throws { @@ -175,6 +174,6 @@ class EventStreamTests: XCBaseTestCase { XCTFail("Error occurred. Code: \(code)\nMessage:\(message)") }) try decoder.decode(data: encoded) - wait(for: [onCompleteWasCalled], timeout: 1) + await awaitExpectation([onCompleteWasCalled]) } } diff --git a/Test/AwsCommonRuntimeKitTests/http/HTTP2ClientConnectionTests.swift b/Test/AwsCommonRuntimeKitTests/http/HTTP2ClientConnectionTests.swift index fc41097ed..38c602e1a 100644 --- a/Test/AwsCommonRuntimeKitTests/http/HTTP2ClientConnectionTests.swift +++ b/Test/AwsCommonRuntimeKitTests/http/HTTP2ClientConnectionTests.swift @@ -3,19 +3,19 @@ import XCTest @testable import AwsCommonRuntimeKit -class HTTP2ClientConnectionTests: HTTPClientTestFixture { +class HTTP2ClientConnectionTests: XCBaseTestCase { let expectedVersion = HTTPVersion.version_2 func testGetHTTP2RequestVersion() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) let connection = try await connectionManager.acquireConnection() XCTAssertEqual(connection.httpVersion, HTTPVersion.version_2) } // Test that the binding works not the actual functionality. C part has tests for functionality func testHTTP2UpdateSetting() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) let connection = try await connectionManager.acquireConnection() if let connection = connection as? HTTP2ClientConnection { try await connection.updateSetting(setting: HTTP2Settings(enablePush: false)) @@ -26,7 +26,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { // Test that the binding works not the actual functionality. C part has tests for functionality func testHTTP2UpdateSettingEmpty() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) let connection = try await connectionManager.acquireConnection() if let connection = connection as? HTTP2ClientConnection { try await connection.updateSetting(setting: HTTP2Settings()) @@ -37,7 +37,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { // Test that the binding works not the actual functionality. C part has tests for functionality func testHTTP2SendPing() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) let connection = try await connectionManager.acquireConnection() if let connection = connection as? HTTP2ClientConnection { var time = try await connection.sendPing() @@ -51,7 +51,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { // Test that the binding works not the actual functionality. C part has tests for functionality func testHTTP2SendGoAway() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) let connection = try await connectionManager.acquireConnection() if let connection = connection as? HTTP2ClientConnection { connection.sendGoAway(error: .internalError, allowMoreStreams: false) @@ -61,8 +61,8 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { } func testGetHttpsRequest() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) - let response = try await sendHTTPRequest( + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let response = try await HTTPClientTestFixture.sendHTTPRequest( method: "GET", endpoint: "httpbin.org", path: "/get", @@ -71,7 +71,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { requestVersion: .version_2) // The first header of response has to be ":status" for HTTP/2 response XCTAssertEqual(response.headers[0].name, ":status") - let response2 = try await sendHTTPRequest( + let response2 = try await HTTPClientTestFixture.sendHTTPRequest( method: "GET", endpoint: "httpbin.org", path: "/delete", @@ -84,8 +84,8 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { func testGetHttpsRequestWithHTTP1_1Request() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) - let response = try await sendHTTPRequest( + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "httpbin.org", alpnList: ["h2","http/1.1"]) + let response = try await HTTPClientTestFixture.sendHTTPRequest( method: "GET", endpoint: "httpbin.org", path: "/get", @@ -94,7 +94,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { requestVersion: .version_1_1) // The first header of response has to be ":status" for HTTP/2 response XCTAssertEqual(response.headers[0].name, ":status") - let response2 = try await sendHTTPRequest( + let response2 = try await HTTPClientTestFixture.sendHTTPRequest( method: "GET", endpoint: "httpbin.org", path: "/delete", @@ -106,8 +106,8 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { } func testHTTP2Download() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "d1cz66xoahf9cl.cloudfront.net", alpnList: ["h2","http/1.1"]) - let response = try await sendHTTPRequest( + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "d1cz66xoahf9cl.cloudfront.net", alpnList: ["h2","http/1.1"]) + let response = try await HTTPClientTestFixture.sendHTTPRequest( method: "GET", endpoint: "d1cz66xoahf9cl.cloudfront.net", path: "/http_test_doc.txt", @@ -121,8 +121,8 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { } func testHTTP2DownloadWithHTTP1_1Request() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "d1cz66xoahf9cl.cloudfront.net", alpnList: ["h2","http/1.1"]) - let response = try await sendHTTPRequest( + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "d1cz66xoahf9cl.cloudfront.net", alpnList: ["h2","http/1.1"]) + let response = try await HTTPClientTestFixture.sendHTTPRequest( method: "GET", endpoint: "d1cz66xoahf9cl.cloudfront.net", path: "/http_test_doc.txt", @@ -136,12 +136,12 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { } func testHTTP2StreamUpload() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: "nghttp2.org", alpnList: ["h2"]) - let semaphore = DispatchSemaphore(value: 0) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: "nghttp2.org", alpnList: ["h2"]) + let semaphore = TestSemaphore(value: 0) var httpResponse = HTTPResponse() var onCompleteCalled = false let testBody = "testBody" - let http2RequestOptions = try getHTTP2RequestOptions( + let http2RequestOptions = try HTTPClientTestFixture.getHTTP2RequestOptions( method: "PUT", path: "/httpbin/put", authority: "nghttp2.org", @@ -156,7 +156,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { let streamBase = try connection.makeRequest(requestOptions: http2RequestOptions) try streamBase.activate() XCTAssertFalse(onCompleteCalled) - let data = TEST_DOC_LINE.data(using: .utf8)! + let data = HTTPClientTestFixture.TEST_DOC_LINE.data(using: .utf8)! for chunk in data.chunked(into: 5) { try await streamBase.writeChunk(chunk: chunk, endOfStream: false) XCTAssertFalse(onCompleteCalled) @@ -167,7 +167,7 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { try await Task.sleep(nanoseconds: 5_000_000_000) XCTAssertFalse(onCompleteCalled) try await streamBase.writeChunk(chunk: Data(), endOfStream: true) - semaphore.wait() + await semaphore.wait() XCTAssertTrue(onCompleteCalled) XCTAssertNil(httpResponse.error) XCTAssertEqual(httpResponse.statusCode, 200) @@ -178,6 +178,6 @@ class HTTP2ClientConnectionTests: HTTPClientTestFixture { } let body: Response = try! JSONDecoder().decode(Response.self, from: httpResponse.body) - XCTAssertEqual(body.data, testBody + TEST_DOC_LINE) + XCTAssertEqual(body.data, testBody + HTTPClientTestFixture.TEST_DOC_LINE) } } diff --git a/Test/AwsCommonRuntimeKitTests/http/HTTP2StreamManagerTests.swift b/Test/AwsCommonRuntimeKitTests/http/HTTP2StreamManagerTests.swift index 110a60761..b961ae369 100644 --- a/Test/AwsCommonRuntimeKitTests/http/HTTP2StreamManagerTests.swift +++ b/Test/AwsCommonRuntimeKitTests/http/HTTP2StreamManagerTests.swift @@ -4,7 +4,7 @@ import XCTest @testable import AwsCommonRuntimeKit -class HTT2StreamManagerTests: HTTPClientTestFixture { +class HTT2StreamManagerTests: XCBaseTestCase { let endpoint = "d1cz66xoahf9cl.cloudfront.net"; // Use cloudfront for HTTP/2 let path = "/random_32_byte.data"; @@ -102,16 +102,16 @@ class HTT2StreamManagerTests: HTTPClientTestFixture { func testHTTP2Stream() async throws { let streamManager = try makeStreamManger(host: endpoint) - _ = try await sendHTTP2Request(method: "GET", path: path, authority: endpoint, streamManager: streamManager) + _ = try await HTTPClientTestFixture.sendHTTP2Request(method: "GET", path: path, authority: endpoint, streamManager: streamManager) } func testHTTP2StreamUpload() async throws { let streamManager = try makeStreamManger(host: "nghttp2.org") - let semaphore = DispatchSemaphore(value: 0) + let semaphore = TestSemaphore(value: 0) var httpResponse = HTTPResponse() var onCompleteCalled = false let testBody = "testBody" - let http2RequestOptions = try getHTTP2RequestOptions( + let http2RequestOptions = try HTTPClientTestFixture.getHTTP2RequestOptions( method: "PUT", path: "/httpbin/put", authority: "nghttp2.org", @@ -128,7 +128,7 @@ class HTT2StreamManagerTests: HTTPClientTestFixture { let metrics = streamManager.fetchMetrics() XCTAssertTrue(metrics.availableConcurrency > 0) XCTAssertTrue(metrics.leasedConcurrency > 0) - let data = TEST_DOC_LINE.data(using: .utf8)! + let data = HTTPClientTestFixture.TEST_DOC_LINE.data(using: .utf8)! for chunk in data.chunked(into: 5) { try await stream.writeChunk(chunk: chunk, endOfStream: false) XCTAssertFalse(onCompleteCalled) @@ -139,7 +139,7 @@ class HTT2StreamManagerTests: HTTPClientTestFixture { try await Task.sleep(nanoseconds: 5_000_000_000) XCTAssertFalse(onCompleteCalled) try await stream.writeChunk(chunk: Data(), endOfStream: true) - semaphore.wait() + await semaphore.wait() XCTAssertTrue(onCompleteCalled) XCTAssertNil(httpResponse.error) XCTAssertEqual(httpResponse.statusCode, 200) @@ -150,13 +150,13 @@ class HTT2StreamManagerTests: HTTPClientTestFixture { } let body: Response = try! JSONDecoder().decode(Response.self, from: httpResponse.body) - XCTAssertEqual(body.data, testBody + TEST_DOC_LINE) + XCTAssertEqual(body.data, testBody + HTTPClientTestFixture.TEST_DOC_LINE) } // Test that the binding works not the actual functionality. C part has tests for functionality func testHTTP2StreamReset() async throws { let streamManager = try makeStreamManger(host: endpoint) - let http2RequestOptions = try getHTTP2RequestOptions( + let http2RequestOptions = try HTTPClientTestFixture.getHTTP2RequestOptions( method: "PUT", path: "/httpbin/put", authority: "nghttp2.org") @@ -171,18 +171,12 @@ class HTT2StreamManagerTests: HTTPClientTestFixture { func testHTTP2ParallelStreams(count: Int) async throws { let streamManager = try makeStreamManger(host: "nghttp2.org") - let requestCompleteExpectation = XCTestExpectation(description: "Request was completed successfully") - requestCompleteExpectation.expectedFulfillmentCount = count - await withTaskGroup(of: Void.self) { taskGroup in + return await withTaskGroup(of: Void.self) { taskGroup in for _ in 1...count { taskGroup.addTask { - _ = try! await self.sendHTTP2Request(method: "GET", path: "/httpbin/get", authority: "nghttp2.org", streamManager: streamManager, onComplete: { _ in - requestCompleteExpectation.fulfill() - }) + _ = try! await HTTPClientTestFixture.sendHTTP2Request(method: "GET", path: "/httpbin/get", authority: "nghttp2.org", streamManager: streamManager) } } } - wait(for: [requestCompleteExpectation], timeout: 15) - print("Request were successfully completed.") } } diff --git a/Test/AwsCommonRuntimeKitTests/http/HTTPClientConnectionManagerTests.swift b/Test/AwsCommonRuntimeKitTests/http/HTTPClientConnectionManagerTests.swift index 748db1973..8c87bca56 100644 --- a/Test/AwsCommonRuntimeKitTests/http/HTTPClientConnectionManagerTests.swift +++ b/Test/AwsCommonRuntimeKitTests/http/HTTPClientConnectionManagerTests.swift @@ -37,6 +37,6 @@ class HTTPClientConnectionManagerTests: XCBaseTestCase { } _ = try HTTPClientConnectionManager(options: httpClientOptions) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } } diff --git a/Test/AwsCommonRuntimeKitTests/http/HTTPClientTestFixture.swift b/Test/AwsCommonRuntimeKitTests/http/HTTPClientTestFixture.swift index 80b87f9d4..73bcabfc4 100644 --- a/Test/AwsCommonRuntimeKitTests/http/HTTPClientTestFixture.swift +++ b/Test/AwsCommonRuntimeKitTests/http/HTTPClientTestFixture.swift @@ -12,14 +12,44 @@ struct HTTPResponse { var version: HTTPVersion? } +/* + * Async Semaphore compatible with Swift's structured concurrency. Swift complains about the normal sync Semaphore since it's a blocking wait. + * See: https://forums.swift.org/t/semaphore-alternatives-for-structured-concurrency/59353 + */ +actor TestSemaphore { + private var count: Int + private var waiters: [CheckedContinuation] = [] + + init(value: Int = 0) { + self.count = value + } + + func wait() async { + count -= 1 + if count >= 0 { return } + await withCheckedContinuation { + waiters.append($0) + } + } + + func signal(count: Int = 1) { + assert(count >= 1) + self.count += count + for _ in 0.. HTTPResponse { var httpResponse = HTTPResponse() - let semaphore = DispatchSemaphore(value: 0) + let semaphore = TestSemaphore(value: 0) let httpRequestOptions: HTTPRequestOptions if requestVersion == HTTPVersion.version_2 { @@ -68,7 +98,7 @@ class HTTPClientTestFixture: XCBaseTestCase { XCTAssertEqual(connection.httpVersion, expectedVersion) let stream = try connection.makeRequest(requestOptions: httpRequestOptions) try stream.activate() - semaphore.wait() + await semaphore.wait() } XCTAssertNil(httpResponse.error) @@ -76,7 +106,7 @@ class HTTPClientTestFixture: XCBaseTestCase { return httpResponse } - func sendHTTP2Request(method: String, + static func sendHTTP2Request(method: String, path: String, scheme: String = "https", authority: String, @@ -90,7 +120,7 @@ class HTTPClientTestFixture: XCBaseTestCase { onComplete: HTTPRequestOptions.OnStreamComplete? = nil) async throws -> HTTPResponse { var httpResponse = HTTPResponse() - let semaphore = DispatchSemaphore(value: 0) + let semaphore = TestSemaphore(value: 0) let httpRequestOptions = try getHTTP2RequestOptions( method: method, @@ -109,7 +139,7 @@ class HTTPClientTestFixture: XCBaseTestCase { print("Attempt#\(i) to send an HTTP request") let stream = try await streamManager.acquireStream(requestOptions: httpRequestOptions) try stream.activate() - semaphore.wait() + await semaphore.wait() } XCTAssertNil(httpResponse.error) @@ -117,7 +147,7 @@ class HTTPClientTestFixture: XCBaseTestCase { return httpResponse } - func getHttpConnectionManager(endpoint: String, + static func getHttpConnectionManager(endpoint: String, ssh: Bool = true, port: Int = 443, alpnList: [String] = ["http/1.1"], @@ -144,9 +174,9 @@ class HTTPClientTestFixture: XCBaseTestCase { return try HTTPClientConnectionManager(options: httpClientOptions) } - func getRequestOptions(request: HTTPRequestBase, + static func getRequestOptions(request: HTTPRequestBase, response: UnsafeMutablePointer? = nil, - semaphore: DispatchSemaphore? = nil, + semaphore: TestSemaphore? = nil, onResponse: HTTPRequestOptions.OnResponse? = nil, onBody: HTTPRequestOptions.OnIncomingBody? = nil, onComplete: HTTPRequestOptions.OnStreamComplete? = nil, @@ -170,18 +200,18 @@ class HTTPClientTestFixture: XCBaseTestCase { response?.pointee.error = error } onComplete?(result) - semaphore?.signal() + Task { await semaphore?.signal() } }, http2ManualDataWrites: http2ManualDataWrites) } - func getHTTPRequestOptions(method: String, + static func getHTTPRequestOptions(method: String, endpoint: String, path: String, body: String = "", response: UnsafeMutablePointer? = nil, - semaphore: DispatchSemaphore? = nil, + semaphore: TestSemaphore? = nil, headers: [HTTPHeader] = [HTTPHeader](), onResponse: HTTPRequestOptions.OnResponse? = nil, onBody: HTTPRequestOptions.OnIncomingBody? = nil, @@ -206,14 +236,14 @@ class HTTPClientTestFixture: XCBaseTestCase { onComplete: onComplete) } - func getHTTP2RequestOptions(method: String, + static func getHTTP2RequestOptions(method: String, path: String, scheme: String = "https", authority: String, body: String = "", manualDataWrites: Bool = false, response: UnsafeMutablePointer? = nil, - semaphore: DispatchSemaphore? = nil, + semaphore: TestSemaphore? = nil, onResponse: HTTPRequestOptions.OnResponse? = nil, onBody: HTTPRequestOptions.OnIncomingBody? = nil, onComplete: HTTPRequestOptions.OnStreamComplete? = nil, diff --git a/Test/AwsCommonRuntimeKitTests/http/HTTPProxyTests.swift b/Test/AwsCommonRuntimeKitTests/http/HTTPProxyTests.swift index 59f75a9a8..dfea036c0 100644 --- a/Test/AwsCommonRuntimeKitTests/http/HTTPProxyTests.swift +++ b/Test/AwsCommonRuntimeKitTests/http/HTTPProxyTests.swift @@ -6,7 +6,7 @@ import AwsCAuth import Foundation @testable import AwsCommonRuntimeKit -class HTTPProxyTests: HTTPClientTestFixture { +class HTTPProxyTests: XCBaseTestCase { let HTTPProxyHost = ProcessInfo.processInfo.environment["AWS_TEST_HTTP_PROXY_HOST"] let HTTPProxyPort = ProcessInfo.processInfo.environment["AWS_TEST_HTTP_PROXY_PORT"] @@ -185,13 +185,13 @@ class HTTPProxyTests: HTTPClientTestFixture { let uri = getURIFromTestType(type: type) let port = getPortFromTestType(type: type) let proxyOptions = try getProxyOptions(type: type, authType: authType) - let manager = try await getHttpConnectionManager( + let manager = try await HTTPClientTestFixture.getHttpConnectionManager( endpoint: uri, ssh: getSSH(type: type), port: port, alpnList: ["http/1.1"], proxyOptions: proxyOptions) - _ = try await sendHTTPRequest(method: "GET", endpoint: uri, connectionManager: manager) + _ = try await HTTPClientTestFixture.sendHTTPRequest(method: "GET", endpoint: uri, connectionManager: manager) } } diff --git a/Test/AwsCommonRuntimeKitTests/http/HTTPTests.swift b/Test/AwsCommonRuntimeKitTests/http/HTTPTests.swift index c7c6ef796..09e9eab37 100644 --- a/Test/AwsCommonRuntimeKitTests/http/HTTPTests.swift +++ b/Test/AwsCommonRuntimeKitTests/http/HTTPTests.swift @@ -6,35 +6,35 @@ import XCTest import AwsCCommon import AwsCHttp -class HTTPTests: HTTPClientTestFixture { +class HTTPTests: XCBaseTestCase { let host = "postman-echo.com" let getPath = "/get" func testGetHTTPSRequest() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) - _ = try await sendHTTPRequest(method: "GET", endpoint: host, path: getPath, connectionManager: connectionManager) - _ = try await sendHTTPRequest(method: "GET", endpoint: host, path: "/delete", expectedStatus: 404, connectionManager: connectionManager) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + _ = try await HTTPClientTestFixture.sendHTTPRequest(method: "GET", endpoint: host, path: getPath, connectionManager: connectionManager) + _ = try await HTTPClientTestFixture.sendHTTPRequest(method: "GET", endpoint: host, path: "/delete", expectedStatus: 404, connectionManager: connectionManager) } func testGetHTTPSRequestWithUtf8Header() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let utf8Header = HTTPHeader(name: "TestHeader", value: "TestValueWithEmoji🤯") - let headers = try await sendHTTPRequest(method: "GET", endpoint: host, path: "/response-headers?\(utf8Header.name)=\(utf8Header.value)", connectionManager: connectionManager).headers + let headers = try await HTTPClientTestFixture.sendHTTPRequest(method: "GET", endpoint: host, path: "/response-headers?\(utf8Header.name)=\(utf8Header.value)", connectionManager: connectionManager).headers XCTAssertTrue(headers.contains(where: {$0.name == utf8Header.name && $0.value==utf8Header.value})) } func testGetHTTPRequest() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: false, port: 80) - _ = try await sendHTTPRequest(method: "GET", endpoint: host, path: getPath, connectionManager: connectionManager) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: false, port: 80) + _ = try await HTTPClientTestFixture.sendHTTPRequest(method: "GET", endpoint: host, path: getPath, connectionManager: connectionManager) } func testPutHTTPRequest() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) - let response = try await sendHTTPRequest( + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let response = try await HTTPClientTestFixture.sendHTTPRequest( method: "PUT", endpoint: host, path: "/put", - body: TEST_DOC_LINE, + body: HTTPClientTestFixture.TEST_DOC_LINE, connectionManager: connectionManager) // Parse json body @@ -42,15 +42,15 @@ class HTTPTests: HTTPClientTestFixture { let data: String } let body: Response = try! JSONDecoder().decode(Response.self, from: response.body) - XCTAssertEqual(body.data, TEST_DOC_LINE) + XCTAssertEqual(body.data, HTTPClientTestFixture.TEST_DOC_LINE) } func testHTTPChunkTransferEncoding() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, alpnList: ["http/1.1"]) - let semaphore = DispatchSemaphore(value: 0) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, alpnList: ["http/1.1"]) + let semaphore = TestSemaphore(value: 0) var httpResponse = HTTPResponse() var onCompleteCalled = false - let httpRequestOptions = try getHTTPRequestOptions( + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions( method: "PUT", endpoint: host, path: "/put", @@ -67,7 +67,7 @@ class HTTPTests: HTTPClientTestFixture { let metrics = connectionManager.fetchMetrics() XCTAssertTrue(metrics.leasedConcurrency > 0) - let data = TEST_DOC_LINE.data(using: .utf8)! + let data = HTTPClientTestFixture.TEST_DOC_LINE.data(using: .utf8)! for chunk in data.chunked(into: 5) { try await streamBase.writeChunk(chunk: chunk, endOfStream: false) XCTAssertFalse(onCompleteCalled) @@ -78,7 +78,7 @@ class HTTPTests: HTTPClientTestFixture { try await Task.sleep(nanoseconds: 5_000_000_000) XCTAssertFalse(onCompleteCalled) try await streamBase.writeChunk(chunk: Data(), endOfStream: true) - semaphore.wait() + await semaphore.wait() XCTAssertTrue(onCompleteCalled) XCTAssertNil(httpResponse.error) XCTAssertEqual(httpResponse.statusCode, 200) @@ -89,15 +89,15 @@ class HTTPTests: HTTPClientTestFixture { } let body: Response = try! JSONDecoder().decode(Response.self, from: httpResponse.body) - XCTAssertEqual(body.data, TEST_DOC_LINE) + XCTAssertEqual(body.data, HTTPClientTestFixture.TEST_DOC_LINE) } func testHTTPChunkTransferEncodingWithDataInLastChunk() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, alpnList: ["http/1.1"]) - let semaphore = DispatchSemaphore(value: 0) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, alpnList: ["http/1.1"]) + let semaphore = TestSemaphore(value: 0) var httpResponse = HTTPResponse() var onCompleteCalled = false - let httpRequestOptions = try getHTTPRequestOptions( + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions( method: "PUT", endpoint: host, path: "/put", @@ -111,7 +111,7 @@ class HTTPTests: HTTPClientTestFixture { let streamBase = try connection.makeRequest(requestOptions: httpRequestOptions) try streamBase.activate() XCTAssertFalse(onCompleteCalled) - let data = TEST_DOC_LINE.data(using: .utf8)! + let data = HTTPClientTestFixture.TEST_DOC_LINE.data(using: .utf8)! for chunk in data.chunked(into: 5) { try await streamBase.writeChunk(chunk: chunk, endOfStream: false) XCTAssertFalse(onCompleteCalled) @@ -124,7 +124,7 @@ class HTTPTests: HTTPClientTestFixture { let lastChunkData = Data("last chunk data".utf8) try await streamBase.writeChunk(chunk: lastChunkData, endOfStream: true) - semaphore.wait() + await semaphore.wait() XCTAssertTrue(onCompleteCalled) XCTAssertNil(httpResponse.error) XCTAssertEqual(httpResponse.statusCode, 200) @@ -135,14 +135,14 @@ class HTTPTests: HTTPClientTestFixture { } let body: Response = try! JSONDecoder().decode(Response.self, from: httpResponse.body) - XCTAssertEqual(body.data, TEST_DOC_LINE + String(decoding: lastChunkData, as: UTF8.self)) + XCTAssertEqual(body.data, HTTPClientTestFixture.TEST_DOC_LINE + String(decoding: lastChunkData, as: UTF8.self)) } func testHTTPStreamIsReleasedIfNotActivated() async throws { do { - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let connection = try await connectionManager.acquireConnection() _ = try connection.makeRequest(requestOptions: httpRequestOptions) } catch let err { @@ -151,67 +151,67 @@ class HTTPTests: HTTPClientTestFixture { } func testStreamLivesUntilComplete() async throws { - let semaphore = DispatchSemaphore(value: 0) - + let semaphore = TestSemaphore(value: 0) do { - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath, semaphore: semaphore) - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath, semaphore: semaphore) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let connection = try await connectionManager.acquireConnection() let stream = try connection.makeRequest(requestOptions: httpRequestOptions) try stream.activate() } - semaphore.wait() + await semaphore.wait() } func testManagerLivesUntilComplete() async throws { var connection: HTTPClientConnection! = nil - let semaphore = DispatchSemaphore(value: 0) + let semaphore = TestSemaphore(value: 0) do { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) connection = try await connectionManager.acquireConnection() } - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath, semaphore: semaphore) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath, semaphore: semaphore) let stream = try connection.makeRequest(requestOptions: httpRequestOptions) try stream.activate() - semaphore.wait() + await semaphore.wait() } func testConnectionLivesUntilComplete() async throws { var stream: HTTPStream! = nil - let semaphore = DispatchSemaphore(value: 0) + let semaphore = TestSemaphore(value: 0) + do { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let connection = try await connectionManager.acquireConnection() - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath, semaphore: semaphore) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath, semaphore: semaphore) stream = try connection.makeRequest(requestOptions: httpRequestOptions) } try stream.activate() - semaphore.wait() + await semaphore.wait() } func testConnectionCloseThrow() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let connection = try await connectionManager.acquireConnection() connection.close() - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) XCTAssertThrowsError( _ = try connection.makeRequest(requestOptions: httpRequestOptions)) } func testConnectionCloseActivateThrow() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let connection = try await connectionManager.acquireConnection() - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) let stream = try connection.makeRequest(requestOptions: httpRequestOptions) connection.close() XCTAssertThrowsError(try stream.activate()) } func testConnectionCloseIsIdempotent() async throws { - let connectionManager = try await getHttpConnectionManager(endpoint: host, ssh: true, port: 443) + let connectionManager = try await HTTPClientTestFixture.getHttpConnectionManager(endpoint: host, ssh: true, port: 443) let connection = try await connectionManager.acquireConnection() - let httpRequestOptions = try getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) + let httpRequestOptions = try HTTPClientTestFixture.getHTTPRequestOptions(method: "GET", endpoint: host, path: getPath) let stream = try connection.makeRequest(requestOptions: httpRequestOptions) connection.close() connection.close() diff --git a/Test/AwsCommonRuntimeKitTests/io/BootstrapTests.swift b/Test/AwsCommonRuntimeKitTests/io/BootstrapTests.swift index 3e9d22d01..9bc5f0db8 100644 --- a/Test/AwsCommonRuntimeKitTests/io/BootstrapTests.swift +++ b/Test/AwsCommonRuntimeKitTests/io/BootstrapTests.swift @@ -33,6 +33,6 @@ class BootstrapTests: XCBaseTestCase { hostResolver: resolver, shutdownCallback: shutdownCallback) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } } diff --git a/Test/AwsCommonRuntimeKitTests/io/EventLoopGroupTests.swift b/Test/AwsCommonRuntimeKitTests/io/EventLoopGroupTests.swift index c7d4cbcbd..55ba8905e 100644 --- a/Test/AwsCommonRuntimeKitTests/io/EventLoopGroupTests.swift +++ b/Test/AwsCommonRuntimeKitTests/io/EventLoopGroupTests.swift @@ -11,7 +11,7 @@ class EventLoopGroupTests: XCBaseTestCase { _ = try EventLoopGroup() { shutdownWasCalled.fulfill() } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testCanCreateGroupWithThreads() throws { diff --git a/Test/AwsCommonRuntimeKitTests/io/HostResolverTests.swift b/Test/AwsCommonRuntimeKitTests/io/HostResolverTests.swift index 9ba7482dd..fe97bd88d 100644 --- a/Test/AwsCommonRuntimeKitTests/io/HostResolverTests.swift +++ b/Test/AwsCommonRuntimeKitTests/io/HostResolverTests.swift @@ -4,13 +4,13 @@ import XCTest @testable import AwsCommonRuntimeKit class HostResolverTests: XCBaseTestCase { - + func testCanResolveHosts() async throws { let elg = try EventLoopGroup() let resolver = try HostResolver(eventLoopGroup: elg, maxHosts: 8, maxTTL: 5) - + let addresses = try await resolver.resolveAddress(args: HostResolverArguments(hostName: "localhost")) XCTAssertNoThrow(addresses) XCTAssertNotNil(addresses.count) @@ -61,6 +61,6 @@ class HostResolverTests: XCBaseTestCase { maxTTL: 5, shutdownCallback: shutdownCallback) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } } diff --git a/Test/AwsCommonRuntimeKitTests/io/RetryerTests.swift b/Test/AwsCommonRuntimeKitTests/io/RetryerTests.swift index 13218b0a9..351143fe1 100644 --- a/Test/AwsCommonRuntimeKitTests/io/RetryerTests.swift +++ b/Test/AwsCommonRuntimeKitTests/io/RetryerTests.swift @@ -4,12 +4,12 @@ import XCTest @testable import AwsCommonRuntimeKit class RetryerTests: XCBaseTestCase { let expectation = XCTestExpectation(description: "Credentials callback was called") - + func testCreateAWSRetryer() throws { let elg = try EventLoopGroup(threadCount: 1) _ = try RetryStrategy(eventLoopGroup: elg) } - + func testAcquireToken() async throws { let elg = try EventLoopGroup(threadCount: 1) let retryer = try RetryStrategy(eventLoopGroup: elg) @@ -37,7 +37,7 @@ class RetryerTests: XCBaseTestCase { XCTAssertNotNil(token) _ = try await retryer.scheduleRetry(token: token, errorType: RetryError.serverError) } - wait(for: [shutdownWasCalled], timeout: 15) + await awaitExpectation([shutdownWasCalled]) } func testGenerateRandom() async throws { @@ -59,6 +59,6 @@ class RetryerTests: XCBaseTestCase { XCTAssertNotNil(token) _ = try await retryer.scheduleRetry(token: token, errorType: RetryError.serverError) } - wait(for: [generateRandomWasCalled, shutdownWasCalled], timeout: 15) + await awaitExpectation([generateRandomWasCalled, shutdownWasCalled]) } } diff --git a/aws-common-runtime/aws-c-cal b/aws-common-runtime/aws-c-cal index 656762aef..fbbe2612a 160000 --- a/aws-common-runtime/aws-c-cal +++ b/aws-common-runtime/aws-c-cal @@ -1 +1 @@ -Subproject commit 656762aefbee2bc8f509cb23cd107abff20a72bb +Subproject commit fbbe2612a3385d1ded02a52d20ad7fd2da4501f4 diff --git a/aws-common-runtime/aws-c-common b/aws-common-runtime/aws-c-common index 63187b976..3334fee31 160000 --- a/aws-common-runtime/aws-c-common +++ b/aws-common-runtime/aws-c-common @@ -1 +1 @@ -Subproject commit 63187b976a482309e23296c5f967fc19c4131746 +Subproject commit 3334fee3131f53afcf9fd2cfe44b20964b9b9587 diff --git a/aws-common-runtime/s2n b/aws-common-runtime/s2n index 493b77167..2e79e7efe 160000 --- a/aws-common-runtime/s2n +++ b/aws-common-runtime/s2n @@ -1 +1 @@ -Subproject commit 493b77167dc367c394de23cfe78a029298e2a254 +Subproject commit 2e79e7efeb26f06eb59a1d4f3444ea63fc3e20c3