From 824551ebfdead96a807c8c36cd11efb4956e35f1 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Fri, 1 May 2026 16:38:28 -0700 Subject: [PATCH 1/6] Make unit/integ tests more reliable --- .../ApiGatewayStreamingTests.cs | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs index 36e4d5da6..964aebcad 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs @@ -140,14 +140,22 @@ public async Task OnCompletedCallback_IsExecuted() Output.WriteLine($"Body: {body}"); Assert.Contains("OnCompleted callback registered", body); - var verifyResponse = await httpClient.GetWithRetryAsync($"{apiUrl}oncompleted-verify"); + // The OnCompleted callback runs AFTER the response is sent, so we need to + // poll the verify endpoint until the callback has executed. Additionally, + // Lambda may route the verify request to a different execution environment, + // so we retry to eventually hit the same instance that ran the callback. + var verifyResponse = await httpClient.GetWithRetryAsync( + $"{apiUrl}oncompleted-verify", + HttpStatusCode.OK, + async resp => + { + var content = await resp.Content.ReadAsStringAsync(); + Output.WriteLine($"Verify body: {content}"); + var doc = JsonDocument.Parse(content); + return doc.RootElement.GetProperty("onCompletedExecuted").GetBoolean(); + }, + maxRetries: 10, delaySeconds: 3); Assert.Equal(HttpStatusCode.OK, verifyResponse.StatusCode); - var verifyBody = await verifyResponse.Content.ReadAsStringAsync(); - Output.WriteLine($"Verify body: {verifyBody}"); - - var doc = JsonDocument.Parse(verifyBody); - Assert.True(doc.RootElement.GetProperty("onCompletedExecuted").GetBoolean(), - "OnCompleted callback should have been executed"); } [Fact] @@ -198,8 +206,9 @@ public async Task PostWithBody_EchoesRequestBody() var apiUrl = await _fixture.GetApiUrlAsync(); using var httpClient = new HttpClient(); - var content = new StringContent("Hello from integration test", Encoding.UTF8, "text/plain"); - var response = await httpClient.PostAsync($"{apiUrl}echo-body", content); + var response = await httpClient.PostWithRetryAsync( + $"{apiUrl}echo-body", + new StringContent("Hello from integration test", Encoding.UTF8, "text/plain")); Output.WriteLine($"Status: {response.StatusCode}"); var body = await response.Content.ReadAsStringAsync(); @@ -401,7 +410,7 @@ await s3Client.PutBucketAsync(new Amazon.S3.Model.PutBucketRequest private async Task WaitForEndpointAsync() { using var httpClient = new HttpClient(); - var maxRetries = 10; + var maxRetries = 20; for (var i = 0; i < maxRetries; i++) { try @@ -427,6 +436,15 @@ public static async Task GetWithRetryAsync( this HttpClient httpClient, string url, HttpStatusCode expectedCode = HttpStatusCode.OK, int maxRetries = 5, int delaySeconds = 5) + { + return await GetWithRetryAsync(httpClient, url, expectedCode, null, maxRetries, delaySeconds); + } + + public static async Task GetWithRetryAsync( + this HttpClient httpClient, string url, + HttpStatusCode expectedCode, + Func> contentValidator, + int maxRetries = 5, int delaySeconds = 5) { for (var i = 0; i < maxRetries; i++) { @@ -434,6 +452,33 @@ public static async Task GetWithRetryAsync( { var response = await httpClient.GetAsync(url); if (response.StatusCode == expectedCode) + { + if (contentValidator == null || await contentValidator(response)) + { + return response; + } + } + } + catch + { + // Ignore and retry + } + await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); + } + throw new Exception($"Failed to get expected response from {url} after {maxRetries} attempts"); + } + + public static async Task PostWithRetryAsync( + this HttpClient httpClient, string url, HttpContent content, + HttpStatusCode expectedCode = HttpStatusCode.OK, + int maxRetries = 5, int delaySeconds = 5) + { + for (var i = 0; i < maxRetries; i++) + { + try + { + var response = await httpClient.PostAsync(url, content); + if (response.StatusCode == expectedCode) { return response; } @@ -442,9 +487,22 @@ public static async Task GetWithRetryAsync( { // Ignore and retry } + // HttpContent can only be consumed once; create fresh content for retries + content = await CloneHttpContentAsync(content); await Task.Delay(TimeSpan.FromSeconds(delaySeconds)); } throw new Exception($"Failed to get expected status code {expectedCode} from {url} after {maxRetries} attempts"); } + + private static async Task CloneHttpContentAsync(HttpContent original) + { + var bytes = await original.ReadAsByteArrayAsync(); + var clone = new ByteArrayContent(bytes); + if (original.Headers.ContentType != null) + { + clone.Headers.ContentType = original.Headers.ContentType; + } + return clone; + } } } From c6c4ab022344eb0cd49e4851b04ac26c91723e4d Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Sat, 2 May 2026 17:32:12 -0700 Subject: [PATCH 2/6] Remove unstable test --- .../ApiGatewayStreamingTests.cs | 30 -------------- .../Program.cs | 41 ------------------- 2 files changed, 71 deletions(-) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs index 964aebcad..d357ee50f 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/ApiGatewayStreamingTests.cs @@ -128,36 +128,6 @@ public async Task StreamingErrorEndpoint_StreamIsTruncated() } } - [Fact] - public async Task OnCompletedCallback_IsExecuted() - { - var apiUrl = await _fixture.GetApiUrlAsync(); - using var httpClient = new HttpClient(); - - var response = await httpClient.GetWithRetryAsync($"{apiUrl}oncompleted-test"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - Output.WriteLine($"Body: {body}"); - Assert.Contains("OnCompleted callback registered", body); - - // The OnCompleted callback runs AFTER the response is sent, so we need to - // poll the verify endpoint until the callback has executed. Additionally, - // Lambda may route the verify request to a different execution environment, - // so we retry to eventually hit the same instance that ran the callback. - var verifyResponse = await httpClient.GetWithRetryAsync( - $"{apiUrl}oncompleted-verify", - HttpStatusCode.OK, - async resp => - { - var content = await resp.Content.ReadAsStringAsync(); - Output.WriteLine($"Verify body: {content}"); - var doc = JsonDocument.Parse(content); - return doc.RootElement.GetProperty("onCompletedExecuted").GetBoolean(); - }, - maxRetries: 10, delaySeconds: 3); - Assert.Equal(HttpStatusCode.OK, verifyResponse.StatusCode); - } - [Fact] public async Task CustomHeaders_PassedThrough() { diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs index fbebed76b..43f23a628 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/AspNetCoreStreamingApiGatewayTest/Program.cs @@ -61,37 +61,6 @@ return Results.Json(new { message = "Hello from streaming Lambda", timestamp = DateTime.UtcNow.ToString("o") }); }); -app.MapGet("/oncompleted-test", async (HttpContext context) => -{ - // Register an OnCompleted callback that writes a marker to a response header. - // Since headers are sent in the prelude before the body, we use a different approach: - // write a marker into the body from the OnCompleted callback via a shared flag. - var completedMarker = new CompletedMarker(); - context.Response.RegisterForDispose(completedMarker); - - context.Response.OnCompleted(async (state) => - { - var marker = (CompletedMarker)state; - marker.WasExecuted = true; - // Write to a static so the next request can verify it ran - CompletedMarkerStore.LastMarkerExecuted = true; - }, completedMarker); - - context.Response.ContentType = "text/plain"; - context.Response.StatusCode = 200; - - var stream = context.Response.BodyWriter.AsStream(); - using var writer = new StreamWriter(stream, leaveOpen: true); - await writer.WriteAsync("OnCompleted callback registered"); - await writer.FlushAsync(); -}); - -app.MapGet("/oncompleted-verify", (HttpContext context) => -{ - // Returns whether the OnCompleted callback from the previous request was executed - return Results.Json(new { onCompletedExecuted = CompletedMarkerStore.LastMarkerExecuted }); -}); - app.MapGet("/custom-headers", (HttpContext context) => { context.Response.StatusCode = 201; @@ -121,13 +90,3 @@ app.Run(); -class CompletedMarker : IDisposable -{ - public bool WasExecuted { get; set; } - public void Dispose() { } -} - -static class CompletedMarkerStore -{ - public static bool LastMarkerExecuted { get; set; } -} From f43f62e597b574209b9701b8170614ca647100a1 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Mon, 4 May 2026 12:25:10 -0700 Subject: [PATCH 3/6] More test work --- .../Amazon.Lambda.RuntimeSupport.csproj | 2 +- .../SnapshotRestore.Registry.csproj | 2 +- .../HandlerTests.cs | 2 +- .../HandlerWrapperTests.cs | 1 + .../LambdaBootstrapTests.cs | 2 +- .../LambdaResponseStreamingCoreTests.cs | 2 +- .../ResponseStreamFactoryTests.cs | 2 +- .../StreamingE2EWithMoq.cs | 24 +++++++++---------- 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj index 45c05f6f9..d6bb73f1f 100644 --- a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj @@ -3,7 +3,7 @@ - net8.0;net9.0;net10.0;net11.0 + net8.0;net9.0;net10.0 1.14.3 Provides a bootstrap and Lambda Runtime API Client to help you to develop custom .NET Core Lambda Runtimes. Amazon.Lambda.RuntimeSupport diff --git a/Libraries/src/SnapshotRestore.Registry/SnapshotRestore.Registry.csproj b/Libraries/src/SnapshotRestore.Registry/SnapshotRestore.Registry.csproj index 0bb3f5886..0664c1214 100644 --- a/Libraries/src/SnapshotRestore.Registry/SnapshotRestore.Registry.csproj +++ b/Libraries/src/SnapshotRestore.Registry/SnapshotRestore.Registry.csproj @@ -3,7 +3,7 @@ - net8.0;net9.0;net10.0;net11.0 + net8.0;net9.0;net10.0 1.0.1 Provides a Restore Hooks library to help you register before snapshot and after restore hooks. SnapshotRestore.Registry diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs index e71acddcd..93a4c9fbf 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs @@ -31,7 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class HandlerTests { private const string AggregateExceptionTestMarker = "AggregateExceptionTesting"; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs index a08cef865..fb4aae2e1 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs @@ -23,6 +23,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { + [Collection("RuntimeSupportStateCheck")] public class HandlerWrapperTests { private static readonly JsonSerializer Serializer = new JsonSerializer(); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs index 76e924ac0..341dece54 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs @@ -31,7 +31,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests /// Tests to test LambdaBootstrap when it's constructed using its actual constructor. /// Tests of the static GetLambdaBootstrap methods can be found in LambdaBootstrapWrapperTests. /// - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class LambdaBootstrapTests { readonly TestHandler _testFunction; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs index d7f4f0845..5da3d5e8b 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaResponseStreamingCoreTests.cs @@ -398,7 +398,7 @@ public void Dispose_DisposesInnerStream() // LambdaResponseStreamFactory tests // ───────────────────────────────────────────────────────────────────────────── - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class LambdaResponseStreamFactoryTests : IDisposable { diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs index cc9a19af2..0b49eb27a 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/ResponseStreamFactoryTests.cs @@ -21,7 +21,7 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class ResponseStreamFactoryTests : IDisposable { private const long MaxResponseSize = 20 * 1024 * 1024; diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs index 2ab9744e0..eff751f36 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs @@ -26,15 +26,15 @@ namespace Amazon.Lambda.RuntimeSupport.UnitTests { - [CollectionDefinition("ResponseStreamFactory")] - public class ResponseStreamFactoryCollection { } + [CollectionDefinition("RuntimeSupportStateCheck")] + public class RuntimeSupportStateCheckCollection { } /// /// End-to-end integration tests for the true-streaming architecture. /// These tests exercise the full pipeline: LambdaBootstrap → ResponseStreamFactory → /// ResponseStream → captured HTTP output stream. /// - [Collection("ResponseStreamFactory")] + [Collection("RuntimeSupportStateCheck")] public class StreamingE2EWithMoq : IDisposable { public void Dispose() @@ -162,7 +162,7 @@ public async Task Streaming_AllDataTransmitted_ContentRoundTrip() return new InvocationResponse(Stream.Null, false); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -189,7 +189,7 @@ public async Task Streaming_StreamFinalized_BytesWrittenMatchesPayload() return new InvocationResponse(Stream.Null, false); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -213,7 +213,7 @@ public async Task Buffered_HandlerDoesNotCallCreateStream_UsesSendResponsePath() return new InvocationResponse(new MemoryStream(responseBody)); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -237,7 +237,7 @@ public async Task Buffered_ResponseBodyTransmittedCorrectly() return new InvocationResponse(new MemoryStream(responseBody)); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -273,7 +273,7 @@ public async Task MidstreamError_SetsErrorStateWithExceptionDetails() throw new InvalidOperationException(errorMessage); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -457,7 +457,7 @@ public async Task BackwardCompat_ExistingHandlerSignature_WorksUnchanged() return new InvocationResponse(new MemoryStream(Encoding.UTF8.GetBytes("classic response"))); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -481,7 +481,7 @@ public async Task BackwardCompat_BufferedResponse_NoRegression() return new InvocationResponse(new MemoryStream(expected)); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); @@ -506,7 +506,7 @@ public async Task BackwardCompat_NullOutputStream_HandledGracefully() return new InvocationResponse(Stream.Null, false); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; // Should not throw @@ -529,7 +529,7 @@ public async Task BackwardCompat_HandlerThrows_StandardErrorReportingUsed() throw new Exception("classic handler error"); }; - using var bootstrap = new LambdaBootstrap(handler, null); + using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); bootstrap.Client = client; await bootstrap.InvokeOnceAsync(); From bdaa39661842a43e82dd81d3746b621fd0812b48 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Mon, 4 May 2026 12:41:34 -0700 Subject: [PATCH 4/6] Fix up test --- .../tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs index ad0fde951..0b3dc7cdf 100644 --- a/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs +++ b/Tools/LambdaTestTool-v2/tests/Amazon.Lambda.TestTool.UnitTests/PackagingTests.cs @@ -89,7 +89,7 @@ public void VerifyPackageContentsHasStaticAssets() public void VerifyPackageContentsHasRuntimeSupport() { var projectPath = Path.Combine(_workingDirectory, "Tools", "LambdaTestTool-v2", "src", "Amazon.Lambda.TestTool", "Amazon.Lambda.TestTool.csproj"); - var expectedFrameworks = new string[] { "net8.0", "net9.0", "net10.0", "net11.0" }; + var expectedFrameworks = new string[] { "net8.0", "net9.0", "net10.0" }; _output.WriteLine("Packing TestTool..."); var packProcess = new Process { From a5b6a8d3762159b8524cddbe5e8ee1122fb8e749 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Mon, 4 May 2026 16:38:32 -0700 Subject: [PATCH 5/6] Remove flaky unit test that is covered by integ tests --- .../LambdaBootstrapTests.cs | 10 ++--- .../StreamingE2EWithMoq.cs | 45 ------------------- 2 files changed, 5 insertions(+), 50 deletions(-) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs index 341dece54..d2b1a1556 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs @@ -319,7 +319,7 @@ public async Task StreamingMode_HandlerCallsCreateStream_SendTaskAwaited() return new InvocationResponse(Stream.Null, false); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -346,7 +346,7 @@ public async Task BufferedMode_HandlerDoesNotCallCreateStream_UsesSendResponse() return new InvocationResponse(outputStream); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -374,7 +374,7 @@ public async Task MidstreamError_ExceptionAfterWrites_ReportsViaTrailers() throw new InvalidOperationException("midstream failure"); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -404,7 +404,7 @@ public async Task PreStreamError_ExceptionBeforeCreateStream_UsesStandardErrorRe throw new InvalidOperationException("pre-stream failure"); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); @@ -430,7 +430,7 @@ public async Task Cleanup_ResponseStreamFactoryStateCleared_AfterInvocation() return new InvocationResponse(Stream.Null, false); }; - using (var bootstrap = new LambdaBootstrap(handler, null)) + using (var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables())) { bootstrap.Client = streamingClient; await bootstrap.InvokeOnceAsync(); diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs index eff751f36..6f6d6492c 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/StreamingE2EWithMoq.cs @@ -248,51 +248,6 @@ public async Task Buffered_ResponseBodyTransmittedCorrectly() Assert.Equal(responseBody, received.ToArray()); } - /// - /// End-to-end: midstream error sets error state on ResponseStream with exception details. - /// In production, RawStreamingHttpClient reads this state and writes trailing headers. - /// - [Fact] - public async Task MidstreamError_SetsErrorStateWithExceptionDetails() - { - var client = CreateClient(); - const string errorMessage = "something went wrong mid-stream"; - - // Signal so the handler waits until the streaming pipeline is fully - // established (WaitForCompletionAsync is actively listening) before throwing. - var streamingReady = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - client.OnStreamingReady = () => streamingReady.TrySetResult(true); - - LambdaBootstrapHandler handler = async (invocation) => - { - var stream = ResponseStreamFactory.CreateStream(Array.Empty()); - await stream.WriteAsync(Encoding.UTF8.GetBytes("some data")); - // Wait until StartStreamingResponseAsync has reached WaitForCompletionAsync - // so the completion signal will be observed when ReportError fires. - await streamingReady.Task; - throw new InvalidOperationException(errorMessage); - }; - - using var bootstrap = new LambdaBootstrap(handler, null, null, new TestEnvironmentVariables()); - bootstrap.Client = client; - await bootstrap.InvokeOnceAsync(); - - Assert.True(client.StartStreamingCalled); - Assert.NotNull(client.LastResponseStream); - Assert.True(client.LastResponseStream.HasError); - Assert.NotNull(client.LastResponseStream.ReportedError); - Assert.IsType(client.LastResponseStream.ReportedError); - Assert.Equal(errorMessage, client.LastResponseStream.ReportedError.Message); - - // Verify the handler's data was still captured before the error. - // Read directly from the output stream that was provided to the ResponseStream, - // which avoids any timing dependency on when CapturedHttpBytes is assigned - // relative to the SendTask completion. - Assert.NotNull(client.CapturedOutputStream); - var output = Encoding.UTF8.GetString(client.CapturedOutputStream.ToArray()); - Assert.Contains("some data", output); - } - /// /// Multi-concurrency: concurrent invocations use AsyncLocal for state isolation. /// Each invocation independently uses streaming or buffered mode without interference. From db749b6bc444b83b15ba7975ec980b56cedb84c2 Mon Sep 17 00:00:00 2001 From: Norm Johanson Date: Mon, 4 May 2026 18:59:39 -0700 Subject: [PATCH 6/6] More Test Work --- .../Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs | 1 + .../TestHelpers/TestRuntimeApiClient.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs index 93a4c9fbf..dacd01f87 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerTests.cs @@ -285,6 +285,7 @@ await Record.ExceptionAsync(async () => private async Task InvokeAsync(LambdaBootstrap bootstrap, string dataIn, TestRuntimeApiClient testRuntimeApiClient) { testRuntimeApiClient.FunctionInput = dataIn != null ? Encoding.UTF8.GetBytes(dataIn) : new byte[0]; + testRuntimeApiClient.LastOutputStream = null; using (var cancellationTokenSource = new CancellationTokenSource()) { diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs index 608439dd6..b2ef549bc 100644 --- a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs @@ -50,7 +50,7 @@ public TestRuntimeApiClient(IEnvironmentVariables environmentVariables, Dictiona public string LastTraceId { get; private set; } public byte[] FunctionInput { get; set; } - public Stream LastOutputStream { get; private set; } + public Stream LastOutputStream { get; internal set; } public Exception LastRecordedException { get; private set; } public void VerifyOutput(string expectedOutput)