Skip to content

Remove dependency on XCTest #516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ Package.resolved
.serverless
.vscode
Makefile
.devcontainer
.devcontainer
.amazonq
230 changes: 133 additions & 97 deletions Tests/AWSLambdaRuntimeTests/ControlPlaneRequestEncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
3 changes: 2 additions & 1 deletion Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}()

Expand Down
8 changes: 4 additions & 4 deletions Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down Expand Up @@ -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
Expand Down
21 changes: 11 additions & 10 deletions Tests/AWSLambdaRuntimeTests/UtilsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Loading