Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</GlobalPackageReference>
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Aspire.Hosting.AppHost" Version="13.2.0" />
<PackageVersion Include="Aspire.Hosting.Testing" Version="13.2.0" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />
Expand Down
6 changes: 6 additions & 0 deletions TUnit.Aspire.Tests.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
var builder = DistributedApplication.CreateBuilder(args);

builder.AddParameter("my-secret", secret: true);
builder.AddContainer("nginx-no-healthcheck", "nginx");

builder.Build().Run();
14 changes: 14 additions & 0 deletions TUnit.Aspire.Tests.AppHost/TUnit.Aspire.Tests.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Aspire.AppHost.Sdk/13.2.0">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<IsAspireHost>true</IsAspireHost>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" />
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions TUnit.Aspire.Tests/TUnit.Aspire.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\TUnit.Engine\TUnit.Engine.props" />

<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\TUnit.Aspire\TUnit.Aspire.csproj" />
<ProjectReference Include="..\TUnit.Engine\TUnit.Engine.csproj" />
<ProjectReference Include="..\TUnit.Assertions\TUnit.Assertions.csproj" />
<ProjectReference Include="..\TUnit.Aspire.Tests.AppHost\TUnit.Aspire.Tests.AppHost.csproj" />
<ProjectReference Include="..\TUnit.Core.SourceGenerator\TUnit.Core.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\TUnit.Analyzers\TUnit.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" />
<PackageReference Include="Microsoft.Testing.Platform.MSBuild" />
</ItemGroup>

</Project>
94 changes: 94 additions & 0 deletions TUnit.Aspire.Tests/WaitForHealthyReproductionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Testing;
using Microsoft.Extensions.DependencyInjection;
using TUnit.Assertions;
using TUnit.Assertions.Extensions;
using TUnit.Core;

namespace TUnit.Aspire.Tests;

/// <summary>
/// Reproduction and fix verification tests for https://github.com/thomhurst/TUnit/issues/5260
/// </summary>
public class WaitForHealthyReproductionTests
{
[Test]
[Category("Docker")]
public async Task ParameterResource_DoesNotImplement_IResourceWithoutLifetime_InAspire13_2(CancellationToken ct)
{
await using var builder = await DistributedApplicationTestingBuilder.CreateAsync<Projects.TUnit_Aspire_Tests_AppHost>();
await using var app = await builder.BuildAsync();

var model = app.Services.GetRequiredService<DistributedApplicationModel>();
var paramResource = model.Resources.First(r => r.Name == "my-secret");

await Assert.That(paramResource is IResourceWithoutLifetime).IsFalse();
}

[Test]
[Category("Docker")]
public async Task ParameterResource_IsNot_IComputeResource(CancellationToken ct)
{
await using var builder = await DistributedApplicationTestingBuilder.CreateAsync<Projects.TUnit_Aspire_Tests_AppHost>();
await using var app = await builder.BuildAsync();

var model = app.Services.GetRequiredService<DistributedApplicationModel>();
var paramResource = model.Resources.First(r => r.Name == "my-secret");
var containerResource = model.Resources.First(r => r.Name == "nginx-no-healthcheck");

await Assert.That(paramResource is IComputeResource).IsFalse();
await Assert.That(containerResource is IComputeResource).IsTrue();
}

[Test]
[Category("Docker")]
public async Task WaitForResourceHealthyAsync_OnParameterResource_Hangs(CancellationToken ct)
{
await using var builder = await DistributedApplicationTestingBuilder.CreateAsync<Projects.TUnit_Aspire_Tests_AppHost>();
await using var app = await builder.BuildAsync();
await app.StartAsync(ct);

var notificationService = app.Services.GetRequiredService<ResourceNotificationService>();

using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(15));

var timedOut = false;
try
{
await notificationService.WaitForResourceHealthyAsync("my-secret", cts.Token);
}
catch (OperationCanceledException)
{
timedOut = true;
}
catch (InvalidOperationException)
{
timedOut = true;
}

await Assert.That(timedOut).IsTrue();
}

