diff --git a/.gitignore b/.gitignore index f7a26a78..b3b30ec1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ Package.resolved .serverless .vscode Makefile -.devcontainer \ No newline at end of file +.devcontainer +.amazonq \ No newline at end of file diff --git a/Tests/AWSLambdaRuntimeTests/ControlPlaneRequestEncoderTests.swift b/Tests/AWSLambdaRuntimeTests/ControlPlaneRequestEncoderTests.swift index 840a17c6..b1c22a58 100644 --- a/Tests/AWSLambdaRuntimeTests/ControlPlaneRequestEncoderTests.swift +++ b/Tests/AWSLambdaRuntimeTests/ControlPlaneRequestEncoderTests.swift @@ -15,148 +15,184 @@ import NIOCore import NIOEmbedded import NIOHTTP1 -import XCTest +import Testing @testable import AWSLambdaRuntime -final class ControlPlaneRequestEncoderTests: XCTestCase { - let host = "192.168.0.1" +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif - var client: EmbeddedChannel! - var server: EmbeddedChannel! +struct ControlPlaneRequestEncoderTests { + let host = "192.168.0.1" - 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) + } - func testNextRequest() { - var request: NIOHTTPServerRequestFull? - XCTAssertNoThrow(request = try self.sendRequest(.next)) + let request = try sendRequest(.next, client: client, server: server) - 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]) + #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]) - XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self)) + #expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil) } - func testPostInvocationSuccessWithoutBody() { + @Test + func testPostInvocationSuccessWithoutBody() throws { + let (client, server) = createChannels() + defer { + _ = try? client.finish(acceptAlreadyClosed: false) + _ = try? server.finish(acceptAlreadyClosed: false) + } + let requestID = UUID().uuidString - var request: NIOHTTPServerRequestFull? - XCTAssertNoThrow(request = try self.sendRequest(.invocationResponse(requestID, nil))) - - 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"]) - - XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self)) + let request = try sendRequest(.invocationResponse(requestID, nil), client: client, server: server) + + #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"]) + + #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))) - - 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"]) + let request = try sendRequest(.invocationError(requestID, error), client: client, server: server) + + #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))) - - 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"]) + let request = try sendRequest(.initializationError(error), client: client, server: server) + + #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) } } diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift index 53afbaf6..33ebde3f 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift @@ -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 }() diff --git a/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift index 11f43ba4..bb1dce4c 100644 --- a/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift @@ -100,16 +100,16 @@ final class MockLambdaServer { 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 diff --git a/Tests/AWSLambdaRuntimeTests/UtilsTest.swift b/Tests/AWSLambdaRuntimeTests/UtilsTest.swift index 0b3b5917..15a5fd27 100644 --- a/Tests/AWSLambdaRuntimeTests/UtilsTest.swift +++ b/Tests/AWSLambdaRuntimeTests/UtilsTest.swift @@ -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() for _ in 0.. 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 + diff --git a/scripts/check-format.sh b/scripts/check-format.sh new file mode 100755 index 00000000..51fd80ac --- /dev/null +++ b/scripts/check-format.sh @@ -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."