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
7 changes: 5 additions & 2 deletions test/CodoMetis.TypeKit.EntityFrameworkCore.Tests/SqlServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public sealed class SqlServer : IAsyncLifetime
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("SQL Server is now ready for client connections"))
.Build();

private readonly ConcurrentDictionary<string, Task<string>> _databases = new();
// Lazy, because GetOrAdd may run its factory more than once for callers that arrive together, and two of them
// would drop and create the same database under each other: with the project's rows run in parallel, the first
// rows of a sweep did, "Database 'typekit_queries' already exists" (#251).
private readonly ConcurrentDictionary<string, Lazy<Task<string>>> _databases = new();

private string ConnectionString(string database) => new SqlConnectionStringBuilder
{
Expand All @@ -44,7 +47,7 @@ public sealed class SqlServer : IAsyncLifetime
}.ConnectionString;

/// <summary>A database of its own on the server, created empty the first time it is asked for, and its connection string.</summary>
public Task<string> DatabaseAsync(string name) => _databases.GetOrAdd(name, CreateAsync);
public Task<string> DatabaseAsync(string name) => _databases.GetOrAdd(name, key => new Lazy<Task<string>>(() => CreateAsync(key))).Value;

private async Task<string> CreateAsync(string name)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.Data.SqlClient;

namespace CodoMetis.TypeKit.EntityFrameworkCore.Tests;

/// <summary>
/// A guard on the harness: a database many callers ask for at once is created once, as the rows of a sweep ask for
/// it when they run in parallel (#251). Created more than once, one caller drops it under another, "Database
/// 'typekit_queries' already exists", or after another has returned, and every row reading it fails.
/// </summary>
[Collection(SqlServerCollection.Name)]
[Trait("Needs", "Docker")]
public sealed class SqlServerFixtureTests(SqlServer server)
{
[Fact]
public async Task A_database_asked_for_by_many_callers_at_once_is_created_once()
{
var name = $"typekit_fixture_{Guid.NewGuid():N}";

var answers = await Task.WhenAll(Enumerable.Range(0, 32).Select(_ => Task.Run(() => server.DatabaseAsync(name))));

answers.Distinct().ShouldHaveSingleItem();

// A creation that lost the race could still drop the database after the winner returned; give it the time.
await Task.Delay(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken);
await using var connection = new SqlConnection(answers[0]);
await connection.OpenAsync(TestContext.Current.CancellationToken);
await using var command = connection.CreateCommand();
command.CommandText = "SELECT DB_NAME()";
(await command.ExecuteScalarAsync(TestContext.Current.CancellationToken)).ShouldBe(name);
}
}
Loading