From 1c2bed7b8960917a684ae873324756f3c588397a Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 5 Feb 2026 19:14:04 -0600 Subject: [PATCH 1/5] Update to Azure.Core 1.51.1 Use latest versions for all dotnet/runtime nuget packages. This simplifies our dependency management. Remove ForceLatestDotnetVersions property from multiple project files --- Directory.Packages.props | 70 ++++++------------- .../AzureAIFoundryEndToEnd.WebStory.csproj | 3 - .../AzureOpenAIEndToEnd.WebStory.csproj | 3 - .../GitHubModelsEndToEnd.WebStory.csproj | 3 - .../OpenAIEndToEnd.WebStory.csproj | 3 - .../Aspire.Azure.AI.Inference.csproj | 2 - .../Aspire.Azure.AI.OpenAI.csproj | 2 - .../Aspire.OpenAI/Aspire.OpenAI.csproj | 2 - .../Aspire.Azure.AI.Inference.Tests.csproj | 3 - .../Aspire.Azure.AI.OpenAI.Tests.csproj | 3 - .../Aspire.OpenAI.Tests.csproj | 3 - 11 files changed, 23 insertions(+), 74 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8e4674e5743..097be0ed6d4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -175,17 +175,18 @@ - + - + + - + @@ -195,6 +196,23 @@ + + + + + + + + + + + + + + + + @@ -218,18 +236,6 @@ - - - - - - - - - - - - @@ -254,26 +260,8 @@ - - - - - - - - - - - - - - - - - - - + @@ -295,17 +283,5 @@ - - - - - - - - - - - - diff --git a/playground/AzureAIFoundryEndToEnd/AzureAIFoundryEndToEnd.WebStory/AzureAIFoundryEndToEnd.WebStory.csproj b/playground/AzureAIFoundryEndToEnd/AzureAIFoundryEndToEnd.WebStory/AzureAIFoundryEndToEnd.WebStory.csproj index cd25e05445f..aa19fad6805 100644 --- a/playground/AzureAIFoundryEndToEnd/AzureAIFoundryEndToEnd.WebStory/AzureAIFoundryEndToEnd.WebStory.csproj +++ b/playground/AzureAIFoundryEndToEnd/AzureAIFoundryEndToEnd.WebStory/AzureAIFoundryEndToEnd.WebStory.csproj @@ -4,9 +4,6 @@ $(DefaultTargetFramework) enable enable - - - true diff --git a/playground/AzureOpenAIEndToEnd/AzureOpenAIEndToEnd.WebStory/AzureOpenAIEndToEnd.WebStory.csproj b/playground/AzureOpenAIEndToEnd/AzureOpenAIEndToEnd.WebStory/AzureOpenAIEndToEnd.WebStory.csproj index cefd34325a6..ede52a85213 100644 --- a/playground/AzureOpenAIEndToEnd/AzureOpenAIEndToEnd.WebStory/AzureOpenAIEndToEnd.WebStory.csproj +++ b/playground/AzureOpenAIEndToEnd/AzureOpenAIEndToEnd.WebStory/AzureOpenAIEndToEnd.WebStory.csproj @@ -4,9 +4,6 @@ $(DefaultTargetFramework) enable enable - - - true diff --git a/playground/GitHubModelsEndToEnd/GitHubModelsEndToEnd.WebStory/GitHubModelsEndToEnd.WebStory.csproj b/playground/GitHubModelsEndToEnd/GitHubModelsEndToEnd.WebStory/GitHubModelsEndToEnd.WebStory.csproj index cd25e05445f..aa19fad6805 100644 --- a/playground/GitHubModelsEndToEnd/GitHubModelsEndToEnd.WebStory/GitHubModelsEndToEnd.WebStory.csproj +++ b/playground/GitHubModelsEndToEnd/GitHubModelsEndToEnd.WebStory/GitHubModelsEndToEnd.WebStory.csproj @@ -4,9 +4,6 @@ $(DefaultTargetFramework) enable enable - - - true diff --git a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj index 2df50b9502b..60f4356d1a3 100644 --- a/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj +++ b/playground/OpenAIEndToEnd/OpenAIEndToEnd.WebStory/OpenAIEndToEnd.WebStory.csproj @@ -4,9 +4,6 @@ $(DefaultTargetFramework) enable enable - - - true diff --git a/src/Components/Aspire.Azure.AI.Inference/Aspire.Azure.AI.Inference.csproj b/src/Components/Aspire.Azure.AI.Inference/Aspire.Azure.AI.Inference.csproj index 7370e834905..7926621c583 100644 --- a/src/Components/Aspire.Azure.AI.Inference/Aspire.Azure.AI.Inference.csproj +++ b/src/Components/Aspire.Azure.AI.Inference/Aspire.Azure.AI.Inference.csproj @@ -9,8 +9,6 @@ $(NoWarn);SYSLIB1100;SYSLIB1101 true - - true diff --git a/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj b/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj index 99d48574b2e..db2deffe6bc 100644 --- a/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj +++ b/src/Components/Aspire.Azure.AI.OpenAI/Aspire.Azure.AI.OpenAI.csproj @@ -10,8 +10,6 @@ $(NoWarn);SYSLIB1100;SYSLIB1101;AOAI001 true - - true diff --git a/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj b/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj index 836c1d9d11c..6b76d148c20 100644 --- a/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj +++ b/src/Components/Aspire.OpenAI/Aspire.OpenAI.csproj @@ -8,8 +8,6 @@ $(NoWarn);SYSLIB1100;SYSLIB1101 true - - true diff --git a/tests/Aspire.Azure.AI.Inference.Tests/Aspire.Azure.AI.Inference.Tests.csproj b/tests/Aspire.Azure.AI.Inference.Tests/Aspire.Azure.AI.Inference.Tests.csproj index cca72f73ac9..948e92b0096 100644 --- a/tests/Aspire.Azure.AI.Inference.Tests/Aspire.Azure.AI.Inference.Tests.csproj +++ b/tests/Aspire.Azure.AI.Inference.Tests/Aspire.Azure.AI.Inference.Tests.csproj @@ -2,9 +2,6 @@ $(AllTargetFrameworks) - - - true diff --git a/tests/Aspire.Azure.AI.OpenAI.Tests/Aspire.Azure.AI.OpenAI.Tests.csproj b/tests/Aspire.Azure.AI.OpenAI.Tests/Aspire.Azure.AI.OpenAI.Tests.csproj index a27e07a3a63..2f45183b667 100644 --- a/tests/Aspire.Azure.AI.OpenAI.Tests/Aspire.Azure.AI.OpenAI.Tests.csproj +++ b/tests/Aspire.Azure.AI.OpenAI.Tests/Aspire.Azure.AI.OpenAI.Tests.csproj @@ -2,9 +2,6 @@ $(AllTargetFrameworks) - - - true diff --git a/tests/Aspire.OpenAI.Tests/Aspire.OpenAI.Tests.csproj b/tests/Aspire.OpenAI.Tests/Aspire.OpenAI.Tests.csproj index 62892924e61..dec5f627481 100644 --- a/tests/Aspire.OpenAI.Tests/Aspire.OpenAI.Tests.csproj +++ b/tests/Aspire.OpenAI.Tests/Aspire.OpenAI.Tests.csproj @@ -2,9 +2,6 @@ $(AllTargetFrameworks) - - - true From e415b4a303e1849695bca65761171bc2bc5be510 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 10 Feb 2026 18:19:15 -0600 Subject: [PATCH 2/5] Update AzureDeployerTests to use WaitForShutdown instead of StopAsync There is a timing issue when using Start/Stop since the background pipeline might still be running and it cancels the pipeline before it can complete. --- tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs b/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs index 0c9ff5c0492..09460f25c8d 100644 --- a/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs +++ b/tests/Aspire.Hosting.Azure.Tests/AzureDeployerTests.cs @@ -1072,7 +1072,7 @@ public async Task DeployAsync_WithAzureResourceDependencies_DoesNotHang(string s // Act using var app = builder.Build(); await app.StartAsync(); - await app.StopAsync(); + await app.WaitForShutdownAsync(); if (step == "diagnostics") { @@ -1159,7 +1159,7 @@ public async Task DeployAsync_WithRedisAccessKeyAuthentication_CreatesCorrectDep // Act using var app = builder.Build(); await app.StartAsync(); - await app.StopAsync(); + await app.WaitForShutdownAsync(); // In diagnostics mode, verify the deployment graph shows correct dependencies var logs = mockActivityReporter.LoggedMessages From 8d9bfeb47f2d4f705dba49163eb0b7aef700acd6 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 11 Feb 2026 10:05:05 -0600 Subject: [PATCH 3/5] Fix AuxiliaryBackchannelTests by adding a Task that completes when the AuxiliaryBackchannelService is listening and ready for connections. --- .../AuxiliaryBackchannelService.cs | 10 ++++++++ .../Backchannel/AuxiliaryBackchannelTests.cs | 23 +++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelService.cs b/src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelService.cs index 6bf750cff6a..2ef434dd1af 100644 --- a/src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelService.cs +++ b/src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelService.cs @@ -22,12 +22,21 @@ internal sealed class AuxiliaryBackchannelService( : BackgroundService { private Socket? _serverSocket; + private readonly TaskCompletionSource _listeningTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); /// /// Gets the Unix socket path where the auxiliary backchannel is listening. /// public string? SocketPath { get; private set; } + /// + /// Gets a task that completes when the server socket is bound and listening for connections. + /// + /// + /// Used by tests to wait until the backchannel is ready before attempting to connect. + /// + internal Task ListeningTask => _listeningTcs.Task; + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try @@ -72,6 +81,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) _serverSocket.Listen(backlog: 10); // Allow multiple pending connections logger.LogDebug("Auxiliary backchannel listening on {SocketPath}", SocketPath); + _listeningTcs.TrySetResult(); // Accept connections in a loop (supporting multiple concurrent connections) while (!stoppingToken.IsCancellationRequested) diff --git a/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs b/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs index 86f7622be67..f47a3e50e06 100644 --- a/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs +++ b/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs @@ -38,6 +38,7 @@ public async Task CanStartAuxiliaryBackchannelService() // Get the service and verify it started var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); Assert.True(File.Exists(service.SocketPath)); @@ -81,15 +82,16 @@ public async Task CanConnectMultipleClientsToAuxiliaryBackchannel() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect multiple clients concurrently var client1Socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); var client2Socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); var client3Socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); - + var endpoint = new UnixDomainSocketEndPoint(service.SocketPath); - + await client1Socket.ConnectAsync(endpoint).WaitAsync(TimeSpan.FromSeconds(60)); await client2Socket.ConnectAsync(endpoint).WaitAsync(TimeSpan.FromSeconds(60)); await client3Socket.ConnectAsync(endpoint).WaitAsync(TimeSpan.FromSeconds(60)); @@ -126,6 +128,7 @@ public async Task CanInvokeRpcMethodOnAuxiliaryBackchannel() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -164,6 +167,7 @@ public async Task GetAppHostInformationAsyncReturnsAppHostPath() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -208,6 +212,7 @@ public async Task MultipleClientsCanInvokeRpcMethodsConcurrently() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Create multiple clients and invoke RPC methods concurrently @@ -255,6 +260,7 @@ public async Task GetAppHostInformationAsyncReturnsFilePathWithExtension() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -275,10 +281,10 @@ public async Task GetAppHostInformationAsyncReturnsFilePathWithExtension() Assert.NotNull(appHostInfo); Assert.NotNull(appHostInfo.AppHostPath); Assert.NotEmpty(appHostInfo.AppHostPath); - + // The path should be an absolute path Assert.True(Path.IsPathRooted(appHostInfo.AppHostPath), $"Expected absolute path but got: {appHostInfo.AppHostPath}"); - + // In test scenarios where assembly metadata is not available, we may get a path without extension // (falling back to AppHost:Path). In real scenarios with proper metadata, we should get .csproj or .cs // So we just verify the path is non-empty and rooted @@ -304,12 +310,13 @@ public async Task SocketPathUsesAuxiPrefix() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Verify that the socket path uses "auxi.sock." prefix var fileName = Path.GetFileName(service.SocketPath); Assert.StartsWith("auxi.sock.", fileName); - + // Verify that the socket file can be created (not blocked by Windows reserved names) Assert.True(File.Exists(service.SocketPath), $"Socket file should exist at: {service.SocketPath}"); @@ -338,6 +345,7 @@ public async Task CallResourceMcpToolAsyncThrowsWhenResourceNotFound() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -382,6 +390,7 @@ public async Task CallResourceMcpToolAsyncThrowsWhenResourceHasNoMcpAnnotation() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -422,6 +431,7 @@ public async Task StopAppHostAsyncInitiatesShutdown() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -474,6 +484,7 @@ public async Task GetCapabilitiesAsyncReturnsV1AndV2() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -515,6 +526,7 @@ public async Task GetAppHostInfoAsyncV2ReturnsAppHostInfo() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -561,6 +573,7 @@ public async Task GetResourcesAsyncV2ReturnsResources() // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client From ead3d4bd9180b82cddf2e4159a025fa108c06f59 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 11 Feb 2026 10:47:30 -0600 Subject: [PATCH 4/5] Remove double registration of AuxiliaryBackchannelService as an IHostedService. --- .../Backchannel/AuxiliaryBackchannelTests.cs | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs b/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs index f47a3e50e06..1c06ea8f482 100644 --- a/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs +++ b/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs @@ -28,10 +28,6 @@ public async Task CanStartAuxiliaryBackchannelService() return Task.CompletedTask; }); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TimeSpan.FromSeconds(60)); @@ -72,10 +68,6 @@ public async Task CanConnectMultipleClientsToAuxiliaryBackchannel() return Task.CompletedTask; }); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TimeSpan.FromSeconds(60)); @@ -118,10 +110,6 @@ public async Task CanInvokeRpcMethodOnAuxiliaryBackchannel() // When the Dashboard is not part of the app model, null should be returned using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TimeSpan.FromSeconds(60)); @@ -157,10 +145,6 @@ public async Task GetAppHostInformationAsyncReturnsAppHostPath() // This test verifies that GetAppHostInformationAsync returns the AppHost path using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TimeSpan.FromSeconds(60)); @@ -202,10 +186,6 @@ public async Task MultipleClientsCanInvokeRpcMethodsConcurrently() // When the Dashboard is not part of the app model, null should be returned using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TimeSpan.FromSeconds(60)); @@ -250,10 +230,6 @@ public async Task GetAppHostInformationAsyncReturnsFilePathWithExtension() // For .csproj-based AppHosts, it should include the .csproj extension using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TimeSpan.FromSeconds(60)); @@ -300,10 +276,6 @@ public async Task SocketPathUsesAuxiPrefix() // to avoid Windows reserved device name issues (AUX is reserved on Windows < 11) using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); @@ -335,10 +307,6 @@ public async Task CallResourceMcpToolAsyncThrowsWhenResourceNotFound() // Add a simple container resource (without MCP) builder.AddContainer("mycontainer", "nginx"); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); @@ -380,10 +348,6 @@ public async Task CallResourceMcpToolAsyncThrowsWhenResourceHasNoMcpAnnotation() // Add a simple container resource (without MCP) builder.AddContainer("mycontainer", "nginx"); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); @@ -421,10 +385,6 @@ public async Task StopAppHostAsyncInitiatesShutdown() // This test verifies that StopAppHostAsync initiates AppHost shutdown using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); @@ -474,10 +434,6 @@ public async Task GetCapabilitiesAsyncReturnsV1AndV2() // This test verifies that GetCapabilitiesAsync returns both v1 and v2 capabilities using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); @@ -516,10 +472,6 @@ public async Task GetAppHostInfoAsyncV2ReturnsAppHostInfo() // This test verifies that the v2 GetAppHostInfoAsync returns AppHost info using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(outputHelper); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); @@ -563,10 +515,6 @@ public async Task GetResourcesAsyncV2ReturnsResources() // Add a simple parameter resource builder.AddParameter("myparam"); - // Register the auxiliary backchannel service - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => sp.GetRequiredService()); - using var app = builder.Build(); await app.StartAsync().WaitAsync(TestConstants.DefaultTimeoutTimeSpan); From 4da92ddbfc912d96b018355439a033fc3a4443c8 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 11 Feb 2026 11:15:18 -0600 Subject: [PATCH 5/5] Fix ResourceLoggerForwarderServiceTests to ensure the ResourceLoggerForwarderService has started before signalling the stopping token. --- .../ResourceLoggerForwarderServiceTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs index 2e1084139c8..d10d3e2775f 100644 --- a/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs +++ b/tests/Aspire.Hosting.Testing.Tests/ResourceLoggerForwarderServiceTests.cs @@ -35,11 +35,29 @@ public async Task ExecuteDoesNotThrowOperationCanceledWhenAppStoppingTokenSignal var loggerFactory = new NullLoggerFactory(); var resourceLogForwarder = new ResourceLoggerForwarderService(resourceNotificationService, resourceLoggerService, hostEnvironment, loggerFactory); + // use a task to signal when the resourceLogForwarder has started executing + var subscribedTcs = new TaskCompletionSource(); + var subscriberLoop = Task.Run(async () => + { + await foreach (var sub in resourceLoggerService.WatchAnySubscribersAsync(hostApplicationLifetime.ApplicationStopping)) + { + subscribedTcs.TrySetResult(); + return; + } + }); + await resourceLogForwarder.StartAsync(hostApplicationLifetime.ApplicationStopping); Assert.NotNull(resourceLogForwarder.ExecuteTask); Assert.Equal(TaskStatus.WaitingForActivation, resourceLogForwarder.ExecuteTask.Status); + // Publish an update to the resource to kickstart the notification service loop + var myresource = new CustomResource("myresource"); + await resourceNotificationService.PublishUpdateAsync(myresource, snapshot => snapshot with { State = "Running" }); + + // Wait for the log stream to begin + await subscribedTcs.Task.WaitAsync(TimeSpan.FromSeconds(15)); + // Signal the stopping token hostApplicationLifetime.StopApplication();