Skip to content

Commit

Permalink
[tests] Add resource logging to EndToEnd tests (dotnet#5328)
Browse files Browse the repository at this point in the history
* [tests] Add resource logging to EndToEnd tests

* ResourceLoggerForwarderService: don't propogate TaskCanceledException when the token was canceled

It caused failures like:
```
  Failed Aspire.Hosting.Tests.ManifestGenerationTests.PublishingRedisResourceAsContainerResultsInConnectionStringProperty [70 ms]
  Error Message:
   System.AggregateException : One or more errors occurred. (A task was canceled.)
---- System.Threading.Tasks.TaskCanceledException : A task was canceled.
  Stack Trace:
     at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Aspire.Hosting.DistributedApplication.Run() in /_/src/Aspire.Hosting/DistributedApplication.cs:line 339
   at TestProgram.Run() in /_/tests/testproject/TestProject.AppHost/TestProgram.cs:line 150
   at Aspire.Hosting.Tests.ManifestGenerationTests.PublishingRedisResourceAsContainerResultsInConnectionStringProperty() in /_/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs:line 258
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
----- Inner Stack Trace -----
   at System.Threading.Tasks.Task.GetExceptions(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Aspire.Hosting.DistributedApplication.Run() in /_/src/Aspire.Hosting/DistributedApplication.cs:line 339
   at TestProgram.Run() in /_/tests/testproject/TestProject.AppHost/TestProgram.cs:line 150
   at Aspire.Hosting.Tests.ManifestGenerationTests.PublishingRedisResourceAsContainerResultsInConnectionStringProperty() in /_/tests/Aspire.Hosting.Tests/ManifestGenerationTests.cs:line 258
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
   at System.Threading.Tasks.Task.ExecuteEntry()
   at System.Threading.Tasks.SynchronizationContextTaskScheduler.<>c.<.cctor>b__8_0(Object s)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location ---
```
  • Loading branch information
radical committed Aug 17, 2024
1 parent 16c8d49 commit 0f2931a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 20 deletions.
54 changes: 34 additions & 20 deletions src/Aspire.Hosting.Testing/ResourceLoggerForwarderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,54 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken)

private async Task WatchNotifications(CancellationToken cancellationToken)
{
var loggingResourceIds = new HashSet<string>();
var logWatchTasks = new List<Task>();

await foreach (var resourceEvent in resourceNotificationService.WatchAsync(cancellationToken).ConfigureAwait(false))
try
{
var resourceId = resourceEvent.ResourceId;
var loggingResourceIds = new HashSet<string>();
var logWatchTasks = new List<Task>();

if (loggingResourceIds.Add(resourceId))
await foreach (var resourceEvent in resourceNotificationService.WatchAsync(cancellationToken).ConfigureAwait(false))
{
// Start watching the logs for this resource ID
logWatchTasks.Add(WatchResourceLogs(resourceEvent.Resource, resourceId, cancellationToken));
var resourceId = resourceEvent.ResourceId;

if (loggingResourceIds.Add(resourceId))
{
// Start watching the logs for this resource ID
logWatchTasks.Add(WatchResourceLogs(resourceEvent.Resource, resourceId, cancellationToken));
}
}
}

await Task.WhenAll(logWatchTasks).ConfigureAwait(false);
await Task.WhenAll(logWatchTasks).ConfigureAwait(false);
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
// this was expected as the token was canceled
}
}

private async Task WatchResourceLogs(IResource resource, string resourceId, CancellationToken cancellationToken)
{
var applicationName = hostEnvironment.ApplicationName;
var logger = loggerFactory.CreateLogger($"{applicationName}.Resources.{resource.Name}");
await foreach (var logEvent in resourceLoggerService.WatchAsync(resourceId).WithCancellation(cancellationToken).ConfigureAwait(false))
try
{
foreach (var line in logEvent)
var applicationName = hostEnvironment.ApplicationName;
var logger = loggerFactory.CreateLogger($"{applicationName}.Resources.{resource.Name}");
await foreach (var logEvent in resourceLoggerService.WatchAsync(resourceId).WithCancellation(cancellationToken).ConfigureAwait(false))
{
var logLevel = line.IsErrorMessage ? LogLevel.Error : LogLevel.Information;

if (logger.IsEnabled(logLevel))
foreach (var line in logEvent)
{
// Log message format here approximates the format shown in the dashboard
logger.Log(logLevel, "{LineNumber}: {LineContent}", line.LineNumber, line.Content);
OnResourceLog?.Invoke(resourceId);
var logLevel = line.IsErrorMessage ? LogLevel.Error : LogLevel.Information;

if (logger.IsEnabled(logLevel))
{
// Log message format here approximates the format shown in the dashboard
logger.Log(logLevel, "{LineNumber}: {LineContent}", line.LineNumber, line.Content);
OnResourceLog?.Invoke(resourceId);
}
}
}
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
// this was expected as the token was canceled
}
}
}
2 changes: 2 additions & 0 deletions tests/Aspire.EndToEnd.Tests/Aspire.EndToEnd.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

<ItemGroup Condition="'$(_BuildForTestsRunningOutsideOfRepo)' == 'true'">
<None Include="..\testproject\**\*" Link="$(DeployOutsideOfRepoSupportFilesDir)%(RecursiveDir)%(FileName)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
<!-- Needed by TestProject.AppHost -->
<None Include="$(RepoRoot)src\Aspire.Hosting.Testing\ResourceLoggerForwarderService.cs" Link="$(DeployOutsideOfRepoSupportFilesDir)TestProject.AppHost\ResourceLoggerForwarderService.cs" CopyToOutputDirectory="PreserveNewest" />
<None Include="..\.editorconfig" Link="$(DeployOutsideOfRepoSupportFilesDir)..\%(FileName)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

Expand Down
2 changes: 2 additions & 0 deletions tests/testproject/TestProject.AppHost/TestProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.Testing;
using Aspire.TestProject;
using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -101,6 +102,7 @@ private TestProgram(
}
}

AppBuilder.Services.AddHostedService<ResourceLoggerForwarderService>();
AppBuilder.Services.AddLifecycleHook<EndPointWriterHook>();
AppBuilder.Services.AddHttpClient();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<ItemGroup>
<Compile Include="..\Common\TestResourceNames.cs" />

<!-- on helix, the file will be in the source directory, so it will get
picked up by msbuild by default -->
<Compile Condition="'$(RepoRoot)' != ''" Include="$(RepoRoot)src\Aspire.Hosting.Testing\ResourceLoggerForwarderService.cs" Link="Utils\ResourceLoggerForwarderService.cs" />

<ProjectReference Include="..\TestProject.IntegrationServiceA\TestProject.IntegrationServiceA.csproj" AspireProjectMetadataTypeName="IntegrationServiceA" />
<ProjectReference Include="..\TestProject.ServiceA\TestProject.ServiceA.csproj" AspireProjectMetadataTypeName="ServiceA" />
<ProjectReference Include="..\TestProject.ServiceB\TestProject.ServiceB.csproj" AspireProjectMetadataTypeName="ServiceB" />
Expand Down

0 comments on commit 0f2931a

Please sign in to comment.