Skip to content

Commit afa80e8

Browse files
authored
Merge pull request #1218 from json-api-dotnet/xunit-perf-throttling
Improve speed of running tests
2 parents aecf4c7 + 7cdf273 commit afa80e8

31 files changed

+100
-79
lines changed

Diff for: test/AnnotationTests/AnnotationTests.csproj

-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44
<LangVersion>latest</LangVersion>
55
</PropertyGroup>
66

7-
<ItemGroup>
8-
<None Update="xunit.runner.json">
9-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
10-
</None>
11-
</ItemGroup>
12-
137
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.0' ">
148
<Using Remove="System.Net.Http" />
159
</ItemGroup>

Diff for: test/DiscoveryTests/DiscoveryTests.csproj

-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
<TargetFramework>$(TargetFrameworkName)</TargetFramework>
44
</PropertyGroup>
55

6-
<ItemGroup>
7-
<None Update="xunit.runner.json">
8-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
9-
</None>
10-
</ItemGroup>
11-
126
<ItemGroup>
137
<ProjectReference Include="..\..\src\Examples\JsonApiDotNetCoreExample\JsonApiDotNetCoreExample.csproj" />
148
<ProjectReference Include="..\TestBuildingBlocks\TestBuildingBlocks.csproj" />

Diff for: test/DiscoveryTests/xunit.runner.json

-4
This file was deleted.

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3131
builder.Entity<MusicTrack>()
3232
.HasMany(musicTrack => musicTrack.OccursIn)
3333
.WithMany(playlist => playlist.Tracks);
34+
35+
base.OnModelCreating(builder);
3436
}
3537
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs

+21-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Transactions;
1111

12-
public sealed class AtomicTransactionConsistencyTests : IClassFixture<IntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext>>
12+
public sealed class AtomicTransactionConsistencyTests
13+
: IClassFixture<IntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext>>, IAsyncLifetime
1314
{
1415
private readonly IntegrationTestContext<TestableStartup<OperationsDbContext>, OperationsDbContext> _testContext;
1516
private readonly OperationsFakers _fakers = new();
@@ -27,7 +28,7 @@ public AtomicTransactionConsistencyTests(IntegrationTestContext<TestableStartup<
2728
services.AddResourceRepository<LyricRepository>();
2829

2930
string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres";
30-
string dbConnectionString = $"Host=localhost;Port=5432;Database=JsonApiTest-{Guid.NewGuid():N};User ID=postgres;Password={postgresPassword}";
31+
string dbConnectionString = $"Host=localhost;Port=5432;Database=JsonApiTest-Extra-{Guid.NewGuid():N};User ID=postgres;Password={postgresPassword}";
3132

3233
services.AddDbContext<ExtraDbContext>(options => options.UseNpgsql(dbConnectionString));
3334
});
@@ -158,4 +159,22 @@ public async Task Cannot_use_distributed_transaction()
158159
error.Source.ShouldNotBeNull();
159160
error.Source.Pointer.Should().Be("/atomic:operations[0]");
160161
}
162+
163+
public Task InitializeAsync()
164+
{
165+
return Task.CompletedTask;
166+
}
167+
168+
Task IAsyncLifetime.DisposeAsync()
169+
{
170+
return DeleteExtraDatabaseAsync();
171+
}
172+
173+
private async Task DeleteExtraDatabaseAsync()
174+
{
175+
await using AsyncServiceScope scope = _testContext.Factory.Services.CreateAsyncScope();
176+
var dbContext = scope.ServiceProvider.GetRequiredService<ExtraDbContext>();
177+
178+
await dbContext.Database.EnsureDeletedAsync();
179+
}
161180
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3939
builder.Entity<Car>()
4040
.HasMany(car => car.PreviousDealerships)
4141
.WithMany(dealership => dealership.SoldCars);
42+
43+
base.OnModelCreating(builder);
4244
}
4345
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3434
.HasOne(building => building.SecondaryDoor)
3535
.WithOne()
3636
.HasForeignKey<Building>("SecondaryDoorId");
37+
38+
base.OnModelCreating(builder);
3739
}
3840
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3737
builder.Entity<SystemDirectory>()
3838
.HasOne(systemDirectory => systemDirectory.AlsoSelf)
3939
.WithOne();
40+
41+
base.OnModelCreating(builder);
4042
}
4143
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ protected override void OnModelCreating(ModelBuilder builder)
2424
.HasOne(photo => photo.Location)
2525
.WithOne(location => location.Photo)
2626
.HasForeignKey<Photo>("LocationId");
27+
28+
base.OnModelCreating(builder);
2729
}
2830
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3131