[Test]
[Category("Docker")]
public async Task AspireFixture_AllHealthy_Succeeds_AfterFix(CancellationToken ct)
{
var fixture = new HealthyFixture();

try
{
await fixture.InitializeAsync();
}
finally
{
await fixture.DisposeAsync();
}
}

private sealed class HealthyFixture : AspireFixture<Projects.TUnit_Aspire_Tests_AppHost>
{
protected override TimeSpan ResourceTimeout => TimeSpan.FromSeconds(60);
}
}
4 changes: 2 additions & 2 deletions TUnit.Aspire/AspireFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ private List<string> GetWaitableResourceNames(DistributedApplicationModel model)

foreach (var r in model.Resources)
{
if (r is IResourceWithoutLifetime)
if (r is not IComputeResource)
{
skipped ??= [];
skipped.Add(r.Name);
Expand All @@ -543,7 +543,7 @@ private List<string> GetWaitableResourceNames(DistributedApplicationModel model)

if (skipped is { Count: > 0 })
{
LogProgress($"Skipping {skipped.Count} resource(s) without lifecycle: [{string.Join(", ", skipped)}]");
LogProgress($"Skipping {skipped.Count} non-compute resource(s): [{string.Join(", ", skipped)}]");
}

return waitable;
Expand Down
2 changes: 2 additions & 0 deletions TUnit.CI.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
<Project Path="TUnit.RpcTests/TUnit.RpcTests.csproj" />
<Project Path="TUnit.Templates.Tests/TUnit.Templates.Tests.csproj" />
<Project Path="TUnit.UnitTests/TUnit.UnitTests.csproj" />
<Project Path="TUnit.Aspire.Tests/TUnit.Aspire.Tests.csproj" />
<Project Path="TUnit.Aspire.Tests.AppHost/TUnit.Aspire.Tests.AppHost.csproj" />
</Folder>

<!-- Build infrastructure -->
Expand Down
43 changes: 43 additions & 0 deletions TUnit.Pipeline/Modules/RunAspireTestsModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using ModularPipelines.Attributes;
using ModularPipelines.Context;
using ModularPipelines.DotNet.Extensions;
using ModularPipelines.DotNet.Options;
using ModularPipelines.Extensions;
using ModularPipelines.Git.Extensions;
using ModularPipelines.Models;
using ModularPipelines.Modules;
using ModularPipelines.Options;

namespace TUnit.Pipeline.Modules;

[NotInParallel("NetworkTests"), RunOnLinuxOnly, RunOnWindowsOnly]
public class RunAspireTestsModule : Module<CommandResult>
{
protected override async Task<CommandResult?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
{
var project = context.Git().RootDirectory.FindFile(x => x.Name == "TUnit.Aspire.Tests.csproj").AssertExists();

return await context.DotNet().Run(new DotNetRunOptions
{
Project = project.Name,
NoBuild = true,
Configuration = "Release",
Framework = "net10.0",
Arguments = ["--hangdump", "--hangdump-filename", "hangdump.aspire-tests.dmp", "--hangdump-timeout", "5m"],
}, new CommandExecutionOptions
{
WorkingDirectory = project.Folder!.Path,
EnvironmentVariables = new Dictionary<string, string?>
{
["DISABLE_GITHUB_REPORTER"] = "true",
},
LogSettings = new CommandLoggingOptions
{
ShowCommandArguments = true,
ShowStandardError = true,
ShowExecutionTime = true,
ShowExitCode = true
}
}, cancellationToken);
}
}
2 changes: 2 additions & 0 deletions TUnit.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
<Project Path="TUnit.SourceGenerator.IncrementalTests/TUnit.SourceGenerator.IncrementalTests.csproj" />
<Project Path="TUnit.Templates.Tests/TUnit.Templates.Tests.csproj" />
<Project Path="TUnit.UnitTests/TUnit.UnitTests.csproj" />
<Project Path="TUnit.Aspire.Tests/TUnit.Aspire.Tests.csproj" />
<Project Path="TUnit.Aspire.Tests.AppHost/TUnit.Aspire.Tests.AppHost.csproj" />
</Folder>

<!-- Benchmarks -->
Expand Down
Loading