Skip to content

Commit a616996

Browse files
authored
[test] add a unit test for the LambdaHTTPServer Pool (#500)
1 parent 935ea0f commit a616996

File tree

4 files changed

+161
-4
lines changed

4 files changed

+161
-4
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
with:
2424
linux_5_9_enabled: false
2525
linux_5_10_enabled: false
26-
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error"
26+
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error"
2727
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
2828

2929
integration-tests:

[email protected]

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ let package = Package(
5656
.byName(name: "AWSLambdaRuntime"),
5757
.product(name: "NIOTestUtils", package: "swift-nio"),
5858
.product(name: "NIOFoundationCompat", package: "swift-nio"),
59+
],
60+
swiftSettings: [
61+
.define("FoundationJSONSupport"),
62+
.define("ServiceLifecycleSupport"),
63+
.define("LocalServerSupport"),
5964
]
6065
),
6166
// for perf testing

Sources/AWSLambdaRuntime/Lambda+LocalServer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the SwiftAWSLambdaRuntime open source project
44
//
5-
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
5+
// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -76,7 +76,7 @@ extension Lambda {
7676
/// 1. POST /invoke - the client posts the event to the lambda function
7777
///
7878
/// This server passes the data received from /invoke POST request to the lambda function (GET /next) and then forwards the response back to the client.
79-
private struct LambdaHTTPServer {
79+
internal struct LambdaHTTPServer {
8080
private let invocationEndpoint: String
8181

8282
private let invocationPool = Pool<LocalServerInvocation>()
@@ -426,7 +426,7 @@ private struct LambdaHTTPServer {
426426
/// A shared data structure to store the current invocation or response requests and the continuation objects.
427427
/// This data structure is shared between instances of the HTTPHandler
428428
/// (one instance to serve requests from the Lambda function and one instance to serve requests from the client invoking the lambda function).
429-
private final class Pool<T>: AsyncSequence, AsyncIteratorProtocol, Sendable where T: Sendable {
429+
internal final class Pool<T>: AsyncSequence, AsyncIteratorProtocol, Sendable where T: Sendable {
430430
typealias Element = T
431431

432432
enum State: ~Copyable {
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Testing
16+
17+
@testable import AWSLambdaRuntime
18+
19+
struct PoolTests {
20+
21+
@Test
22+
func testBasicPushAndIteration() async throws {
23+
let pool = LambdaHTTPServer.Pool<String>()
24+
25+
// Push values
26+
await pool.push("first")
27+
await pool.push("second")
28+
29+
// Iterate and verify order
30+
var values = [String]()
31+
for try await value in pool {
32+
values.append(value)
33+
if values.count == 2 { break }
34+
}
35+
36+
#expect(values == ["first", "second"])
37+
}
38+
39+
@Test
40+
func testCancellation() async throws {
41+
let pool = LambdaHTTPServer.Pool<String>()
42+
43+
// Create a task that will be cancelled
44+
let task = Task {
45+
for try await _ in pool {
46+
Issue.record("Should not receive any values after cancellation")
47+
}
48+
}
49+
50+
// Cancel the task immediately
51+
task.cancel()
52+
53+
// This should complete without receiving any values
54+
try await task.value
55+
}
56+
57+
@Test
58+
func testConcurrentPushAndIteration() async throws {
59+
let pool = LambdaHTTPServer.Pool<Int>()
60+
let iterations = 1000
61+
62+
// Start consumer task first
63+
let consumer = Task { @Sendable in
64+
var receivedValues = Set<Int>()
65+
var count = 0
66+
for try await value in pool {
67+
receivedValues.insert(value)
68+
count += 1
69+
if count >= iterations { break }
70+
}
71+
return receivedValues
72+
}
73+
74+
// Create multiple producer tasks
75+
try await withThrowingTaskGroup(of: Void.self) { group in
76+
for i in 0..<iterations {
77+
group.addTask {
78+
await pool.push(i)
79+
}
80+
}
81+
try await group.waitForAll()
82+
}
83+
84+
// Wait for consumer to complete
85+
let receivedValues = try await consumer.value
86+
87+
// Verify all values were received exactly once
88+
#expect(receivedValues.count == iterations)
89+
#expect(Set(0..<iterations) == receivedValues)
90+
}
91+
92+
@Test
93+
func testPushToWaitingConsumer() async throws {
94+
let pool = LambdaHTTPServer.Pool<String>()
95+
let expectedValue = "test value"
96+
97+
// Start a consumer that will wait for a value
98+
let consumer = Task {
99+
for try await value in pool {
100+
#expect(value == expectedValue)
101+
break
102+
}
103+
}
104+
105+
// Give consumer time to start waiting
106+
try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
107+
108+
// Push a value
109+
await pool.push(expectedValue)
110+
111+
// Wait for consumer to complete
112+
try await consumer.value
113+
}
114+
115+
@Test
116+
func testStressTest() async throws {
117+
let pool = LambdaHTTPServer.Pool<Int>()
118+
let producerCount = 10
119+
let messagesPerProducer = 1000
120+
121+
// Start consumer
122+
let consumer = Task { @Sendable in
123+
var receivedValues = [Int]()
124+
var count = 0
125+
for try await value in pool {
126+
receivedValues.append(value)
127+
count += 1
128+
if count >= producerCount * messagesPerProducer { break }
129+
}
130+
return receivedValues
131+
}
132+
133+
// Create multiple producers
134+
try await withThrowingTaskGroup(of: Void.self) { group in
135+
for p in 0..<producerCount {
136+
group.addTask {
137+
for i in 0..<messagesPerProducer {
138+
await pool.push(p * messagesPerProducer + i)
139+
}
140+
}
141+
}
142+
try await group.waitForAll()
143+
}
144+
145+
// Wait for consumer to complete
146+
let receivedValues = try await consumer.value
147+
148+
// Verify we received all values
149+
#expect(receivedValues.count == producerCount * messagesPerProducer)
150+
#expect(Set(receivedValues).count == producerCount * messagesPerProducer)
151+
}
152+
}

0 commit comments

Comments
 (0)