3232
builder.Entity<WebProduct>()
3333
.HasQueryFilter(webProduct => webProduct.Shop.TenantId == _tenantProvider.TenantId);
34+
35+
base.OnModelCreating(builder);
3436
}
3537
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ protected override void OnModelCreating(ModelBuilder builder)
2121
builder.Entity<FilterableResource>()
2222
.Property(resource => resource.SomeDateTimeInLocalZone)
2323
.HasColumnType("timestamp without time zone");
24+
25+
base.OnModelCreating(builder);
2426
}
2527
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3737
.HasOne(man => man.Wife)
3838
.WithOne(woman => woman.Husband)
3939
.HasForeignKey<Man>();
40+
41+
base.OnModelCreating(builder);
4042
}
4143
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,7 @@ protected override void OnModelCreating(ModelBuilder builder)
4949
left => left
5050
.HasOne(joinEntity => joinEntity.ToItem)
5151
.WithMany());
52+
53+
base.OnModelCreating(builder);
5254
}
5355
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3333
.HasOne(order => order.Shipment)
3434
.WithOne(shipment => shipment.Order)
3535
.HasForeignKey<Shipment>("OrderId");
36+
37+
base.OnModelCreating(builder);
3638
}
3739
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ protected override void OnModelCreating(ModelBuilder builder)
2222
builder.Entity<Scholarship>()
2323
.HasMany(scholarship => scholarship.Participants)
2424
.WithOne(student => student.Scholarship!);
25+
26+
base.OnModelCreating(builder);
2527
}
2628
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2429,7 +2429,7 @@ public async Task Can_sort_on_derived_attribute_from_resource_definition_using_e
24292429

24302430
await _testContext.RunOnDatabaseAsync(async dbContext =>
24312431
{
2432-
await dbContext.ClearTableAsync<Vehicle>();
2432+
await dbContext.ClearTableAsync<Wheel>();
24332433
dbContext.Wheels.AddRange(chromeWheel1, chromeWheel2, chromeWheel3, carbonWheel1, carbonWheel2);
24342434
await dbContext.SaveChangesAsync();
24352435
});
@@ -2487,7 +2487,7 @@ public async Task Can_sort_on_derived_attribute_from_resource_definition_using_l
24872487

24882488
await _testContext.RunOnDatabaseAsync(async dbContext =>
24892489
{
2490-
await dbContext.ClearTableAsync<Vehicle>();
2490+
await dbContext.ClearTableAsync<Wheel>();
24912491
dbContext.Wheels.AddRange(chromeWheel1, chromeWheel2, chromeWheel3, carbonWheel1, carbonWheel2);
24922492
await dbContext.SaveChangesAsync();
24932493
});

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@ protected override void OnModelCreating(ModelBuilder builder)
3232
builder.Entity<GenericProperty>().ToTable("GenericProperties");
3333
builder.Entity<StringProperty>().ToTable("StringProperties");
3434
builder.Entity<NumberProperty>().ToTable("NumberProperties");
35+
36+
base.OnModelCreating(builder);
3537
}
3638
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ protected override void OnModelCreating(ModelBuilder builder)
2424

2525
builder.Entity<Department>()
2626
.HasQueryFilter(department => department.SoftDeletedAt == null);
27+
28+
base.OnModelCreating(builder);
2729
}
2830
}

Diff for: test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs

+2
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ protected override void OnModelCreating(ModelBuilder builder)
2727
builder.Entity<Player>()
2828
.HasOne(player => player.ActiveGame)
2929
.WithMany(game => game.ActivePlayers);
30+
31+
base.OnModelCreating(builder);
3032
}
3133
}

