Skip to content

Commit cdb73cf

Browse files
radicaleerhardt
andauthored
[tests] Use ResiliencePipeline in IntegrationTests (dotnet#3385)
* [tests] Use ResiliencePipeline in IntegrationTests .. with a policy to retry with 1 sec delays, and a total timeout of 90 secs on the service side. The test(client) side has an overall http request timeout for 120 secs which would allow the real errors from the service to get surfaced to the client. * address review feedback from @ eerhardt * Use the pipeline for MySql also * IntegrationServiceFixture.DumpComponentLogsAsync: error out if more than one resource is given * Rename TestUtils -> ResilienceUtils - feedback from @ eerhardt * [tests] Ignore `CA1305` for tests https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1305 ``` dotnet_diagnostic.CA1305.severity = none ``` Addresses review feedback from @ eerhardt . * Remove Polly reference * Make code consistent. * add comment addressing review feedback from @ eerhardt --------- Co-authored-by: Eric Erhardt <[email protected]>
1 parent d0ddcbb commit cdb73cf

File tree

9 files changed

+82
-27
lines changed

9 files changed

+82
-27
lines changed

src/Aspire.Hosting/Aspire.Hosting.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<PackageReference Include="Grpc.AspNetCore" />
3535
<PackageReference Include="KubernetesClient" />
3636
<PackageReference Include="Microsoft.Extensions.Hosting" />
37-
<PackageReference Include="Polly" />
37+
<PackageReference Include="Polly.Core" />
3838
</ItemGroup>
3939

4040
<ItemGroup>

tests/.editorconfig

+3
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22

33
# CA2007: Consider calling ConfigureAwait on the awaited task
44
dotnet_diagnostic.CA2007.severity = silent
5+
6+
# CA1305: Specify IFormatProvider
7+
dotnet_diagnostic.CA1305.severity = none

tests/Aspire.EndToEnd.Tests/IntegrationServicesFixture.cs

+9
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ public async Task InitializeAsync()
7878

7979
public Task DumpComponentLogsAsync(TestResourceNames resource, ITestOutputHelper? testOutputArg = null)
8080
{
81+
if (resource == TestResourceNames.None)
82+
{
83+
return;
84+
}
85+
if (resource == TestResourceNames.All || !Enum.IsDefined<TestResourceNames>(resource))
86+
{
87+
throw new ArgumentException($"Only one resource is supported at a time. resource: {resource}");
88+
}
89+
8190
string component = resource switch
8291
{
8392
TestResourceNames.cosmos => "cosmos",

tests/testproject/TestProject.IntegrationServiceA/Cosmos/CosmosExtensions.cs

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text;
5+
using Aspire.TestProject;
46
using Microsoft.Azure.Cosmos;
57
using Polly;
68

@@ -13,15 +15,17 @@ public static void MapCosmosApi(this WebApplication app)
1315

1416
private static async Task<IResult> VerifyCosmosAsync(CosmosClient cosmosClient)
1517
{
18+
StringBuilder errorMessageBuilder = new();
1619
try
1720
{
18-
var policy = Policy
19-
.Handle<HttpRequestException>()
20-
// retry 60 times with a 1 second delay between retries
21-
.WaitAndRetryAsync(60, retryAttempt => TimeSpan.FromSeconds(1));
21+
ResiliencePipeline pipeline = ResilienceUtils.GetDefaultResiliencePipelineBuilder<HttpRequestException>(args =>
22+
{
23+
errorMessageBuilder.AppendLine($"{Environment.NewLine}Service retry #{args.AttemptNumber} due to {args.Outcome.Exception}");
24+
return ValueTask.CompletedTask;
25+
}).Build();
2226

23-
var db = await policy.ExecuteAsync(
24-
async () => (await cosmosClient.CreateDatabaseIfNotExistsAsync("db")).Database);
27+
var db = await pipeline.ExecuteAsync(
28+
async token => (await cosmosClient.CreateDatabaseIfNotExistsAsync("db", cancellationToken: token)).Database);
2529

2630
var container = (await db.CreateContainerIfNotExistsAsync("todos", "/id")).Container;
2731

@@ -38,7 +42,7 @@ private static async Task<IResult> VerifyCosmosAsync(CosmosClient cosmosClient)
3842
}
3943
catch (Exception e)
4044
{
41-
return Results.Problem(e.ToString());
45+
return Results.Problem($"Error: {e}{Environment.NewLine}** Previous retries: {errorMessageBuilder}");
4246
}
4347
}
4448
}

tests/testproject/TestProject.IntegrationServiceA/MySql/MySqlExtensions.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text;
5+
using Aspire.TestProject;
46
using MySqlConnector;
57
using Polly;
68

@@ -13,14 +15,16 @@ public static void MapMySqlApi(this WebApplication app)
1315

1416
private static async Task<IResult> VerifyMySqlAsync(MySqlConnection connection)
1517
{
18+
StringBuilder errorMessageBuilder = new();
1619
try
1720
{
18-
var policy = Policy
19-
.Handle<MySqlException>()
20-
// retry 60 times with a 1 second delay between retries
21-
.WaitAndRetryAsync(60, retryAttempt => TimeSpan.FromSeconds(1));
21+
ResiliencePipeline pipeline = ResilienceUtils.GetDefaultResiliencePipelineBuilder<MySqlException>(args =>
22+
{
23+
errorMessageBuilder.AppendLine($"{Environment.NewLine}Service retry #{args.AttemptNumber} due to {args.Outcome.Exception}");
24+
return ValueTask.CompletedTask;
25+
}).Build();
2226

23-
await policy.ExecuteAsync(connection.OpenAsync);
27+
await pipeline.ExecuteAsync(async token => await connection.OpenAsync(token));
2428

2529
var command = connection.CreateCommand();
2630
command.CommandText = $"SELECT 1";
@@ -30,7 +34,7 @@ private static async Task<IResult> VerifyMySqlAsync(MySqlConnection connection)
3034
}
3135
catch (Exception e)
3236
{
33-
return Results.Problem(e.ToString());
37+
return Results.Problem($"Error: {e}{Environment.NewLine}** Previous retries: {errorMessageBuilder}");
3438
}
3539
}
3640
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text;
5+
using Aspire.TestProject;
46
using Microsoft.EntityFrameworkCore;
57
using Oracle.ManagedDataAccess.Client;
68
using Polly;
@@ -14,22 +16,24 @@ public static void MapOracleDatabaseApi(this WebApplication app)
1416

1517
private static IResult VerifyOracleDatabase(MyDbContext context)
1618
{
19+
StringBuilder errorMessageBuilder = new();
1720
try
1821
{
19-
var policy = Policy
20-
.Handle<OracleException>()
21-
// retry 60 times with a 1 second delay between retries
22-
.WaitAndRetry(60, retryAttempt => TimeSpan.FromSeconds(1));
22+
ResiliencePipeline pipeline = ResilienceUtils.GetDefaultResiliencePipelineBuilder<OracleException>(args =>
23+
{
24+
errorMessageBuilder.AppendLine($"{Environment.NewLine}Service retry #{args.AttemptNumber} due to {args.Outcome.Exception}");
25+
return ValueTask.CompletedTask;
26+
}).Build();
2327

24-
return policy.Execute(() =>
28+
return pipeline.Execute(() =>
2529
{
2630
var results = context.Database.SqlQueryRaw<int>("SELECT 1 FROM DUAL");
2731
return results.Any() ? Results.Ok("Success!") : Results.Problem("Failed");
2832
});
2933
}
3034
catch (Exception e)
3135
{
32-
return Results.Problem(e.ToString());
36+
return Results.Problem($"Error: {e}{Environment.NewLine}** Previous retries: {errorMessageBuilder}");
3337
}
3438
}
3539
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Polly;
5+
using Polly.Retry;
6+
7+
namespace Aspire.TestProject;
8+
9+
public static class ResilienceUtils
10+
{
11+
public static ResiliencePipelineBuilder GetDefaultResiliencePipelineBuilder<TException>(Func<OnRetryArguments<object>, ValueTask> onRetry, int overallTimeoutSecs = 90) where TException : Exception
12+
{
13+
// Retry for upto 20 times with delay of 1 sec between
14+
// attempts, and also stop before an overall timeout of
15+
// @overallTimeoutSecs
16+
var optionsOnRetry = new RetryStrategyOptions
17+
{
18+
MaxRetryAttempts = 20,
19+
ShouldHandle = new PredicateBuilder().Handle<TException>(),
20+
Delay = TimeSpan.FromSeconds(1),
21+
OnRetry = onRetry
22+
};
23+
return new ResiliencePipelineBuilder()
24+
.AddTimeout(TimeSpan.FromSeconds(overallTimeoutSecs))
25+
.AddRetry(optionsOnRetry);
26+
}
27+
}

tests/testproject/TestProject.IntegrationServiceA/SqlServer/SqlServerExtensions.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Text;
5+
using Aspire.TestProject;
46
using Microsoft.Data.SqlClient;
57
using Polly;
68

@@ -13,14 +15,16 @@ public static void MapSqlServerApi(this WebApplication app)
1315

1416
private static async Task<IResult> VerifySqlServerAsync(SqlConnection connection)
1517
{
18+
StringBuilder errorMessageBuilder = new();
1619
try
1720
{
18-
var policy = Policy
19-
.Handle<SqlException>()
20-
// retry 60 times with a 1 second delay between retries
21-
.WaitAndRetryAsync(60, retryAttempt => TimeSpan.FromSeconds(1));
21+
ResiliencePipeline pipeline = ResilienceUtils.GetDefaultResiliencePipelineBuilder<SqlException>(args =>
22+
{
23+
errorMessageBuilder.AppendLine($"{Environment.NewLine}Service retry #{args.AttemptNumber} due to {args.Outcome.Exception}");
24+
return ValueTask.CompletedTask;
25+
}).Build();
2226

23-
await policy.ExecuteAsync(connection.OpenAsync);
27+
await pipeline.ExecuteAsync(async token => await connection.OpenAsync(token));
2428

2529
var command = connection.CreateCommand();
2630
command.CommandText = $"SELECT 1";
@@ -30,7 +34,7 @@ private static async Task<IResult> VerifySqlServerAsync(SqlConnection connection
3034
}
3135
catch (Exception e)
3236
{
33-
return Results.Problem(e.ToString());
37+
return Results.Problem($"Error: {e}{Environment.NewLine}** Previous retries: {errorMessageBuilder}");
3438
}
3539
}
3640
}

tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<ItemGroup>
3232
<ProjectReference Condition="'$(TestsRunningOutsideOfRepo)' != 'true'" Include="@(ComponentReferenceForTests -> '$(RepoRoot)src\Components\%(Identity)\%(Identity).csproj')" />
3333
<PackageReference Condition="'$(TestsRunningOutsideOfRepo)' == 'true'" Include="@(ComponentReferenceForTests)" />
34-
<PackageReference Include="Polly" />
34+
<PackageReference Include="Polly.Core" />
3535
</ItemGroup>
3636

3737
</Project>

0 commit comments

Comments
 (0)