From eb4e376c315c254fe974d8337e2a9cbbb1cedbad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= <sebastien.stormacq@gmail.com>
Date: Fri, 7 Mar 2025 19:42:18 +0100
Subject: [PATCH 1/3] add a unit test for the LambdaHTTPServer Pool

---
 Package@swift-6.0.swift                       |   7 +-
 .../AWSLambdaRuntime/Lambda+LocalServer.swift |   4 +-
 Tests/AWSLambdaRuntimeTests/PoolTests.swift   | 135 ++++++++++++++++++
 3 files changed, 143 insertions(+), 3 deletions(-)
 create mode 100644 Tests/AWSLambdaRuntimeTests/PoolTests.swift

diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift
index fb4e82d0..4ee6690c 100644
--- a/Package@swift-6.0.swift
+++ b/Package@swift-6.0.swift
@@ -56,7 +56,12 @@ let package = Package(
                 .byName(name: "AWSLambdaRuntime"),
                 .product(name: "NIOTestUtils", package: "swift-nio"),
                 .product(name: "NIOFoundationCompat", package: "swift-nio"),
-            ]
+            ],
+            swiftSettings: [
+                .define("FoundationJSONSupport"),
+                .define("ServiceLifecycleSupport"),
+                .define("LocalServerSupport"),
+            ]            
         ),
         // for perf testing
         .executableTarget(
diff --git a/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift b/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
index 4d85f7b2..c340efd5 100644
--- a/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
+++ b/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
@@ -75,7 +75,7 @@ extension Lambda {
 /// 1. POST /invoke - the client posts the event to the lambda function
 ///
 /// 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.
-private struct LambdaHTTPServer {
+internal struct LambdaHTTPServer {
     private let invocationEndpoint: String
 
     private let invocationPool = Pool<LocalServerInvocation>()
@@ -425,7 +425,7 @@ private struct LambdaHTTPServer {
     /// A shared data structure to store the current invocation or response requests and the continuation objects.
     /// This data structure is shared between instances of the HTTPHandler
     /// (one instance to serve requests from the Lambda function and one instance to serve requests from the client invoking the lambda function).
-    private final class Pool<T>: AsyncSequence, AsyncIteratorProtocol, Sendable where T: Sendable {
+    internal final class Pool<T>: AsyncSequence, AsyncIteratorProtocol, Sendable where T: Sendable {
         typealias Element = T
 
         enum State: ~Copyable {
diff --git a/Tests/AWSLambdaRuntimeTests/PoolTests.swift b/Tests/AWSLambdaRuntimeTests/PoolTests.swift
new file mode 100644
index 00000000..e720e0e2
--- /dev/null
+++ b/Tests/AWSLambdaRuntimeTests/PoolTests.swift
@@ -0,0 +1,135 @@
+import Testing
+@testable import AWSLambdaRuntime
+
+struct PoolTests {
+    
+    @Test
+    func testBasicPushAndIteration() async throws {
+        let pool = LambdaHTTPServer.Pool<String>()
+        
+        // Push values
+        await pool.push("first")
+        await pool.push("second")
+        
+        // Iterate and verify order
+        var values = [String]()
+        for try await value in pool {
+            values.append(value)
+            if values.count == 2 { break }
+        }
+        
+        #expect(values == ["first", "second"])
+    }
+    
+    @Test
+    func testCancellation() async throws {
+        let pool = LambdaHTTPServer.Pool<String>()
+        
+        // Create a task that will be cancelled
+        let task = Task {
+            for try await _ in pool {
+                Issue.record("Should not receive any values after cancellation")
+            }
+        }
+        
+        // Cancel the task immediately
+        task.cancel()
+        
+        // This should complete without receiving any values
+        try await task.value
+    }
+    
+    @Test
+    func testConcurrentPushAndIteration() async throws {
+        let pool = LambdaHTTPServer.Pool<Int>()
+        let iterations = 1000
+        var receivedValues = Set<Int>()
+        
+        // Start consumer task first
+        let consumer = Task {
+            var count = 0
+            for try await value in pool {
+                receivedValues.insert(value)
+                count += 1
+                if count >= iterations { break }
+            }
+        }
+        
+        // Create multiple producer tasks
+        try await withThrowingTaskGroup(of: Void.self) { group in
+            for i in 0..<iterations {
+                group.addTask {
+                    await pool.push(i)
+                }
+            }
+            try await group.waitForAll()
+        }
+        
+        // Wait for consumer to complete
+        try await consumer.value
+        
+        // Verify all values were received exactly once
+        #expect(receivedValues.count == iterations)
+        #expect(Set(0..<iterations) == receivedValues)
+    }
+    
+    @Test
+    func testPushToWaitingConsumer() async throws {
+        let pool = LambdaHTTPServer.Pool<String>()
+        let expectedValue = "test value"
+        
+        // Start a consumer that will wait for a value
+        let consumer = Task {
+            for try await value in pool {
+                #expect(value == expectedValue)
+                break
+            }
+        }
+        
+        // Give consumer time to start waiting
+        try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
+        
+        // Push a value
+        await pool.push(expectedValue)
+        
+        // Wait for consumer to complete
+        try await consumer.value
+    }
+    
+    @Test
+    func testStressTest() async throws {
+        let pool = LambdaHTTPServer.Pool<Int>()
+        let producerCount = 10
+        let messagesPerProducer = 1000
+        var receivedValues = [Int]()
+        
+        // Start consumer
+        let consumer = Task {
+            var count = 0
+            for try await value in pool {
+                receivedValues.append(value)
+                count += 1
+                if count >= producerCount * messagesPerProducer { break }
+            }
+        }
+        
+        // Create multiple producers
+        try await withThrowingTaskGroup(of: Void.self) { group in
+            for p in 0..<producerCount {
+                group.addTask {
+                    for i in 0..<messagesPerProducer {
+                        await pool.push(p * messagesPerProducer + i)
+                    }
+                }
+            }
+            try await group.waitForAll()
+        }
+        
+        // Wait for consumer to complete
+        try await consumer.value
+        
+        // Verify we received all values
+        #expect(receivedValues.count == producerCount * messagesPerProducer)
+        #expect(Set(receivedValues).count == producerCount * messagesPerProducer)
+    }
+}
\ No newline at end of file

From 593adcc5bc6f8945fb555a6528eddc309f475bc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= <sebastien.stormacq@gmail.com>
Date: Fri, 7 Mar 2025 19:44:16 +0100
Subject: [PATCH 2/3] swift format

---
 Package@swift-6.0.swift                     |  2 +-
 Tests/AWSLambdaRuntimeTests/PoolTests.swift | 51 +++++++++++----------
 2 files changed, 27 insertions(+), 26 deletions(-)

diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift
index 4ee6690c..a10bb0b8 100644
--- a/Package@swift-6.0.swift
+++ b/Package@swift-6.0.swift
@@ -61,7 +61,7 @@ let package = Package(
                 .define("FoundationJSONSupport"),
                 .define("ServiceLifecycleSupport"),
                 .define("LocalServerSupport"),
-            ]            
+            ]
         ),
         // for perf testing
         .executableTarget(
diff --git a/Tests/AWSLambdaRuntimeTests/PoolTests.swift b/Tests/AWSLambdaRuntimeTests/PoolTests.swift
index e720e0e2..a9309a21 100644
--- a/Tests/AWSLambdaRuntimeTests/PoolTests.swift
+++ b/Tests/AWSLambdaRuntimeTests/PoolTests.swift
@@ -1,50 +1,51 @@
 import Testing
+
 @testable import AWSLambdaRuntime
 
 struct PoolTests {
-    
+
     @Test
     func testBasicPushAndIteration() async throws {
         let pool = LambdaHTTPServer.Pool<String>()
-        
+
         // Push values
         await pool.push("first")
         await pool.push("second")
-        
+
         // Iterate and verify order
         var values = [String]()
         for try await value in pool {
             values.append(value)
             if values.count == 2 { break }
         }
-        
+
         #expect(values == ["first", "second"])
     }
-    
+
     @Test
     func testCancellation() async throws {
         let pool = LambdaHTTPServer.Pool<String>()
-        
+
         // Create a task that will be cancelled
         let task = Task {
             for try await _ in pool {
                 Issue.record("Should not receive any values after cancellation")
             }
         }
-        
+
         // Cancel the task immediately
         task.cancel()
-        
+
         // This should complete without receiving any values
         try await task.value
     }
-    
+
     @Test
     func testConcurrentPushAndIteration() async throws {
         let pool = LambdaHTTPServer.Pool<Int>()
         let iterations = 1000
         var receivedValues = Set<Int>()
-        
+
         // Start consumer task first
         let consumer = Task {
             var count = 0
@@ -54,7 +55,7 @@ struct PoolTests {
                 if count >= iterations { break }
             }
         }
-        
+
         // Create multiple producer tasks
         try await withThrowingTaskGroup(of: Void.self) { group in
             for i in 0..<iterations {
@@ -64,20 +65,20 @@ struct PoolTests {
             }
             try await group.waitForAll()
         }
-        
+
         // Wait for consumer to complete
         try await consumer.value
-        
+
         // Verify all values were received exactly once
         #expect(receivedValues.count == iterations)
         #expect(Set(0..<iterations) == receivedValues)
     }
-    
+
     @Test
     func testPushToWaitingConsumer() async throws {
         let pool = LambdaHTTPServer.Pool<String>()
         let expectedValue = "test value"
-        
+
         // Start a consumer that will wait for a value
         let consumer = Task {
             for try await value in pool {
@@ -85,24 +86,24 @@ struct PoolTests {
                 break
             }
         }
-        
+
         // Give consumer time to start waiting
-        try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds
-        
+        try await Task.sleep(nanoseconds: 100_000_000)  // 0.1 seconds
+
         // Push a value
         await pool.push(expectedValue)
-        
+
         // Wait for consumer to complete
         try await consumer.value
     }
-    
+
     @Test
     func testStressTest() async throws {
         let pool = LambdaHTTPServer.Pool<Int>()
         let producerCount = 10
         let messagesPerProducer = 1000
         var receivedValues = [Int]()
-        
+
         // Start consumer
         let consumer = Task {
             var count = 0
@@ -112,7 +113,7 @@ struct PoolTests {
                 if count >= producerCount * messagesPerProducer { break }
             }
         }
-        
+
         // Create multiple producers
         try await withThrowingTaskGroup(of: Void.self) { group in
             for p in 0..<producerCount {
@@ -124,12 +125,12 @@ struct PoolTests {
             }
             try await group.waitForAll()
         }
-        
+
         // Wait for consumer to complete
         try await consumer.value
-        
+
         // Verify we received all values
         #expect(receivedValues.count == producerCount * messagesPerProducer)
         #expect(Set(receivedValues).count == producerCount * messagesPerProducer)
     }
-}
\ No newline at end of file
+}

From ce1f959290be68f845c20a6352708ab2a2505b4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Stormacq?= <sebastien.stormacq@gmail.com>
Date: Fri, 7 Mar 2025 19:46:08 +0100
Subject: [PATCH 3/3] license header

---
 Sources/AWSLambdaRuntime/Lambda+LocalServer.swift |  2 +-
 Tests/AWSLambdaRuntimeTests/PoolTests.swift       | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift b/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
index c340efd5..d959a776 100644
--- a/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
+++ b/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift
@@ -2,7 +2,7 @@
 //
 // This source file is part of the SwiftAWSLambdaRuntime open source project
 //
-// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
+// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
 // Licensed under Apache License v2.0
 //
 // See LICENSE.txt for license information
diff --git a/Tests/AWSLambdaRuntimeTests/PoolTests.swift b/Tests/AWSLambdaRuntimeTests/PoolTests.swift
index a9309a21..a5def86d 100644
--- a/Tests/AWSLambdaRuntimeTests/PoolTests.swift
+++ b/Tests/AWSLambdaRuntimeTests/PoolTests.swift
@@ -1,3 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
 import Testing
 
 @testable import AWSLambdaRuntime