Diff for: test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj

-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
<TargetFramework>$(TargetFrameworkName)</TargetFramework>
44
</PropertyGroup>
55

6-
<ItemGroup>
7-
<None Update="xunit.runner.json">
8-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
9-
</None>
10-
</ItemGroup>
11-
126
<ItemGroup>
137
<ProjectReference Include="..\TestBuildingBlocks\TestBuildingBlocks.csproj" />
148
<ProjectReference Include="..\..\src\JsonApiDotNetCore.SourceGenerators\JsonApiDotNetCore.SourceGenerators.csproj" OutputItemType="Analyzer"

Diff for: test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ private static IHostBuilder CreateValidatingHostBuilder()
7474
IHostBuilder hostBuilder = Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder =>
7575
{
7676
webBuilder.ConfigureServices(services =>
77-
services.AddDbContext<DependencyContainerRegistrationDbContext>(options => options.UseInMemoryDatabase("db")));
77+
services.AddDbContext<DependencyContainerRegistrationDbContext>(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString())));
7878

7979
webBuilder.UseStartup<TestableStartup<DependencyContainerRegistrationDbContext>>();
8080

Diff for: test/JsonApiDotNetCoreTests/xunit.runner.json

-5
This file was deleted.

Diff for: test/MultiDbContextTests/MultiDbContextTests.csproj

-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
<TargetFramework>$(TargetFrameworkName)</TargetFramework>
44
</PropertyGroup>
55

6-
<ItemGroup>
7-
<None Update="xunit.runner.json">
8-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
9-
</None>
10-
</ItemGroup>
11-
126
<ItemGroup>
137
<ProjectReference Include="..\..\src\Examples\MultiDbContextExample\MultiDbContextExample.csproj" />
148
<ProjectReference Include="..\TestBuildingBlocks\TestBuildingBlocks.csproj" />

Diff for: test/MultiDbContextTests/xunit.runner.json

-5
This file was deleted.

Diff for: test/TestBuildingBlocks/DbContextExtensions.cs

+1-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Microsoft.EntityFrameworkCore;
22
using Microsoft.EntityFrameworkCore.Metadata;
3-
using Npgsql;
43

54
namespace TestBuildingBlocks;
65

@@ -36,17 +35,7 @@ private static async Task ClearTablesAsync(this DbContext dbContext, params Type
3635
}
3736

3837
string tableName = entityType.GetTableName()!;
39-
40-
// PERF: We first try to clear the table, which is fast and usually succeeds, unless foreign key constraints are violated.
41-
// In that case, we recursively delete all related data, which is slow.
42-
try
43-
{
44-
await dbContext.Database.ExecuteSqlRawAsync($"delete from \"{tableName}\"");
45-
}
46-
catch (PostgresException)
47-
{
48-
await dbContext.Database.ExecuteSqlRawAsync($"truncate table \"{tableName}\" cascade");
49-
}
38+
await dbContext.Database.ExecuteSqlRawAsync($"delete from \"{tableName}\"");
5039
}
5140
}
5241
}

Diff for: test/TestBuildingBlocks/IntegrationTest.cs

+17-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
using System.Text;
33
using System.Text.Json;
44
using JsonApiDotNetCore.Middleware;
5+
using Xunit;
56

67
namespace TestBuildingBlocks;
78

89
/// <summary>
9-
/// A base class for tests that conveniently enables to execute HTTP requests against JSON:API endpoints.
10+
/// A base class for tests that conveniently enables to execute HTTP requests against JSON:API endpoints. It throttles tests that are running in parallel
11+
/// to avoid exceeding the maximum active database connections.
1012
/// </summary>
11-
public abstract class IntegrationTest
13+
public abstract class IntegrationTest : IAsyncLifetime
1214
{
15+
private static readonly SemaphoreSlim ThrottleSemaphore = new(64);
16+
1317
protected abstract JsonSerializerOptions SerializerOptions { get; }
1418

1519
public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecuteHeadAsync<TResponseDocument>(string requestUrl,
@@ -105,4 +109,15 @@ public abstract class IntegrationTest
105109
throw new FormatException($"Failed to deserialize response body to JSON:\n{responseText}", exception);
106110
}
107111
}
112+
113+
public async Task InitializeAsync()
114+
{
115+
await ThrottleSemaphore.WaitAsync();
116+
}
117+
118+
public virtual Task DisposeAsync()
119+
{
120+
_ = ThrottleSemaphore.Release();
121+
return Task.CompletedTask;
122+
}
108123
}

