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/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/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.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 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(); diff --git a/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs b/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs index 86f7622be67..1c06ea8f482 100644 --- a/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs +++ b/tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelTests.cs @@ -28,16 +28,13 @@ 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)); // 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)); @@ -71,25 +68,22 @@ 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)); // 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)); @@ -116,16 +110,13 @@ 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)); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -154,16 +145,13 @@ 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)); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -198,16 +186,13 @@ 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)); // 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 @@ -245,16 +230,13 @@ 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)); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -275,10 +257,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 @@ -294,22 +276,19 @@ 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); // 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}"); @@ -328,16 +307,13 @@ 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); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -372,16 +348,13 @@ 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); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -412,16 +385,13 @@ 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); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -464,16 +434,13 @@ 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); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -505,16 +472,13 @@ 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); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client @@ -551,16 +515,13 @@ 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); // Get the service var service = app.Services.GetRequiredService(); + await service.ListeningTask.WaitAsync(TimeSpan.FromSeconds(60)); Assert.NotNull(service.SocketPath); // Connect a client 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