From a0eb7479508b7239979c8da7268b9ab5d8ca290b Mon Sep 17 00:00:00 2001 From: Damian Edwards Date: Fri, 2 Aug 2024 12:35:22 -0700 Subject: [PATCH] Use 8.1 testing features --- .../DistributedApplicationExtensions.cs | 61 ----- .../Infrastructure/LoggerLogStore.cs | 38 --- .../Infrastructure/ResourceLogStore.cs | 75 ------ .../Infrastructure/ResourceWatcher.cs | 228 ------------------ .../Infrastructure/StoredLogsLogger.cs | 26 -- .../StoredLogsLoggerProvider.cs | 23 -- .../Infrastructure/XUnitExtensions.cs | 40 --- .../Infrastructure/XUnitLogger.cs | 57 ----- .../Infrastructure/XUnitLoggerProvider.cs | 24 -- .../Infrastructure/XUnitTextWriter.cs | 33 --- src/IntegrationTests/IntegrationTests.csproj | 1 + src/IntegrationTests/WebAppTests.cs | 11 +- .../eShop.ServiceDefaults.csproj | 2 +- 13 files changed, 11 insertions(+), 608 deletions(-) delete mode 100644 src/IntegrationTests/Infrastructure/LoggerLogStore.cs delete mode 100644 src/IntegrationTests/Infrastructure/ResourceLogStore.cs delete mode 100644 src/IntegrationTests/Infrastructure/ResourceWatcher.cs delete mode 100644 src/IntegrationTests/Infrastructure/StoredLogsLogger.cs delete mode 100644 src/IntegrationTests/Infrastructure/StoredLogsLoggerProvider.cs delete mode 100644 src/IntegrationTests/Infrastructure/XUnitExtensions.cs delete mode 100644 src/IntegrationTests/Infrastructure/XUnitLogger.cs delete mode 100644 src/IntegrationTests/Infrastructure/XUnitLoggerProvider.cs delete mode 100644 src/IntegrationTests/Infrastructure/XUnitTextWriter.cs diff --git a/src/IntegrationTests/Infrastructure/DistributedApplicationExtensions.cs b/src/IntegrationTests/Infrastructure/DistributedApplicationExtensions.cs index a885404..2765d9b 100644 --- a/src/IntegrationTests/Infrastructure/DistributedApplicationExtensions.cs +++ b/src/IntegrationTests/Infrastructure/DistributedApplicationExtensions.cs @@ -7,46 +7,12 @@ using System.Security.Cryptography; using IntegrationTests.Infrastructure; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace IntegrationTests.Infrastructure; public static partial class DistributedApplicationExtensions { - internal const string OutputWriterKey = $"{nameof(DistributedApplicationExtensions)}.OutputWriter"; - - /// - /// Adds a background service to watch resource status changes and optionally logs. - /// - public static IServiceCollection AddResourceWatching(this IServiceCollection services) - { - // Add background service to watch resource status changes and optionally logs - services.AddSingleton(); - services.AddHostedService(sp => sp.GetRequiredService()); - - return services; - } - - /// - /// Configures the builder to write logs to the supplied and store for optional assertion later. - /// - public static TBuilder WriteOutputTo(this TBuilder builder, TextWriter outputWriter) - where TBuilder : IDistributedApplicationTestingBuilder - { - builder.Services.AddResourceWatching(); - - // Add a resource log store to capture logs from resources - builder.Services.AddSingleton(); - - // Configure the builder's logger to redirect it output & store for assertion later - builder.Services.AddKeyedSingleton(OutputWriterKey, outputWriter); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - return builder; - } - /// /// Ensures all parameters in the application configuration have values set. /// @@ -149,33 +115,6 @@ public static HttpClient CreateHttpClient(this DistributedApplication app, strin return httpClient; } - /// - public static async Task StartAsync(this DistributedApplication app, bool waitForResourcesToStart, CancellationToken cancellationToken = default) - { - var resourceWatcher = app.Services.GetRequiredService(); - var resourcesStartingTask = waitForResourcesToStart ? resourceWatcher.WaitForResourcesToStart() : Task.CompletedTask; - - await app.StartAsync(cancellationToken); - await resourcesStartingTask; - } - - public static LoggerLogStore GetAppHostLogs(this DistributedApplication app) - { - var logStore = app.Services.GetService() - ?? throw new InvalidOperationException($"Log store service was not registered. Ensure the '{nameof(WriteOutputTo)}' method is called before attempting to get AppHost logs."); - return logStore; - } - - /// - /// Gets the logs for all resources in the application. - /// - public static ResourceLogStore GetResourceLogs(this DistributedApplication app) - { - var logStore = app.Services.GetService() - ?? throw new InvalidOperationException($"Log store service was not registered. Ensure the '{nameof(WriteOutputTo)}' method is called before attempting to get resource logs."); ; - return logStore; - } - /// /// Attempts to apply EF migrations for the specified project by sending a request to the migrations endpoint /ApplyDatabaseMigrations. /// diff --git a/src/IntegrationTests/Infrastructure/LoggerLogStore.cs b/src/IntegrationTests/Infrastructure/LoggerLogStore.cs deleted file mode 100644 index 3d850dc..0000000 --- a/src/IntegrationTests/Infrastructure/LoggerLogStore.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Concurrent; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace IntegrationTests.Infrastructure; - -/// -/// Stores logs from instances created from . -/// -public class LoggerLogStore(IHostEnvironment hostEnvironment) -{ - private readonly ConcurrentDictionary> _store = []; - - public void AddLog(string category, LogLevel level, string message, Exception? exception) - { - _store.GetOrAdd(category, _ => []).Add((DateTimeOffset.Now, category, level, message, exception)); - } - - public IReadOnlyDictionary> GetLogs() - { - return _store.ToDictionary(entry => entry.Key, entry => (IList<(DateTimeOffset, string, LogLevel, string, Exception?)>)entry.Value); - } - - public void EnsureNoErrors() - { - var logs = GetLogs(); - - var errors = logs.SelectMany(kvp => kvp.Value).Where(log => log.Level == LogLevel.Error || log.Level == LogLevel.Critical).ToList(); - //Where(category => category.Value.Any(log => log.Level == LogLevel.Error || log.Level == LogLevel.Critical)).ToList(); - if (errors.Count > 0) - { - var appName = hostEnvironment.ApplicationName; - throw new InvalidOperationException( - $"AppHost '{appName}' logged errors: {Environment.NewLine}" + - string.Join(Environment.NewLine, errors.Select(log => $"[{log.Category}] {log.Message}"))); - } - } -} diff --git a/src/IntegrationTests/Infrastructure/ResourceLogStore.cs b/src/IntegrationTests/Infrastructure/ResourceLogStore.cs deleted file mode 100644 index 14b3ba8..0000000 --- a/src/IntegrationTests/Infrastructure/ResourceLogStore.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Concurrent; - -namespace IntegrationTests.Infrastructure; - -public class ResourceLogStore -{ - private readonly ConcurrentDictionary> _logs = []; - - internal void Add(IResource resource, IEnumerable logs) - { - _logs.GetOrAdd(resource, _ => []).AddRange(logs); - } - - /// - /// Gets a snapshot of the logs for all resources. - /// - public IReadOnlyDictionary> GetLogs() => - _logs.ToDictionary(entry => entry.Key, entry => (IReadOnlyList)entry.Value); - - /// - /// Gets the logs for the specified resource in the application. - /// - public IReadOnlyList GetLogs(string resourceName) - { - var resource = _logs.Keys.FirstOrDefault(k => string.Equals(k.Name, resourceName, StringComparison.OrdinalIgnoreCase)); - if (resource is not null && _logs.TryGetValue(resource, out var logs)) - { - return logs; - } - return []; - } - - /// - /// Ensures no errors were logged for the specified resource. - /// - public void EnsureNoErrors(string resourceName) - { - EnsureNoErrors(r => string.Equals(r.Name, resourceName, StringComparison.OrdinalIgnoreCase)); - } - - /// - /// Ensures no errors were logged for the specified resources. - /// - public void EnsureNoErrors(Func? resourcePredicate = null, bool throwIfNoResourcesMatch = false) - { - var logStore = GetLogs(); - - var resourcesMatched = 0; - foreach (var (resource, logs) in logStore) - { - if (resourcePredicate is null || resourcePredicate(resource)) - { - EnsureNoErrors(resource, logs); - resourcesMatched++; - } - } - - if (throwIfNoResourcesMatch && resourcesMatched == 0 && resourcePredicate is not null) - { - throw new ArgumentException("No resources matched the predicate.", nameof(resourcePredicate)); - } - - static void EnsureNoErrors(IResource resource, IEnumerable logs) - { - var errors = logs.Where(l => l.IsErrorMessage).ToList(); - if (errors.Count > 0) - { - throw new InvalidOperationException($"Resource '{resource.Name}' logged errors: {Environment.NewLine}{string.Join(Environment.NewLine, errors.Select(e => e.Content))}"); - } - } - } -} diff --git a/src/IntegrationTests/Infrastructure/ResourceWatcher.cs b/src/IntegrationTests/Infrastructure/ResourceWatcher.cs deleted file mode 100644 index 10472a8..0000000 --- a/src/IntegrationTests/Infrastructure/ResourceWatcher.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace IntegrationTests.Infrastructure; - -/// -/// A background service that watches for resource start/stop notifications and logs resource state changes. -/// -internal sealed class ResourceWatcher( - DistributedApplicationModel appModel, - ResourceNotificationService resourceNotification, - ResourceLoggerService resourceLoggerService, - IServiceProvider serviceProvider, - ILogger logger) - : BackgroundService -{ - private readonly HashSet _waitingToStartResources = new(StringComparer.OrdinalIgnoreCase); - private readonly HashSet _startedResources = new(StringComparer.OrdinalIgnoreCase); - private readonly HashSet _waitingToStopResources = new(StringComparer.OrdinalIgnoreCase); - private readonly HashSet _stoppedResources = new(StringComparer.OrdinalIgnoreCase); - private readonly Dictionary _resourceState = []; - private readonly TaskCompletionSource _resourcesStartedTcs = new(); - private readonly TaskCompletionSource _resourcesStoppedTcs = new(); - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - logger.LogInformation("Resource watcher started"); - - var statusWatchableResources = GetStatusWatchableResources().ToList(); - statusWatchableResources.ForEach(r => - { - _waitingToStartResources.Add(r.Name); - _waitingToStopResources.Add(r.Name); - }); - logger.LogInformation("Watching {resourceCount} resources for start/stop changes", statusWatchableResources.Count); - - // We need to pass the stopping token in here because the ResourceNotificationService doesn't stop on host shutdown in preview.5 - await WatchNotifications(stoppingToken); - - logger.LogInformation("Resource watcher stopped"); - } - - public override void Dispose() - { - _resourcesStartedTcs.TrySetException(new DistributedApplicationException("Resource watcher was disposed while waiting for resources to start, likely due to a timeout")); - _resourcesStoppedTcs.TrySetException(new DistributedApplicationException("Resource watcher was disposed while waiting for resources to stop, likely due to a timeout")); - } - - public Task WaitForResourcesToStart() => _resourcesStartedTcs.Task; - - public Task WaitForResourcesToStop() => _resourcesStoppedTcs.Task; - - private async Task WatchNotifications(CancellationToken cancellationToken) - { - var logStore = serviceProvider.GetService(); - var outputWriter = serviceProvider.GetKeyedService(DistributedApplicationExtensions.OutputWriterKey); - var watchingLogs = logStore is not null || outputWriter is not null; - var loggingResourceIds = new HashSet(); - var logWatchTasks = new List(); - - logger.LogInformation("Waiting on {resourcesToStartCount} resources to start", _waitingToStartResources.Count); - - await foreach (var resourceEvent in resourceNotification.WatchAsync().WithCancellation(cancellationToken)) - { - var resourceName = resourceEvent.Resource.Name; - var resourceId = resourceEvent.ResourceId; - - if (watchingLogs && loggingResourceIds.Add(resourceId)) - { - // Start watching the logs for this resource ID - logWatchTasks.Add(WatchResourceLogs(logStore, outputWriter, resourceEvent.Resource, resourceId, cancellationToken)); - } - - _resourceState.TryGetValue(resourceName, out var prevState); - _resourceState[resourceName] = (resourceEvent.Snapshot.State, resourceEvent.Snapshot.ExitCode); - - if (resourceEvent.Snapshot.ExitCode is null && resourceEvent.Snapshot.State is { } newState && !string.IsNullOrEmpty(newState.Text)) - { - if (!string.Equals(prevState.Snapshot?.Text, newState.Text, StringComparison.OrdinalIgnoreCase)) - { - // Log resource state change - logger.LogInformation("Resource '{resourceName}' of type '{resourceType}' changed state: {oldState} -> {newState}", resourceId, resourceEvent.Resource.GetType().Name, prevState.Snapshot?.Text ?? "[null]", newState.Text); - - if (newState.Text.Contains("running", StringComparison.OrdinalIgnoreCase)) - { - // Resource started - HandleResourceStarted(resourceEvent.Resource); - } - else if (newState.Text.Contains("failedtostart", StringComparison.OrdinalIgnoreCase)) - { - // Resource failed to start - HandleResourceStartError(resourceName, $"Resource '{resourceName}' failed to start: {newState.Text}"); - } - else if (newState.Text.Contains("exited", StringComparison.OrdinalIgnoreCase)) - { - if (_waitingToStartResources.Contains(resourceEvent.Resource.Name)) - { - // Resource went straight to exited state - HandleResourceStartError(resourceName, $"Resource '{resourceName}' exited without first running: {newState.Text}"); - } - - // Resource stopped - HandleResourceStopped(resourceEvent.Resource); - } - else if (newState.Text.Contains("starting", StringComparison.OrdinalIgnoreCase) - || newState.Text.Contains("hidden", StringComparison.OrdinalIgnoreCase)) - { - // Resource is still starting - } - else if (!string.IsNullOrEmpty(newState.Text)) - { - logger.LogWarning("Unknown resource state encountered: {state}", newState.Text); - } - } - } - else if (resourceEvent.Snapshot.ExitCode is { } exitCode) - { - if (exitCode != 0) - { - if (_waitingToStartResources.Remove(resourceName)) - { - // Error starting resource - HandleResourceStartError(resourceName, $"Resource '{resourceName}' exited with exit code {exitCode}"); - } - HandleResourceStopError(resourceName, $"Resource '{resourceName}' exited with exit code {exitCode}"); - } - else - { - // Resource exited cleanly - HandleResourceStarted(resourceEvent.Resource, " (exited with code 0)"); - HandleResourceStopped(resourceEvent.Resource, " (exited with code 0)"); - } - } - - if (_waitingToStartResources.Count == 0) - { - logger.LogInformation("All resources started"); - _resourcesStartedTcs.TrySetResult(); - } - - if (_waitingToStopResources.Count == 0) - { - logger.LogInformation("All resources stopped"); - _resourcesStoppedTcs.TrySetResult(); - } - } - - void HandleResourceStartError(string resourceName, string message) - { - if (_waitingToStartResources.Remove(resourceName)) - { - _resourcesStartedTcs.TrySetException(new DistributedApplicationException(message)); - } - _waitingToStopResources.Remove(resourceName); - _stoppedResources.Add(resourceName); - } - - void HandleResourceStopError(string resourceName, string message) - { - _waitingToStartResources.Remove(resourceName); - if (_waitingToStopResources.Remove(resourceName)) - { - _resourcesStoppedTcs.TrySetException(new DistributedApplicationException(message)); - } - _stoppedResources.Add(resourceName); - } - - void HandleResourceStarted(IResource resource, string? suffix = null) - { - if (_waitingToStartResources.Remove(resource.Name) && _startedResources.Add(resource.Name)) - { -#pragma warning disable CA2254 // Template should be a static expression: suffix is not log data - logger.LogInformation($"Resource '{{resourceName}}' started{suffix}", resource.Name); -#pragma warning restore CA2254 - } - - if (_waitingToStartResources.Count > 0) - { - var resourceNames = string.Join(", ", _waitingToStartResources.Select(r => r)); - logger.LogInformation("Still waiting on {resourcesToStartCount} resources to start: {resourcesToStart}", _waitingToStartResources.Count, resourceNames); - } - } - - void HandleResourceStopped(IResource resource, string? suffix = null) - { - if (_waitingToStopResources.Remove(resource.Name) && _stoppedResources.Add(resource.Name)) - { -#pragma warning disable CA2254 // Template should be a static expression: suffix is not log data - logger.LogInformation($"Resource '{{resourceName}}' stopped{suffix}", resource.Name); -#pragma warning restore CA2254 - } - - if (_waitingToStopResources.Count > 0) - { - var resourceNames = string.Join(", ", _waitingToStopResources.Select(r => r)); - logger.LogInformation("Still waiting on {resourcesToStartCount} resources to stop: {resourcesToStart}", _waitingToStopResources.Count, resourceNames); - } - } - - await Task.WhenAll(logWatchTasks); - } - - private async Task WatchResourceLogs(ResourceLogStore? logStore, TextWriter? outputWriter, IResource resource, string resourceId, CancellationToken cancellationToken) - { - if (logStore is not null || outputWriter is not null) - { - await foreach (var logEvent in resourceLoggerService.WatchAsync(resourceId).WithCancellation(cancellationToken)) - { - logStore?.Add(resource, logEvent); - - foreach (var line in logEvent) - { - var kind = line.IsErrorMessage ? "error" : "log"; - outputWriter?.WriteLine("{0} Resource '{1}' {2}: {3}", DateTime.Now.ToString("O"), resource.Name, kind, line.Content); - } - } - } - } - - private IEnumerable GetStatusWatchableResources() - { - return appModel.Resources.Where(r => r is ContainerResource || r is ExecutableResource || r is ProjectResource); - } -} diff --git a/src/IntegrationTests/Infrastructure/StoredLogsLogger.cs b/src/IntegrationTests/Infrastructure/StoredLogsLogger.cs deleted file mode 100644 index a513c8b..0000000 --- a/src/IntegrationTests/Infrastructure/StoredLogsLogger.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Logging; - -namespace IntegrationTests.Infrastructure; - -/// -/// A logger that stores logs in a . -/// -/// -/// -/// -internal class StoredLogsLogger(LoggerLogStore logStore, LoggerExternalScopeProvider scopeProvider, string categoryName) : ILogger -{ - public string CategoryName { get; } = categoryName; - - public IDisposable? BeginScope(TState state) where TState : notnull => scopeProvider.Push(state); - - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - logStore.AddLog(CategoryName, logLevel, formatter(state, exception), exception); - } -} diff --git a/src/IntegrationTests/Infrastructure/StoredLogsLoggerProvider.cs b/src/IntegrationTests/Infrastructure/StoredLogsLoggerProvider.cs deleted file mode 100644 index 0b4c796..0000000 --- a/src/IntegrationTests/Infrastructure/StoredLogsLoggerProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Logging; - -namespace IntegrationTests.Infrastructure; - -/// -/// A logger provider that stores logs in an . -/// -internal class StoredLogsLoggerProvider(LoggerLogStore logStore) : ILoggerProvider -{ - private readonly LoggerExternalScopeProvider _scopeProvider = new(); - - public ILogger CreateLogger(string categoryName) - { - return new StoredLogsLogger(logStore, _scopeProvider, categoryName); - } - - public void Dispose() - { - } -} diff --git a/src/IntegrationTests/Infrastructure/XUnitExtensions.cs b/src/IntegrationTests/Infrastructure/XUnitExtensions.cs deleted file mode 100644 index 29d9f0c..0000000 --- a/src/IntegrationTests/Infrastructure/XUnitExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Xunit.Abstractions; - -namespace IntegrationTests.Infrastructure; - -internal static partial class DistributedApplicationTestFactory -{ - ///// - ///// Creates an for the specified app host assembly and outputs logs to the provided . - ///// - //public static async Task CreateAsync(string appHostAssemblyPath, ITestOutputHelper testOutputHelper) - //{ - // var builder = await CreateAsync(appHostAssemblyPath, new XUnitTextWriter(testOutputHelper)); - // builder.Services.AddSingleton(); - // builder.Services.AddSingleton(testOutputHelper); - // return builder; - //} - - /// - /// Writes messages and resource logs to the provided . - /// - /// The builder. - /// The output. - /// The builder. - public static IDistributedApplicationTestingBuilder WriteOutputTo(this IDistributedApplicationTestingBuilder builder, ITestOutputHelper testOutputHelper) - { - // Enable the core ILogger and resource output capturing - builder.WriteOutputTo(new XUnitTextWriter(testOutputHelper)); - - // Enable ILogger going to xUnit output - builder.Services.AddSingleton(testOutputHelper); - builder.Services.AddSingleton(); - - return builder; - } -} diff --git a/src/IntegrationTests/Infrastructure/XUnitLogger.cs b/src/IntegrationTests/Infrastructure/XUnitLogger.cs deleted file mode 100644 index ab5b1ea..0000000 --- a/src/IntegrationTests/Infrastructure/XUnitLogger.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text; -using Microsoft.Extensions.Logging; -using Xunit.Abstractions; - -namespace IntegrationTests.Infrastructure; - -/// -/// An that writes log messages to an . -/// -internal class XUnitLogger(ITestOutputHelper testOutputHelper, LoggerExternalScopeProvider scopeProvider, string categoryName) : ILogger -{ - public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; - - public IDisposable? BeginScope(TState state) where TState : notnull => scopeProvider.Push(state); - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - var sb = new StringBuilder(); - - sb.Append(DateTime.Now.ToString("O")).Append(' ') - .Append(GetLogLevelString(logLevel)) - .Append(" [").Append(categoryName).Append("] ") - .Append(formatter(state, exception)); - - if (exception is not null) - { - sb.AppendLine().Append(exception); - } - - // Append scopes - scopeProvider.ForEachScope((scope, state) => - { - state.AppendLine(); - state.Append(" => "); - state.Append(scope); - }, sb); - - testOutputHelper.WriteLine(sb.ToString()); - } - - private static string GetLogLevelString(LogLevel logLevel) - { - return logLevel switch - { - LogLevel.Trace => "trce", - LogLevel.Debug => "dbug", - LogLevel.Information => "info", - LogLevel.Warning => "warn", - LogLevel.Error => "fail", - LogLevel.Critical => "crit", - _ => throw new ArgumentOutOfRangeException(nameof(logLevel)) - }; - } -} diff --git a/src/IntegrationTests/Infrastructure/XUnitLoggerProvider.cs b/src/IntegrationTests/Infrastructure/XUnitLoggerProvider.cs deleted file mode 100644 index 235f351..0000000 --- a/src/IntegrationTests/Infrastructure/XUnitLoggerProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.Logging; -using Xunit.Abstractions; - -namespace IntegrationTests.Infrastructure; - -/// -/// An that creates instances that output to the supplied . -/// -internal class XUnitLoggerProvider(ITestOutputHelper testOutputHelper) : ILoggerProvider -{ - private readonly LoggerExternalScopeProvider _scopeProvider = new(); - - public ILogger CreateLogger(string categoryName) - { - return new XUnitLogger(testOutputHelper, _scopeProvider, categoryName); - } - - public void Dispose() - { - } -} diff --git a/src/IntegrationTests/Infrastructure/XUnitTextWriter.cs b/src/IntegrationTests/Infrastructure/XUnitTextWriter.cs deleted file mode 100644 index 31a3046..0000000 --- a/src/IntegrationTests/Infrastructure/XUnitTextWriter.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Text; -using Xunit.Abstractions; - -namespace IntegrationTests.Infrastructure; - -/// -/// A that writes to an . -/// -internal class XUnitTextWriter(ITestOutputHelper output) : TextWriter -{ - private readonly StringBuilder _sb = new(); - - public override Encoding Encoding => Encoding.Unicode; - - public override void Write(char value) - { - if (value == '\r' || value == '\n') - { - if (_sb.Length > 0) - { - output.WriteLine(_sb.ToString()); - _sb.Clear(); - } - } - else - { - _sb.Append(value); - } - } -} diff --git a/src/IntegrationTests/IntegrationTests.csproj b/src/IntegrationTests/IntegrationTests.csproj index e8722bc..c211edd 100644 --- a/src/IntegrationTests/IntegrationTests.csproj +++ b/src/IntegrationTests/IntegrationTests.csproj @@ -17,6 +17,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/IntegrationTests/WebAppTests.cs b/src/IntegrationTests/WebAppTests.cs index f4089a7..a676f7a 100644 --- a/src/IntegrationTests/WebAppTests.cs +++ b/src/IntegrationTests/WebAppTests.cs @@ -1,6 +1,7 @@ using System.Net; using IntegrationTests.Infrastructure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Xunit.Abstractions; namespace IntegrationTests; @@ -11,7 +12,9 @@ public class WebAppTests(ITestOutputHelper outputHelper) public async Task GetWebAppUrlsReturnsOkStatusCode() { var appHostBuilder = await DistributedApplicationTestingBuilder.CreateAsync(); - appHostBuilder.WriteOutputTo(outputHelper); + + appHostBuilder.Services.AddLogging(logging => logging.AddXUnit(outputHelper)); + appHostBuilder.WithRandomParameterValues(); appHostBuilder.WithRandomVolumeNames(); @@ -30,7 +33,11 @@ public async Task GetWebAppUrlsReturnsOkStatusCode() }); await using var app = await appHostBuilder.BuildAsync(); - await app.StartAsync(waitForResourcesToStart: true); + + var notificationService = app.Services.GetRequiredService(); + await notificationService.WaitForResourceAsync("webapp").WaitAsync(TimeSpan.FromSeconds(30)); + + await app.StartAsync(); var httpClient = app.CreateHttpClient("webapp"); diff --git a/src/eShop.ServiceDefaults/eShop.ServiceDefaults.csproj b/src/eShop.ServiceDefaults/eShop.ServiceDefaults.csproj index cfd3f49..bb4d68f 100644 --- a/src/eShop.ServiceDefaults/eShop.ServiceDefaults.csproj +++ b/src/eShop.ServiceDefaults/eShop.ServiceDefaults.csproj @@ -17,7 +17,7 @@ - +