| File: tests\Shared\DistributedApplicationTestingBuilderExtensions.cs | Web Access |
| Project: src\tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj (Aspire.Hosting.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Hosting.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Xunit; namespace Aspire.Hosting.Utils; /// <summary> /// Extensions for <see cref="IDistributedApplicationTestingBuilder"/>. /// </summary> public static class DistributedApplicationTestingBuilderExtensions { // Returns the unique prefix used for volumes from unnamed volumes this builder public static string GetVolumePrefix(this IDistributedApplicationTestingBuilder builder) => $"{VolumeNameGenerator.Sanitize(builder.Environment.ApplicationName).ToLowerInvariant()}-{builder.Configuration["AppHost:Sha256"]!.ToLowerInvariant()[..10]}"; public static T WithTestAndResourceLogging<T>(this T builder, ITestOutputHelper testOutputHelper) where T : IDistributedApplicationBuilder { builder.Services.AddTestAndResourceLogging(testOutputHelper, builder.Configuration, builder.Environment.ApplicationName, isPublishMode: builder.ExecutionContext.IsPublishMode); return builder; } public static IServiceCollection AddTestAndResourceLogging(this IServiceCollection services, ITestOutputHelper testOutputHelper, IConfigurationManager configuration, string? applicationName = null, bool isPublishMode = false) { services.AddXunitLogging(testOutputHelper); services.AddLogging(builder => { builder.AddFilter("Aspire.Hosting", LogLevel.Trace); // Suppress all console logging during tests to reduce noise builder.AddFilter<ConsoleLoggerProvider>(null, LogLevel.None); }); if (!isPublishMode) { services.AddDcpDiagnostics(configuration, applicationName, testOutputHelper); } return services; } public static IDistributedApplicationTestingBuilder WithTempAspireStore(this IDistributedApplicationTestingBuilder builder, string? path = null) { // We create the Aspire Store in a folder with user-only access. This way non-root containers won't be able // to access the files unless they correctly assign the required permissions for the container to work. builder.Configuration["Aspire:Store:Path"] = path ?? Directory.CreateTempSubdirectory().FullName; return builder; } public static IDistributedApplicationTestingBuilder WithResourceCleanUp(this IDistributedApplicationTestingBuilder builder, bool? resourceCleanup = null) { builder.Configuration["DcpPublisher:WaitForResourceCleanup"] = resourceCleanup.ToString(); return builder; } private static IServiceCollection AddDcpDiagnostics(this IServiceCollection services, IConfigurationManager configuration, string? applicationName, ITestOutputHelper testOutputHelper) { // Use Aspire:Test:DcpLogBasePath as the base path (set externally, e.g., in CI via env var ASPIRE__TEST__DCPLOGBASEPATH) var baseDcpLogFolder = configuration["Aspire:Test:DcpLogBasePath"]; if (!string.IsNullOrEmpty(baseDcpLogFolder)) { var uniqueId = Guid.NewGuid().ToString("N")[..8]; var folderName = !string.IsNullOrEmpty(applicationName) ? $"{VolumeNameGenerator.Sanitize(applicationName).ToLowerInvariant()}-{uniqueId}" : uniqueId; var uniqueFolder = Path.Combine(baseDcpLogFolder, folderName); configuration["DcpPublisher:DiagnosticsLogFolder"] = uniqueFolder; configuration["DcpPublisher:DiagnosticsLogLevel"] = "debug"; configuration["DcpPublisher:PreserveExecutableLogs"] = "true"; // Register as hosted service to forward DCP logs to test output when app stops services.AddSingleton<IHostedService>(sp => new DcpLogForwarder(testOutputHelper, uniqueFolder)); } return services; } /// <summary> /// Adds xunit logging and suppresses console logging for a host application builder used in tests. /// This redirects logs to the xunit test output and prevents console clutter during test runs. /// </summary> public static IHostApplicationBuilder AddTestLogging(this IHostApplicationBuilder builder, ITestOutputHelper testOutputHelper) { builder.Logging.AddXunit(testOutputHelper); builder.Logging.AddFilter<ConsoleLoggerProvider>(null, LogLevel.None); return builder; } } /// <summary> /// Forwards DCP log files to xUnit test output when stopped. /// Implements IHostedService so it gets automatically resolved and stopped when the app shuts down. /// </summary> /// <remarks> /// DCP is not started in publish mode, so no logs will be available. /// </remarks> internal sealed class DcpLogForwarder : IHostedService { private readonly ITestOutputHelper _testOutputHelper; private readonly string _logFolder; public DcpLogForwarder(ITestOutputHelper testOutputHelper, string logFolder) { _testOutputHelper = testOutputHelper; _logFolder = logFolder; } public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; public async Task StopAsync(CancellationToken cancellationToken) { if (!Directory.Exists(_logFolder)) { _testOutputHelper.WriteLine($"DCP log folder not found: {_logFolder}"); return; } foreach (var logFile in Directory.GetFiles(_logFolder, "*.log")) { try { _testOutputHelper.WriteLine($"=== DCP Log: {Path.GetFileName(logFile)} ==="); var content = await File.ReadAllTextAsync(logFile, cancellationToken); _testOutputHelper.WriteLine(content); } catch (Exception ex) { _testOutputHelper.WriteLine($"Failed to read DCP log {logFile}: {ex.Message}"); } } } }