diff --git a/tests/Aspire.EndToEnd.Tests/IntegrationServicesFixture.cs b/tests/Aspire.EndToEnd.Tests/IntegrationServicesFixture.cs index 47d6abc3b9..1a146c9787 100644 --- a/tests/Aspire.EndToEnd.Tests/IntegrationServicesFixture.cs +++ b/tests/Aspire.EndToEnd.Tests/IntegrationServicesFixture.cs @@ -6,6 +6,9 @@ using Xunit.Abstractions; using Aspire.TestProject; using Aspire.Workload.Tests; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; +using Xunit.Sdk; namespace Aspire.EndToEnd.Tests; @@ -24,6 +27,8 @@ public sealed class IntegrationServicesFixture : IAsyncLifetime #endif public static string? TestScenario { get; } = EnvironmentVariables.TestScenario; + private const int DefaultWaitForTextsTimeoutSecs = 8 * 60; + public Dictionary Projects => Project?.InfoTable ?? throw new InvalidOperationException("Project is not initialized"); private TestResourceNames _resourcesToSkip; private readonly IMessageSink _diagnosticMessageSink; @@ -82,11 +87,60 @@ public async Task InitializeAsync() extraArgs += $"--skip-resources {skipArg}"; } await Project.StartAppHostAsync([extraArgs]); + var waitForTextsTask = WaitForAllTextAsync(TimeSpan.FromSeconds(DefaultWaitForTextsTimeoutSecs)); foreach (var project in Projects.Values) { project.Client = AspireProject.Client.Value; } + + await waitForTextsTask; + } + + public async Task WaitForAllTextAsync(TimeSpan timeSpan) + { + string[] waitForTexts = GetWaitForTexts(); + if (waitForTexts.Length == 0) + { + return; + } + + ConcurrentDictionary waitForTextRegexes = new(); + foreach (var waitForText in waitForTexts) + { + waitForTextRegexes.TryAdd(waitForText, new Regex(waitForText)); + } + + TaskCompletionSource tcs = new(); + Project.AppHostProcess!.OutputDataReceived += (_, e) => CheckText(e.Data); + Project.AppHostProcess!.ErrorDataReceived += (_, e) => CheckText(e.Data); + + try + { + await tcs.Task.WaitAsync(timeSpan); + } + catch (TimeoutException te) + { + throw new XunitException($"Timed out waiting for the following texts after {timeSpan.TotalSeconds} secs: '{string.Join("', '", waitForTextRegexes.Keys)}'", te); + } + + void CheckText(string? line) + { + if (string.IsNullOrEmpty(line)) + { + return; + } + + if (waitForTextRegexes.Where(r => r.Value.IsMatch(line)).Select(kvp => kvp.Key).FirstOrDefault() is string matched) + { + waitForTextRegexes.TryRemove(matched, out _); + } + + if (waitForTextRegexes.IsEmpty) + { + tcs.TrySetResult(); + } + } } public Task DumpComponentLogsAsync(TestResourceNames resource, ITestOutputHelper? testOutputArg = null) @@ -165,4 +219,13 @@ private static TestResourceNames GetResourcesToSkip() return resourcesToSkip; } + + private static string[] GetWaitForTexts() => TestScenario switch + { + "cosmos" => [ + "TestProject.AppHost.Resources.cosmos\\[0\\] .* Started$", + "TestProject.AppHost.Resources.integrationservicea\\[0\\] .* Application started" + ], + _ => [] + }; } diff --git a/tests/Aspire.EndToEnd.Tests/IntegrationServicesTests.cs b/tests/Aspire.EndToEnd.Tests/IntegrationServicesTests.cs index 78e1db05aa..ed3707ec3c 100644 --- a/tests/Aspire.EndToEnd.Tests/IntegrationServicesTests.cs +++ b/tests/Aspire.EndToEnd.Tests/IntegrationServicesTests.cs @@ -32,6 +32,7 @@ public Task VerifyComponentWorks(TestResourceNames resourceName) _integrationServicesFixture.EnsureAppHasResources(resourceName); try { + _testOutput.WriteLine($"**** Sending request for {resourceName}"); var response = await _integrationServicesFixture.IntegrationServiceA.HttpGetAsync("http", $"/{resourceName}/verify"); var responseContent = await response.Content.ReadAsStringAsync(); diff --git a/tests/testproject/TestProject.AppHost/TestProgram.cs b/tests/testproject/TestProject.AppHost/TestProgram.cs index e6956dc222..c3c3987596 100644 --- a/tests/testproject/TestProject.AppHost/TestProgram.cs +++ b/tests/testproject/TestProject.AppHost/TestProgram.cs @@ -8,6 +8,7 @@ using Aspire.Hosting.Testing; using Aspire.TestProject; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; public class TestProgram : IDisposable { @@ -85,7 +86,9 @@ private TestProgram( } if (!resourcesToSkip.HasFlag(TestResourceNames.cosmos) || !resourcesToSkip.HasFlag(TestResourceNames.efcosmos)) { - var cosmos = AppBuilder.AddAzureCosmosDB("cosmos").RunAsEmulator(); + var cosmos = AppBuilder + .AddAzureCosmosDB("cosmos") + .RunAsEmulator(); IntegrationServiceABuilder = IntegrationServiceABuilder.WithReference(cosmos); } if (!resourcesToSkip.HasFlag(TestResourceNames.eventhubs)) @@ -95,6 +98,18 @@ private TestProgram( } } + AppBuilder.Services.AddLogging(logging => + { + logging.ClearProviders(); + logging.AddSimpleConsole(configure => + { + configure.SingleLine = true; + }); + logging.SetMinimumLevel(LogLevel.Debug); + logging.AddFilter("Aspire", LogLevel.Debug); + logging.AddFilter(builder.Environment.ApplicationName, LogLevel.Debug); + }); + AppBuilder.Services.AddHostedService(); AppBuilder.Services.AddLifecycleHook(); AppBuilder.Services.AddHttpClient(); diff --git a/tests/testproject/TestProject.WorkerA/Worker.cs b/tests/testproject/TestProject.WorkerA/Worker.cs index e8a2577e17..37e860fb7a 100644 --- a/tests/testproject/TestProject.WorkerA/Worker.cs +++ b/tests/testproject/TestProject.WorkerA/Worker.cs @@ -20,7 +20,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Worker running at: {Time}", DateTimeOffset.Now); } - await Task.Delay(1000, stoppingToken); + await Task.Delay(30_000, stoppingToken); } } }