Skip to content

Commit e11f4ac

Browse files
committed
Fix AspNetCoreServer tests surfaced by unit-test auto-discovery
These test projects were never run in CI before the discovery change, so pre-existing failures went unnoticed. Fixes by category: Real test bugs (failed deterministically, incl. locally): - BuildStreamingPreludeTests / ResponseStreamingPropertyTests: the StandalonePreludeBuilder subclassed the HTTP API v2 function (whose BuildStreamingPrelude override collapses headers into single-value Headers), but the tests assert on the multi-value MultiValueHeaders collection. Derive from the REST v1 APIGatewayProxyFunction instead, matching the assertions and the section comments. CI-environment robustness (passed locally, failed in CI): - ci.buildspec.yml: raise fs.inotify.max_user_instances (container default 128 was exhausted once all ASP.NET-host-starting unit tests run, surfacing as inotify IOExceptions across ~90 tests). - TestMinimalAPI: capture child-process stdout/stderr and handle the WaitForExit timeout so the out-of-process 'dotnet run' failure (exit 129) is diagnosable and the process cannot leak. - AddAWSLambdaBeforeSnapshotRequestTests: poll for the before-snapshot callback instead of racing a fixed 500 ms delay that was flaky under CI load.
1 parent 76971eb commit e11f4ac

5 files changed

Lines changed: 42 additions & 8 deletions

File tree

Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/AddAWSLambdaBeforeSnapshotRequestTests.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,16 @@ public async Task VerifyCallbackIsInvoked(LambdaEventSource hostingType)
4444

4545
var serverTask = app.RunAsync();
4646

47-
// let the server run for a max of 500 ms
48-
await Task.WhenAny(
49-
serverTask,
50-
Task.Delay(TimeSpan.FromMilliseconds(500)));
47+
// Poll for the before-snapshot callback to fire rather than racing a single fixed delay.
48+
// A fixed 500 ms window is flaky under load (observed intermittently in CI): the
49+
// before-snapshot request may not have completed yet, leaving the callback unset. Wait up
50+
// to 10 seconds, checking frequently, and stop as soon as the callback has run.
51+
var timeout = TimeSpan.FromSeconds(10);
52+
var sw = System.Diagnostics.Stopwatch.StartNew();
53+
while (!callbackDidTheCallback && sw.Elapsed < timeout && !serverTask.IsCompleted)
54+
{
55+
await Task.Delay(TimeSpan.FromMilliseconds(50));
56+
}
5157

5258
// shut down server
5359
await app.StopAsync();

Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/BuildStreamingPreludeTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ public class BuildStreamingPreludeTests
1414
{
1515
// Subclass that skips host startup entirely and
1616
// just exposes BuildStreamingPrelude directly without needing a running host.
17-
private class StandalonePreludeBuilder : APIGatewayHttpApiV2ProxyFunction
17+
// Derives from the REST (v1) APIGatewayProxyFunction because these tests assert on the
18+
// multi-value MultiValueHeaders collection; the HTTP API v2 function instead collapses
19+
// headers into the single-value Headers collection (see APIGatewayHttpApiV2ProxyFunction).
20+
private class StandalonePreludeBuilder : APIGatewayProxyFunction
1821
{
1922
// Use the StartupMode.FirstRequest constructor so no host is started eagerly.
2023
public StandalonePreludeBuilder()

Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ protected override APIGatewayHttpApiV2ProxyResponse MarshallResponse(
7171
}
7272
}
7373

74-
private class StandalonePreludeBuilder : APIGatewayHttpApiV2ProxyFunction
74+
// Derives from the REST (v1) APIGatewayProxyFunction because the prelude property tests
75+
// (Property3/Property4) assert on the multi-value MultiValueHeaders collection; the HTTP
76+
// API v2 function instead collapses headers into the single-value Headers collection.
77+
private class StandalonePreludeBuilder : APIGatewayProxyFunction
7578
{
7679
public StandalonePreludeBuilder() : base(StartupMode.FirstRequest) { }
7780

Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestMinimalAPI.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,32 @@ public T ExecuteRequest<T>(string eventFilePath)
5454
processStartInfo.FileName = GetSystemShell();
5555
processStartInfo.Arguments = $"{comamndArgument} dotnet run \"{requestFilePath}\" \"{responseFilePath}\"";
5656
processStartInfo.WorkingDirectory = GetTestAppDirectory();
57+
// Capture the child process output so a launch/build failure (e.g. the exit code 129
58+
// seen intermittently in CI) surfaces a diagnosable message instead of a bare exit code.
59+
processStartInfo.RedirectStandardOutput = true;
60+
processStartInfo.RedirectStandardError = true;
61+
processStartInfo.UseShellExecute = false;
5762

5863

5964
lock (lock_process)
6065
{
6166
using var process = Process.Start(processStartInfo);
62-
process.WaitForExit(15000);
67+
var stdout = process.StandardOutput.ReadToEndAsync();
68+
var stderr = process.StandardError.ReadToEndAsync();
69+
70+
// WaitForExit(timeout) returns false when the process is still running at the
71+
// timeout; kill it so it does not leak, then fail with the captured output.
72+
if (!process.WaitForExit(60000))
73+
{
74+
process.Kill(entireProcessTree: true);
75+
throw new Exception(
76+
$"Process timed out after 60s.{Environment.NewLine}STDOUT:{Environment.NewLine}{stdout.Result}{Environment.NewLine}STDERR:{Environment.NewLine}{stderr.Result}");
77+
}
6378

6479
if (process.ExitCode != 0)
6580
{
66-
throw new Exception("Process failed with exit code: " + process.ExitCode);
81+
throw new Exception(
82+
$"Process failed with exit code: {process.ExitCode}.{Environment.NewLine}STDOUT:{Environment.NewLine}{stdout.Result}{Environment.NewLine}STDERR:{Environment.NewLine}{stderr.Result}");
6783
}
6884

6985
if(!File.Exists(responseFilePath))

buildtools/ci.buildspec.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ phases:
55
runtime-versions:
66
dotnet: 8.x
77
commands:
8+
# Raise the inotify limits. The AspNetCoreServer tests each start an ASP.NET Core host, and the
9+
# file-watching config providers allocate inotify instances; the container default (128) is
10+
# exhausted once all unit-test projects run, surfacing as
11+
# "The configured user limit (128) on the number of inotify instances has been reached".
12+
- sysctl -w fs.inotify.max_user_instances=1024 || true
13+
- sysctl -w fs.inotify.max_user_watches=524288 || true
814
# Find and delete the global.json files that were added by CodeBuild. This causes issues when multiple SDKs are installed.
915
- find / -type f -name 'global.json' -delete
1016
# The tests need .NET 3.1, 6, 8, 9 and 10. .NET6 is installed by default. .NET8 is added in the runtime-versions. .NET 3.1, 9 and 10 are installed manually.

0 commit comments

Comments
 (0)