Diff for: test/TestBuildingBlocks/IntegrationTestContext.cs

+17-11
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace TestBuildingBlocks;
2424
/// The Entity Framework Core database context, which can be defined in the test project or API project.
2525
/// </typeparam>
2626
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
27-
public class IntegrationTestContext<TStartup, TDbContext> : IntegrationTest, IDisposable
27+
public class IntegrationTestContext<TStartup, TDbContext> : IntegrationTest
2828
where TStartup : class
2929
where TDbContext : TestableDbContext
3030
{
@@ -103,16 +103,6 @@ private WebApplicationFactory<TStartup> CreateFactory()
103103
return factoryWithConfiguredContentRoot;
104104
}
105105

106-
public void Dispose()
107-
{
108-
if (_lazyFactory.IsValueCreated)
109-
{
110-
RunOnDatabaseAsync(async dbContext => await dbContext.Database.EnsureDeletedAsync()).Wait();
111-
112-
_lazyFactory.Value.Dispose();
113-
}
114-
}
115-
116106
public void ConfigureLogging(Action<ILoggingBuilder> loggingConfiguration)
117107
{
118108
_loggingConfiguration = loggingConfiguration;
@@ -136,6 +126,22 @@ public async Task RunOnDatabaseAsync(Func<TDbContext, Task> asyncAction)
136126
await asyncAction(dbContext);
137127
}
138128

129+
public override async Task DisposeAsync()
130+
{
131+
try
132+
{
133+
if (_lazyFactory.IsValueCreated)
134+
{
135+
await RunOnDatabaseAsync(async dbContext => await dbContext.Database.EnsureDeletedAsync());
136+
await _lazyFactory.Value.DisposeAsync();
137+
}
138+
}
139+
finally
140+
{
141+
await base.DisposeAsync();
142+
}
143+
}
144+
139145
private sealed class IntegrationTestWebApplicationFactory : WebApplicationFactory<TStartup>
140146
{
141147
private Action<ILoggingBuilder>? _loggingConfiguration;

Diff for: test/TestBuildingBlocks/TestableDbContext.cs

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Diagnostics;
22
using JsonApiDotNetCore;
33
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Metadata;
45
using Microsoft.Extensions.Logging;
56

67
namespace TestBuildingBlocks;
@@ -17,4 +18,15 @@ protected override void OnConfiguring(DbContextOptionsBuilder builder)
1718
// Writes SQL statements to the Output Window when debugging.
1819
builder.LogTo(message => Debug.WriteLine(message), DbLoggerCategory.Database.Name.AsArray(), LogLevel.Information);
1920
}
21+
22+
protected override void OnModelCreating(ModelBuilder builder)
23+
{
24+
foreach (IMutableForeignKey foreignKey in builder.Model.GetEntityTypes().SelectMany(entityType => entityType.GetForeignKeys()))
25+
{
26+
if (foreignKey.DeleteBehavior == DeleteBehavior.ClientSetNull)
27+
{
28+
foreignKey.DeleteBehavior = DeleteBehavior.SetNull;
29+
}
30+
}
31+
}
2032
}

Diff for: test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void RegisterResource_DeviatingDbContextPropertyName_RegistersCorrectly()
2525
// Arrange
2626
var services = new ServiceCollection();
2727
services.AddLogging();
28-
services.AddDbContext<TestDbContext>(options => options.UseInMemoryDatabase("UnitTestDb"));
28+
services.AddDbContext<TestDbContext>(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
2929

3030
// Act
3131
services.AddJsonApi<TestDbContext>();

0 commit comments

Comments
 (0)