Centralize test timeout constants to fix CI flakiness (#1224)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> Co-authored-by: Eric StJohn <ericstj@microsoft.com>
This commit is contained in:
parent
283caf9810
commit
a83d6198a5
7 changed files with 43 additions and 6 deletions
|
|
@ -10,4 +10,16 @@ public static class TestConstants
|
||||||
/// Set to 60 seconds to provide sufficient buffer for slow CI environments.
|
/// Set to 60 seconds to provide sufficient buffer for slow CI environments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);
|
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timeout for HttpClient operations in tests.
|
||||||
|
/// Set to 60 seconds to provide sufficient buffer for slow CI environments.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(60);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timeout for short-lived HTTP requests during polling operations.
|
||||||
|
/// Set to 2 seconds for quick failure detection while polling.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly TimeSpan HttpClientPollingTimeout = TimeSpan.FromSeconds(2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,11 @@ public abstract class OAuthTestBase : KestrelInMemoryTest, IAsyncDisposable
|
||||||
|
|
||||||
protected async Task<WebApplication> StartMcpServerAsync(string path = "", string? authScheme = null)
|
protected async Task<WebApplication> StartMcpServerAsync(string path = "", string? authScheme = null)
|
||||||
{
|
{
|
||||||
|
// Wait for the OAuth server to be ready before starting the MCP server.
|
||||||
|
// This prevents race conditions in CI where the OAuth server may not be
|
||||||
|
// fully initialized when the first test request is made.
|
||||||
|
await TestOAuthServer.ServerStarted.WaitAsync(TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
|
Builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||||
{
|
{
|
||||||
options.TokenValidationParameters.ValidAudience = $"{McpServerUrl}{path}";
|
options.TokenValidationParameters.ValidAudience = $"{McpServerUrl}{path}";
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public class ServerConformanceTests : IAsyncLifetime
|
||||||
// Wait for server to be ready (retry for up to 30 seconds)
|
// Wait for server to be ready (retry for up to 30 seconds)
|
||||||
var timeout = TimeSpan.FromSeconds(30);
|
var timeout = TimeSpan.FromSeconds(30);
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
|
using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientPollingTimeout };
|
||||||
|
|
||||||
while (stopwatch.Elapsed < timeout)
|
while (stopwatch.Elapsed < timeout)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class KestrelInMemoryTest : LoggedTest
|
||||||
protected static void ConfigureHttpClient(HttpClient httpClient)
|
protected static void ConfigureHttpClient(HttpClient httpClient)
|
||||||
{
|
{
|
||||||
httpClient.BaseAddress = new Uri("http://localhost:5000/");
|
httpClient.BaseAddress = new Uri("http://localhost:5000/");
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
httpClient.Timeout = TestConstants.HttpClientTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ public sealed class Program
|
||||||
|
|
||||||
private readonly ILoggerProvider? _loggerProvider;
|
private readonly ILoggerProvider? _loggerProvider;
|
||||||
private readonly IConnectionListenerFactory? _kestrelTransport;
|
private readonly IConnectionListenerFactory? _kestrelTransport;
|
||||||
|
private readonly TaskCompletionSource _serverStarted = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Program"/> class with logging and transport parameters.
|
/// Initializes a new instance of the <see cref="Program"/> class with logging and transport parameters.
|
||||||
|
|
@ -47,6 +48,11 @@ public sealed class Program
|
||||||
_kestrelTransport = kestrelTransport;
|
_kestrelTransport = kestrelTransport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a task that completes when the server has started and is ready to accept connections.
|
||||||
|
/// </summary>
|
||||||
|
public Task ServerStarted => _serverStarted.Task;
|
||||||
|
|
||||||
// Track if we've already issued an already-expired token for the CanAuthenticate_WithTokenRefresh test which uses the test-refresh-client registration.
|
// Track if we've already issued an already-expired token for the CanAuthenticate_WithTokenRefresh test which uses the test-refresh-client registration.
|
||||||
public bool HasRefreshedToken { get; set; }
|
public bool HasRefreshedToken { get; set; }
|
||||||
|
|
||||||
|
|
@ -541,7 +547,20 @@ public sealed class Program
|
||||||
Console.WriteLine($"Demo Client ID: {clientId}");
|
Console.WriteLine($"Demo Client ID: {clientId}");
|
||||||
Console.WriteLine($"Demo Client Secret: {clientSecret}");
|
Console.WriteLine($"Demo Client Secret: {clientSecret}");
|
||||||
|
|
||||||
await app.RunAsync(cancellationToken);
|
await app.StartAsync(cancellationToken);
|
||||||
|
_serverStarted.TrySetResult();
|
||||||
|
|
||||||
|
// Wait until cancellation is requested
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(Timeout.Infinite, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Expected when cancellation is requested
|
||||||
|
}
|
||||||
|
|
||||||
|
await app.StopAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using ModelContextProtocol.Tests.Utils;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests;
|
namespace ModelContextProtocol.Tests;
|
||||||
|
|
||||||
|
|
@ -33,7 +34,7 @@ public class EverythingSseServerFixture : IAsyncDisposable
|
||||||
?? throw new InvalidOperationException($"Could not start process for {processStartInfo.FileName} with '{processStartInfo.Arguments}'.");
|
?? throw new InvalidOperationException($"Could not start process for {processStartInfo.FileName} with '{processStartInfo.Arguments}'.");
|
||||||
|
|
||||||
// Poll until the server is ready (up to 30 seconds)
|
// Poll until the server is ready (up to 30 seconds)
|
||||||
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
|
using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientPollingTimeout };
|
||||||
var endpoint = $"http://localhost:{_port}/sse";
|
var endpoint = $"http://localhost:{_port}/sse";
|
||||||
var deadline = DateTime.UtcNow.AddSeconds(30);
|
var deadline = DateTime.UtcNow.AddSeconds(30);
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ public class EverythingSseServerFixture : IAsyncDisposable
|
||||||
|
|
||||||
using var stopProcess = Process.Start(stopInfo)
|
using var stopProcess = Process.Start(stopInfo)
|
||||||
?? throw new InvalidOperationException($"Could not stop process for {stopInfo.FileName} with '{stopInfo.Arguments}'.");
|
?? throw new InvalidOperationException($"Could not stop process for {stopInfo.FileName} with '{stopInfo.Arguments}'.");
|
||||||
await stopProcess.WaitForExitAsync(TimeSpan.FromSeconds(10));
|
await stopProcess.WaitForExitAsync(TestConstants.DefaultTimeout);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class StdioServerIntegrationTests(ITestOutputHelper testOutputHelper) : L
|
||||||
// https://github.com/dotnet/runtime/issues/109432, https://github.com/dotnet/runtime/issues/44944
|
// https://github.com/dotnet/runtime/issues/109432, https://github.com/dotnet/runtime/issues/44944
|
||||||
Assert.Equal(0, kill(process.Id, SIGINT));
|
Assert.Equal(0, kill(process.Id, SIGINT));
|
||||||
|
|
||||||
await process.WaitForExitAsync(TimeSpan.FromSeconds(10));
|
await process.WaitForExitAsync(TestConstants.DefaultTimeout);
|
||||||
|
|
||||||
Assert.True(process.HasExited);
|
Assert.True(process.HasExited);
|
||||||
Assert.Equal(0, process.ExitCode);
|
Assert.Equal(0, process.ExitCode);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue