Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<ProjectReference Include="..\..\src\Components\Aspire.Pomelo.EntityFrameworkCore.MySql\Aspire.Pomelo.EntityFrameworkCore.MySql.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(TestsSharedDir)AsyncTestHelpers.cs" Link="shared/AsyncTestHelpers.cs" />
</ItemGroup>

</Project>
57 changes: 29 additions & 28 deletions tests/Aspire.Hosting.MySql.Tests/MySqlFunctionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.Utils;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
Expand All @@ -27,7 +28,7 @@ public class MySqlFunctionalTests(ITestOutputHelper testOutputHelper)
[RequiresDocker]
public async Task VerifyWaitForOnMySqlBlocksDependentResources()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
using var cts = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan);
using var builder = TestDistributedApplicationBuilder.CreateWithTestContainerRegistry(testOutputHelper);

var healthCheckTcs = new TaskCompletionSource<HealthCheckResult>();
Expand Down Expand Up @@ -58,14 +59,14 @@ public async Task VerifyWaitForOnMySqlBlocksDependentResources()

await pendingStart;

await app.StopAsync();
await app.StopAsync(cts.Token);
}

[Fact]
[RequiresDocker]
public async Task VerifyMySqlResource()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
using var cts = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan * 2);
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new() { MaxRetryAttempts = 10, BackoffType = DelayBackoffType.Linear, Delay = TimeSpan.FromSeconds(2), ShouldHandle = new PredicateBuilder().Handle<MySqlException>() })
.Build();
Expand All @@ -79,9 +80,9 @@ public async Task VerifyMySqlResource()

using var app = builder.Build();

await app.StartAsync();
await app.StartAsync(cts.Token);

await app.WaitForTextAsync(s_mySqlReadyText).WaitAsync(TimeSpan.FromMinutes(2));
await app.WaitForTextAsync(s_mySqlReadyText, cts.Token).WaitAsync(cts.Token);

var hb = Host.CreateApplicationBuilder();

Expand All @@ -94,7 +95,7 @@ public async Task VerifyMySqlResource()

using var host = hb.Build();

await host.StartAsync();
await host.StartAsync(cts.Token);
await pipeline.ExecuteAsync(async token =>
{
using var connection = host.Services.GetRequiredService<MySqlConnection>();
Expand All @@ -119,7 +120,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume)
string? volumeName = null;
string? bindMountPath = null;

var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
using var cts = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan * 2);
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new() { MaxRetryAttempts = 10, BackoffType = DelayBackoffType.Linear, Delay = TimeSpan.FromSeconds(2) })
.Build();
Expand Down Expand Up @@ -151,9 +152,9 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume)

using (var app = builder1.Build())
{
await app.StartAsync();
await app.StartAsync(cts.Token);

await app.WaitForTextAsync(s_mySqlReadyText).WaitAsync(TimeSpan.FromMinutes(2));
await app.WaitForTextAsync(s_mySqlReadyText, cts.Token).WaitAsync(cts.Token);

try
{
Expand All @@ -168,7 +169,7 @@ public async Task WithDataShouldPersistStateBetweenUsages(bool useVolume)

using (var host = hb.Build())
{
await host.StartAsync();
await host.StartAsync(cts.Token);

// Wait until the database is available
await pipeline.ExecuteAsync(async token =>
Expand Down Expand Up @@ -199,7 +200,7 @@ await pipeline.ExecuteAsync(async token =>
finally
{
// Stops the container, or the Volume/mount would still be in use
await app.StopAsync();
await app.StopAsync(cts.Token);
}
}

Expand All @@ -220,24 +221,24 @@ await pipeline.ExecuteAsync(async token =>

using (var app = builder2.Build())
{
await app.StartAsync();
await app.StartAsync(cts.Token);

await app.WaitForTextAsync(s_mySqlReadyText).WaitAsync(TimeSpan.FromMinutes(2));
await app.WaitForTextAsync(s_mySqlReadyText, cts.Token).WaitAsync(cts.Token);

try
{
var hb = Host.CreateApplicationBuilder();

hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(default)
[$"ConnectionStrings:{db2.Resource.Name}"] = await db2.Resource.ConnectionStringExpression.GetValueAsync(cts.Token)
});

hb.AddMySqlDataSource(db2.Resource.Name);

using (var host = hb.Build())
{
await host.StartAsync();
await host.StartAsync(cts.Token);

// Wait until the database is available
await pipeline.ExecuteAsync(async token =>
Expand All @@ -264,7 +265,7 @@ await pipeline.ExecuteAsync(async token =>
finally
{
// Stops the container, or the Volume/mount would still be in use
await app.StopAsync();
await app.StopAsync(cts.Token);
}
}

Expand Down Expand Up @@ -296,7 +297,7 @@ public async Task VerifyWithInitBindMount()
{
// Creates a script that should be executed when the container is initialized.

var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
using var cts = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan * 2);
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new() { MaxRetryAttempts = 10, BackoffType = DelayBackoffType.Linear, Delay = TimeSpan.FromSeconds(2), ShouldHandle = new PredicateBuilder().Handle<MySqlException>() })
.Build();
Expand All @@ -323,22 +324,22 @@ public async Task VerifyWithInitBindMount()

using var app = builder.Build();

await app.StartAsync();
await app.StartAsync(cts.Token);

await app.WaitForTextAsync(s_mySqlReadyText).WaitAsync(TimeSpan.FromMinutes(2));
await app.WaitForTextAsync(s_mySqlReadyText, cts.Token).WaitAsync(cts.Token);

var hb = Host.CreateApplicationBuilder();

hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default)
[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(cts.Token)
});

hb.AddMySqlDataSource(db.Resource.Name);

using var host = hb.Build();

await host.StartAsync();
await host.StartAsync(cts.Token);

// Wait until the database is available
await pipeline.ExecuteAsync(async token =>
Expand Down Expand Up @@ -380,7 +381,7 @@ await pipeline.ExecuteAsync(async token =>
[QuarantinedTest("https://github.com/dotnet/aspire/issues/7340")]
public async Task VerifyEfMySql()
{
var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
using var cts = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan * 2);
var pipeline = new ResiliencePipelineBuilder()
.AddRetry(new() { MaxRetryAttempts = 10, BackoffType = DelayBackoffType.Linear, Delay = TimeSpan.FromSeconds(1), ShouldHandle = new PredicateBuilder().Handle<MySqlException>() })
.Build();
Expand All @@ -394,22 +395,22 @@ public async Task VerifyEfMySql()

using var app = builder.Build();

await app.StartAsync();
await app.StartAsync(cts.Token);

await app.WaitForTextAsync(s_mySqlReadyText).WaitAsync(TimeSpan.FromMinutes(2));
await app.WaitForTextAsync(s_mySqlReadyText, cts.Token).WaitAsync(cts.Token);

var hb = Host.CreateApplicationBuilder();

hb.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(default)
[$"ConnectionStrings:{db.Resource.Name}"] = await db.Resource.ConnectionStringExpression.GetValueAsync(cts.Token)
});

hb.AddMySqlDbContext<TestDbContext>(db.Resource.Name);

using var host = hb.Build();

await host.StartAsync();
await host.StartAsync(cts.Token);

// Wait until the database is available
await pipeline.ExecuteAsync(async token =>
Expand Down Expand Up @@ -453,7 +454,7 @@ public async Task MySql_WithPersistentLifetime_ReusesContainers(bool useMultiple
// it generates and mounts a config.user.inc.php file instead of using environment variables.
// For this reason we need to test with and without multiple instances to cover both scenarios.

var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));
using var cts = new CancellationTokenSource(TestConstants.ExtraLongTimeoutTimeSpan * 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On CI this would be a 12min timeout, is that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this because it was 10 mins already. Now either 6 mins locally or 12 mins in CI. @sebastienros I assume you picked such a large timeout for a reason..?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 minutes? probably to be sure it has enough time to run on the (slow) CI since it starts multiple containers serially. I haven't checked but obviously we can pick whatever the value is from normal runs on the CI + some margin.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I think this is fine as is -- maybe eventually we have some script that adjusts each test's timeouts to match CI * margin. But right now, most tests don't even have a single timeout.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you sign off if OK?


// Use the same path for both runs
var aspireStorePath = Directory.CreateTempSubdirectory().FullName;
Expand Down Expand Up @@ -511,7 +512,7 @@ public async Task MySql_WithPersistentLifetime_ReusesContainers(bool useMultiple
resourceEvent = await rns.WaitForResourceHealthyAsync("resource-phpmyadmin", cts.Token);
var phpMyAdminId = GetContainerId(resourceEvent);

await app.StopAsync(cts.Token).WaitAsync(TimeSpan.FromMinutes(1), cts.Token);
await app.StopAsync(cts.Token).WaitAsync(cts.Token);

return [mySqlId, mySqlId2, phpMyAdminId];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static Task WaitForTextAsync(this DistributedApplication app, string logT
return WaitForTextAsync(app, (log) => log.Contains(logText), resourceName, cancellationToken);
}

public static async Task WaitForHealthyAsync<T>(this DistributedApplication app, IResourceBuilder<T> resource, CancellationToken cancellationToken = default) where T: IResource
public static async Task WaitForHealthyAsync<T>(this DistributedApplication app, IResourceBuilder<T> resource, CancellationToken cancellationToken = default) where T : IResource
{
ArgumentNullException.ThrowIfNull(app);
ArgumentNullException.ThrowIfNull(resource);
Expand All @@ -52,6 +52,16 @@ public static Task WaitForTextAsync(this DistributedApplication app, IEnumerable
return app.WaitForTextAsync((log) => logTexts.Any(x => log.Contains(x)), resourceName, cancellationToken);
}

/// <summary>
/// Waits for the specified text to be logged.
/// </summary>
/// <param name="app">The <see cref="DistributedApplication" /> instance to watch.</param>
/// <param name="predicate">A predicate checking the text to wait for.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public static Task WaitForTextAsync(this DistributedApplication app, Predicate<string> predicate, CancellationToken cancellationToken = default)
=> app.WaitForTextAsync(predicate, resourceName: null, cancellationToken);

/// <summary>
/// Waits for the specified text to be logged.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions tests/Shared/AsyncTestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ internal static class TestConstants
// Less time waiting for hang unit tests to fail in aspnetcore solution.
public static readonly int DefaultTimeoutDuration = 5 * 1000 * (PlatformDetection.IsRunningOnCI ? 6 : 1); // 5 sec, 30 sec in CI
public static readonly int LongTimeoutDuration = 60 * 1000 * (PlatformDetection.IsRunningOnCI ? 3 : 1); // 60 sec, 180 sec in CI
public static readonly int ExtraLongTimeoutDuration = 60 * 1000 * 3 * (PlatformDetection.IsRunningOnCI ? 2 : 1); // 180 sec, 360 sec in CI -- useful when a docker image might need pulling
public static readonly int DefaultOrchestratorTestTimeout = 15 * 1000 * (PlatformDetection.IsRunningOnCI ? 2 : 1); // 15 sec, 30 sec in CI
public static readonly int DefaultOrchestratorTestLongTimeout = 45 * 1000 * (PlatformDetection.IsRunningOnCI ? 4 : 1); // 45 sec, 180 sec in CI

public static TimeSpan DefaultTimeoutTimeSpan { get; } = TimeSpan.FromMilliseconds(DefaultTimeoutDuration);
public static TimeSpan LongTimeoutTimeSpan { get; } = TimeSpan.FromMilliseconds(LongTimeoutDuration);
public static TimeSpan ExtraLongTimeoutTimeSpan { get; } = TimeSpan.FromMilliseconds(ExtraLongTimeoutDuration);
}

internal static class AsyncTestHelpers
Expand Down
Loading