Remove dependency on XCTest (#516)

Remove dependency on XCTest

### Motivation:

As 6.1 does not include XCTest anymore, finish the migration to Swift
testing

### Modifications:

Replace XCTest by Swift Testing in two files

### Result:

`swift test` works on 6.1.2

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Sébastien Stormacq
2025-06-28 13:09:44 +02:00
committed by GitHub
parent 41c5edaf04
commit 935ea0fe91
7 changed files with 261 additions and 111 deletions
+2 -1
View File
@@ -11,4 +11,5 @@ Package.resolved
.serverless
.vscode
Makefile
.devcontainer
.devcontainer
.amazonq
@@ -15,148 +15,184 @@
import NIOCore
import NIOEmbedded
import NIOHTTP1
import XCTest
import Testing
@testable import AWSLambdaRuntime
final class ControlPlaneRequestEncoderTests: XCTestCase {
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
struct ControlPlaneRequestEncoderTests {
let host = "192.168.0.1"
var client: EmbeddedChannel!
var server: EmbeddedChannel!
override func setUp() {
self.client = EmbeddedChannel(handler: ControlPlaneRequestEncoderHandler(host: self.host))
self.server = EmbeddedChannel(handlers: [
func createChannels() -> (client: EmbeddedChannel, server: EmbeddedChannel) {
let client = EmbeddedChannel(handler: ControlPlaneRequestEncoderHandler(host: self.host))
let server = EmbeddedChannel(handlers: [
ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .dropBytes)),
NIOHTTPServerRequestAggregator(maxContentLength: 1024 * 1024),
])
return (client, server)
}
override func tearDown() {
XCTAssertNoThrow(try self.client.finish(acceptAlreadyClosed: false))
XCTAssertNoThrow(try self.server.finish(acceptAlreadyClosed: false))
self.client = nil
self.server = nil
@Test
func testNextRequest() throws {
let (client, server) = createChannels()
defer {
_ = try? client.finish(acceptAlreadyClosed: false)
_ = try? server.finish(acceptAlreadyClosed: false)
}
let request = try sendRequest(.next, client: client, server: server)
#expect(request?.head.isKeepAlive == true)
#expect(request?.head.method == .GET)
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/next")
#expect(request?.head.version == .http1_1)
#expect(request?.head.headers["host"] == [self.host])
#expect(request?.head.headers["user-agent"] == [.userAgent])
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
}
func testNextRequest() {
var request: NIOHTTPServerRequestFull?
XCTAssertNoThrow(request = try self.sendRequest(.next))
@Test
func testPostInvocationSuccessWithoutBody() throws {
let (client, server) = createChannels()
defer {
_ = try? client.finish(acceptAlreadyClosed: false)
_ = try? server.finish(acceptAlreadyClosed: false)
}
XCTAssertEqual(request?.head.isKeepAlive, true)
XCTAssertEqual(request?.head.method, .GET)
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/next")
XCTAssertEqual(request?.head.version, .http1_1)
XCTAssertEqual(request?.head.headers["host"], [self.host])
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
}
func testPostInvocationSuccessWithoutBody() {
let requestID = UUID().uuidString
var request: NIOHTTPServerRequestFull?
XCTAssertNoThrow(request = try self.sendRequest(.invocationResponse(requestID, nil)))
let request = try sendRequest(.invocationResponse(requestID, nil), client: client, server: server)
XCTAssertEqual(request?.head.isKeepAlive, true)
XCTAssertEqual(request?.head.method, .POST)
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/response")
XCTAssertEqual(request?.head.version, .http1_1)
XCTAssertEqual(request?.head.headers["host"], [self.host])
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
XCTAssertEqual(request?.head.headers["content-length"], ["0"])
#expect(request?.head.isKeepAlive == true)
#expect(request?.head.method == .POST)
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/response")
#expect(request?.head.version == .http1_1)
#expect(request?.head.headers["host"] == [self.host])
#expect(request?.head.headers["user-agent"] == [.userAgent])
#expect(request?.head.headers["content-length"] == ["0"])
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
}
func testPostInvocationSuccessWithBody() {
@Test
func testPostInvocationSuccessWithBody() throws {
let (client, server) = createChannels()
defer {
_ = try? client.finish(acceptAlreadyClosed: false)
_ = try? server.finish(acceptAlreadyClosed: false)
}
let requestID = UUID().uuidString
let payload = ByteBuffer(string: "hello swift lambda!")
var request: NIOHTTPServerRequestFull?
XCTAssertNoThrow(request = try self.sendRequest(.invocationResponse(requestID, payload)))
let request = try sendRequest(.invocationResponse(requestID, payload), client: client, server: server)
XCTAssertEqual(request?.head.isKeepAlive, true)
XCTAssertEqual(request?.head.method, .POST)
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/response")
XCTAssertEqual(request?.head.version, .http1_1)
XCTAssertEqual(request?.head.headers["host"], [self.host])
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
XCTAssertEqual(request?.head.headers["content-length"], ["\(payload.readableBytes)"])
XCTAssertEqual(request?.body, payload)
#expect(request?.head.isKeepAlive == true)
#expect(request?.head.method == .POST)
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/response")
#expect(request?.head.version == .http1_1)
#expect(request?.head.headers["host"] == [self.host])
#expect(request?.head.headers["user-agent"] == [.userAgent])
#expect(request?.head.headers["content-length"] == ["\(payload.readableBytes)"])
#expect(request?.body == payload)
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
}
func testPostInvocationErrorWithBody() {
@Test
func testPostInvocationErrorWithBody() throws {
let (client, server) = createChannels()
defer {
_ = try? client.finish(acceptAlreadyClosed: false)
_ = try? server.finish(acceptAlreadyClosed: false)
}
let requestID = UUID().uuidString
let error = ErrorResponse(errorType: "SomeError", errorMessage: "An error happened")
var request: NIOHTTPServerRequestFull?
XCTAssertNoThrow(request = try self.sendRequest(.invocationError(requestID, error)))
let request = try sendRequest(.invocationError(requestID, error), client: client, server: server)
XCTAssertEqual(request?.head.isKeepAlive, true)
XCTAssertEqual(request?.head.method, .POST)
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/error")
XCTAssertEqual(request?.head.version, .http1_1)
XCTAssertEqual(request?.head.headers["host"], [self.host])
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
XCTAssertEqual(request?.head.headers["lambda-runtime-function-error-type"], ["Unhandled"])
#expect(request?.head.isKeepAlive == true)
#expect(request?.head.method == .POST)
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/error")
#expect(request?.head.version == .http1_1)
#expect(request?.head.headers["host"] == [self.host])
#expect(request?.head.headers["user-agent"] == [.userAgent])
#expect(request?.head.headers["lambda-runtime-function-error-type"] == ["Unhandled"])
let expectedBody = #"{"errorType":"SomeError","errorMessage":"An error happened"}"#
XCTAssertEqual(request?.head.headers["content-length"], ["\(expectedBody.utf8.count)"])
XCTAssertEqual(
try request?.body?.getString(at: 0, length: XCTUnwrap(request?.body?.readableBytes)),
expectedBody
)
#expect(request?.head.headers["content-length"] == ["\(expectedBody.utf8.count)"])
let bodyString = request?.body?.getString(at: 0, length: request?.body?.readableBytes ?? 0)
#expect(bodyString == expectedBody)
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
}
func testPostStartupError() {
@Test
func testPostStartupError() throws {
let (client, server) = createChannels()
defer {
_ = try? client.finish(acceptAlreadyClosed: false)
_ = try? server.finish(acceptAlreadyClosed: false)
}
let error = ErrorResponse(errorType: "StartupError", errorMessage: "Urgh! Startup failed. 😨")
var request: NIOHTTPServerRequestFull?
XCTAssertNoThrow(request = try self.sendRequest(.initializationError(error)))
let request = try sendRequest(.initializationError(error), client: client, server: server)
XCTAssertEqual(request?.head.isKeepAlive, true)
XCTAssertEqual(request?.head.method, .POST)
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/init/error")
XCTAssertEqual(request?.head.version, .http1_1)
XCTAssertEqual(request?.head.headers["host"], [self.host])
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
XCTAssertEqual(request?.head.headers["lambda-runtime-function-error-type"], ["Unhandled"])
#expect(request?.head.isKeepAlive == true)
#expect(request?.head.method == .POST)
#expect(request?.head.uri == "/2018-06-01/runtime/init/error")
#expect(request?.head.version == .http1_1)
#expect(request?.head.headers["host"] == [self.host])
#expect(request?.head.headers["user-agent"] == [.userAgent])
#expect(request?.head.headers["lambda-runtime-function-error-type"] == ["Unhandled"])
let expectedBody = #"{"errorType":"StartupError","errorMessage":"Urgh! Startup failed. 😨"}"#
XCTAssertEqual(request?.head.headers["content-length"], ["\(expectedBody.utf8.count)"])
XCTAssertEqual(
try request?.body?.getString(at: 0, length: XCTUnwrap(request?.body?.readableBytes)),
expectedBody
)
#expect(request?.head.headers["content-length"] == ["\(expectedBody.utf8.count)"])
let bodyString = request?.body?.getString(at: 0, length: request?.body?.readableBytes ?? 0)
#expect(bodyString == expectedBody)
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
}
func testMultipleNextAndResponseSuccessRequests() {
@Test
func testMultipleNextAndResponseSuccessRequests() throws {
let (client, server) = createChannels()
defer {
_ = try? client.finish(acceptAlreadyClosed: false)
_ = try? server.finish(acceptAlreadyClosed: false)
}
for _ in 0..<1000 {
var nextRequest: NIOHTTPServerRequestFull?
XCTAssertNoThrow(nextRequest = try self.sendRequest(.next))
XCTAssertEqual(nextRequest?.head.method, .GET)
XCTAssertEqual(nextRequest?.head.uri, "/2018-06-01/runtime/invocation/next")
let nextRequest = try sendRequest(.next, client: client, server: server)
#expect(nextRequest?.head.method == .GET)
#expect(nextRequest?.head.uri == "/2018-06-01/runtime/invocation/next")
let requestID = UUID().uuidString
let payload = ByteBuffer(string: "hello swift lambda!")
var successRequest: NIOHTTPServerRequestFull?
XCTAssertNoThrow(successRequest = try self.sendRequest(.invocationResponse(requestID, payload)))
XCTAssertEqual(successRequest?.head.method, .POST)
XCTAssertEqual(successRequest?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/response")
let successRequest = try sendRequest(
.invocationResponse(requestID, payload),
client: client,
server: server
)
#expect(successRequest?.head.method == .POST)
#expect(successRequest?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/response")
}
}
func sendRequest(_ request: ControlPlaneRequest) throws -> NIOHTTPServerRequestFull? {
try self.client.writeOutbound(request)
while let part = try self.client.readOutbound(as: ByteBuffer.self) {
XCTAssertNoThrow(try self.server.writeInbound(part))
func sendRequest(
_ request: ControlPlaneRequest,
client: EmbeddedChannel,
server: EmbeddedChannel
) throws -> NIOHTTPServerRequestFull? {
try client.writeOutbound(request)
while let part = try client.readOutbound(as: ByteBuffer.self) {
try server.writeInbound(part)
}
return try self.server.readInbound(as: NIOHTTPServerRequestFull.self)
return try server.readInbound(as: NIOHTTPServerRequestFull.self)
}
}
@@ -26,7 +26,8 @@ struct LambdaRuntimeClientTests {
let logger = {
var logger = Logger(label: "NewLambdaClientRuntimeTest")
logger.logLevel = .trace
// Uncomment the line below to enable trace-level logging for debugging purposes.
// logger.logLevel = .trace
return logger
}()
@@ -100,16 +100,16 @@ final class MockLambdaServer<Behavior: LambdaServerBehavior> {
guard let localAddress = channel.localAddress else {
throw ServerError.cantBind
}
self.logger.info("\(self) started and listening on \(localAddress)")
self.logger.trace("\(self) started and listening on \(localAddress)")
return localAddress.port!
}
fileprivate func stop() async throws {
self.logger.info("stopping \(self)")
self.logger.trace("stopping \(self)")
let channel = self.channel!
try? await channel.close().get()
self.shutdown = true
self.logger.info("\(self) stopped")
self.logger.trace("\(self) stopped")
}
}
@@ -150,7 +150,7 @@ final class HTTPHandler: ChannelInboundHandler {
}
func processRequest(context: ChannelHandlerContext, request: (head: HTTPRequestHead, body: ByteBuffer?)) {
self.logger.info("\(self) processing \(request.head.uri)")
self.logger.trace("\(self) processing \(request.head.uri)")
let requestBody = request.body.flatMap { (buffer: ByteBuffer) -> String? in
var buffer = buffer
+11 -10
View File
@@ -12,29 +12,30 @@
//
//===----------------------------------------------------------------------===//
import XCTest
import Testing
@testable import AWSLambdaRuntime
class UtilsTest: XCTestCase {
struct UtilsTest {
@Test
func testGenerateXRayTraceID() {
// the time and identifier should be in hexadecimal digits
let invalidCharacters = CharacterSet(charactersIn: "abcdef0123456789").inverted
let allowedCharacters = "0123456789abcdef"
let numTests = 1000
var values = Set<String>()
for _ in 0..<numTests {
// check the format, see https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html#xray-api-traceids)
let traceId = AmazonHeaders.generateXRayTraceID()
let segments = traceId.split(separator: "-")
XCTAssertEqual(3, segments.count)
XCTAssertEqual("1", segments[0])
XCTAssertEqual(8, segments[1].count)
XCTAssertNil(segments[1].rangeOfCharacter(from: invalidCharacters))
XCTAssertEqual(24, segments[2].count)
XCTAssertNil(segments[2].rangeOfCharacter(from: invalidCharacters))
#expect(segments.count == 3)
#expect(segments[0] == "1")
#expect(segments[1].count == 8)
#expect(segments[2].count == 24)
#expect(segments[1].allSatisfy { allowedCharacters.contains($0) })
#expect(segments[2].allSatisfy { allowedCharacters.contains($0) })
values.insert(traceId)
}
// check that the generated values are different
XCTAssertEqual(values.count, numTests)
#expect(values.count == numTests)
}
}
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftAWSLambdaRuntime open source project
##
## Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##
##===----------------------------------------------------------------------===##
##
## This source file is part of the Swift.org open source project
##
## Copyright (c) 2020 Apple Inc. and the Swift project authors
## Licensed under Apache License v2.0 with Runtime Library Exception
##
## See https://swift.org/LICENSE.txt for license information
## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
##
##===----------------------------------------------------------------------===##
set +x
set -euo pipefail
SWIFT_IMAGE=swift:latest
CHECK_FORMAT_SCRIPT=https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/check-swift-format.sh
echo "Downloading check-swift-format.sh"
curl -s ${CHECK_FORMAT_SCRIPT} > format.sh && chmod u+x format.sh
echo "Running check-swift-format.sh"
/usr/local/bin/container run --rm -v "$(pwd):/workspace" -w /workspace ${SWIFT_IMAGE} bash -clx "./format.sh"
echo "Cleaning up"
rm format.sh
YAML_LINT=https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/configs/yamllint.yml
YAML_IMAGE=ubuntu:latest
echo "Downloading yamllint.yml"
curl -s ${YAML_LINT} > yamllint.yml
echo "Running yamllint"
/usr/local/bin/container run --rm -v "$(pwd):/workspace" -w /workspace ${YAML_IMAGE} bash -clx "apt-get -qq update && apt-get -qq -y install yamllint && yamllint --strict --config-file /workspace/yamllint.yml .github"
echo "Cleaning up"
rm yamllint.yml
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
##===----------------------------------------------------------------------===##
##
## This source file is part of the SwiftAWSLambdaRuntime open source project
##
## Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
## Licensed under Apache License v2.0
##
## See LICENSE.txt for license information
## See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
##
## SPDX-License-Identifier: Apache-2.0
##
##===----------------------------------------------------------------------===##
##===----------------------------------------------------------------------===##
##
## This source file is part of the Swift.org open source project
##
## Copyright (c) 2020 Apple Inc. and the Swift project authors
## Licensed under Apache License v2.0 with Runtime Library Exception
##
## See https://swift.org/LICENSE.txt for license information
## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
##
##===----------------------------------------------------------------------===##
set -euo pipefail
log() { printf -- "** %s\n" "$*" >&2; }
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
fatal() { error "$@"; exit 1; }
if [[ -f .swiftformatignore ]]; then
log "Found swiftformatignore file..."
log "Running swift format format..."
tr '\n' '\0' < .swiftformatignore| xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place
log "Running swift format lint..."
tr '\n' '\0' < .swiftformatignore | xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift format lint --strict --parallel
else
log "Running swift format format..."
git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place
log "Running swift format lint..."
git ls-files -z '*.swift' | xargs -0 swift format lint --strict --parallel
fi
log "Checking for modified files..."
GIT_PAGER='' git diff --exit-code '*.swift'
log "✅ Found no formatting issues."