Multiple test databases, similar setup to LocalDbTestDatabase.DatabasePool
This commit is contained in:
@@ -24,6 +24,7 @@ public class TestsSetup
|
||||
public void TearDown()
|
||||
{
|
||||
LocalDbTestDatabase.KillLocalDb();
|
||||
SqlDeveloperTestDatabase.Instance?.Finish();
|
||||
Console.WriteLine("TOTAL TESTS DURATION: {0}", _stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
|
||||
|
||||
private static void ResetLocalDb(IDbCommand cmd)
|
||||
internal static void ResetLocalDb(IDbCommand cmd)
|
||||
{
|
||||
// https://stackoverflow.com/questions/536350
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
}
|
||||
|
||||
private static void Retry(int maxIterations, Action action)
|
||||
internal static void Retry(int maxIterations, Action action)
|
||||
{
|
||||
for (var i = 0; i < maxIterations; i++)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -18,83 +21,90 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
/// </remarks>
|
||||
public class SqlDeveloperTestDatabase : ITestDatabase
|
||||
{
|
||||
|
||||
// This is gross but it's how the other one works and I don't want to refactor everything.
|
||||
public string ConnectionString { get; private set; }
|
||||
|
||||
private readonly string _masterConnectionString;
|
||||
private readonly string _databaseName;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger _log;
|
||||
private readonly IUmbracoDatabaseFactory _databaseFactory;
|
||||
private readonly IDictionary<int, TestDbMeta> _testDatabases;
|
||||
private UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands;
|
||||
|
||||
private BlockingCollection<TestDbMeta> _prepareQueue;
|
||||
private BlockingCollection<TestDbMeta> _readySchemaQueue;
|
||||
private BlockingCollection<TestDbMeta> _readyEmptyQueue;
|
||||
|
||||
private const string _databasePrefix = "UmbracoTest";
|
||||
private const int _threadCount = 2;
|
||||
|
||||
public static SqlDeveloperTestDatabase Instance;
|
||||
|
||||
public SqlDeveloperTestDatabase(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory databaseFactory, string masterConnectionString)
|
||||
{
|
||||
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
_databaseFactory = databaseFactory ?? throw new ArgumentNullException(nameof(databaseFactory));
|
||||
_masterConnectionString = masterConnectionString;
|
||||
_databaseName = $"Umbraco_Integration_{Guid.NewGuid()}".Replace("-", string.Empty);
|
||||
_log = loggerFactory.CreateLogger<SqlDeveloperTestDatabase>();
|
||||
}
|
||||
|
||||
public string ConnectionString { get; private set; }
|
||||
_testDatabases = new[]
|
||||
{
|
||||
new TestDbMeta(1, false, masterConnectionString),
|
||||
new TestDbMeta(2, false, masterConnectionString),
|
||||
|
||||
new TestDbMeta(3, true, masterConnectionString),
|
||||
new TestDbMeta(4, true, masterConnectionString),
|
||||
}.ToDictionary(x => x.Id);
|
||||
|
||||
Instance = this; // For GlobalSetupTeardown.cs
|
||||
}
|
||||
|
||||
public int AttachEmpty()
|
||||
{
|
||||
CreateDatabase();
|
||||
return -1;
|
||||
if (_prepareQueue == null)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
var meta = _readyEmptyQueue.Take();
|
||||
|
||||
ConnectionString = meta.ConnectionString;
|
||||
|
||||
return meta.Id;
|
||||
}
|
||||
|
||||
public int AttachSchema()
|
||||
{
|
||||
CreateDatabase();
|
||||
|
||||
_log.LogInformation($"Attaching schema {_databaseName}");
|
||||
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
if (_prepareQueue == null)
|
||||
{
|
||||
connection.Open();
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
RebuildSchema(command);
|
||||
}
|
||||
Initialize();
|
||||
}
|
||||
|
||||
return -1;
|
||||
var meta = _readySchemaQueue.Take();
|
||||
|
||||
ConnectionString = meta.ConnectionString;
|
||||
|
||||
return meta.Id;
|
||||
}
|
||||
|
||||
public void Detach(int id)
|
||||
{
|
||||
_log.LogInformation($"Dropping database {_databaseName}");
|
||||
using (var connection = new SqlConnection(_masterConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
SetCommand(command, $@"
|
||||
ALTER DATABASE{LocalDb.QuotedName(_databaseName)}
|
||||
SET SINGLE_USER
|
||||
WITH ROLLBACK IMMEDIATE
|
||||
");
|
||||
command.ExecuteNonQuery();
|
||||
|
||||
SetCommand(command, $@"DROP DATABASE {LocalDb.QuotedName(_databaseName)}");
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
_prepareQueue.TryAdd(_testDatabases[id]);
|
||||
}
|
||||
|
||||
private void CreateDatabase()
|
||||
private void CreateDatabase(TestDbMeta meta)
|
||||
{
|
||||
_log.LogInformation($"Creating database {_databaseName}");
|
||||
_log.LogInformation($"Creating database {meta.Name}");
|
||||
using (var connection = new SqlConnection(_masterConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
SetCommand(command, $@"CREATE DATABASE {LocalDb.QuotedName(_databaseName)}");
|
||||
var unused = command.ExecuteNonQuery();
|
||||
SetCommand(command, $@"CREATE DATABASE {LocalDb.QuotedName(meta.Name)}");
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionString = ConstructConnectionString(_masterConnectionString, _databaseName);
|
||||
}
|
||||
|
||||
private static string ConstructConnectionString(string masterConnectionString, string databaseName)
|
||||
@@ -116,7 +126,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildSchema(IDbCommand command)
|
||||
private void RebuildSchema(IDbCommand command, TestDbMeta meta)
|
||||
{
|
||||
if (_cachedDatabaseInitCommands != null)
|
||||
{
|
||||
@@ -141,7 +151,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseFactory.Configure(ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
_databaseFactory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
|
||||
using (var database = (UmbracoDatabase)_databaseFactory.CreateDatabase())
|
||||
{
|
||||
@@ -159,5 +169,120 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_prepareQueue = new BlockingCollection<TestDbMeta>();
|
||||
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
|
||||
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
|
||||
|
||||
foreach (var meta in _testDatabases.Values)
|
||||
{
|
||||
CreateDatabase(meta);
|
||||
_prepareQueue.Add(meta);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _threadCount; i++)
|
||||
{
|
||||
var thread = new Thread(PrepareThread);
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void Drop(TestDbMeta meta)
|
||||
{
|
||||
_log.LogInformation($"Dropping database {meta.Name}");
|
||||
using (var connection = new SqlConnection(_masterConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
SetCommand(command, $@"
|
||||
ALTER DATABASE{LocalDb.QuotedName(meta.Name)}
|
||||
SET SINGLE_USER
|
||||
WITH ROLLBACK IMMEDIATE
|
||||
");
|
||||
command.ExecuteNonQuery();
|
||||
|
||||
SetCommand(command, $@"DROP DATABASE {LocalDb.QuotedName(meta.Name)}");
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareThread()
|
||||
{
|
||||
LocalDbTestDatabase.Retry(10, () =>
|
||||
{
|
||||
while (_prepareQueue.IsCompleted == false)
|
||||
{
|
||||
TestDbMeta meta;
|
||||
try
|
||||
{
|
||||
meta = _prepareQueue.Take();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using (var conn = new SqlConnection(meta.ConnectionString))
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
conn.Open();
|
||||
LocalDbTestDatabase.ResetLocalDb(cmd);
|
||||
|
||||
if (!meta.IsEmpty)
|
||||
{
|
||||
RebuildSchema(cmd, meta);
|
||||
}
|
||||
}
|
||||
|
||||
if (!meta.IsEmpty)
|
||||
{
|
||||
_readySchemaQueue.TryAdd(meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
_readyEmptyQueue.TryAdd(meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Finish()
|
||||
{
|
||||
if (_prepareQueue == null)
|
||||
return;
|
||||
|
||||
_prepareQueue.CompleteAdding();
|
||||
while (_prepareQueue.TryTake(out _)) { }
|
||||
|
||||
_readyEmptyQueue.CompleteAdding();
|
||||
while (_readyEmptyQueue.TryTake(out _)) { }
|
||||
|
||||
_readySchemaQueue.CompleteAdding();
|
||||
while (_readySchemaQueue.TryTake(out _)) { }
|
||||
|
||||
foreach (var testDatabase in _testDatabases.Values)
|
||||
{
|
||||
Drop(testDatabase);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDbMeta
|
||||
{
|
||||
public int Id { get; }
|
||||
public string Name => $"{_databasePrefix}-{Id}";
|
||||
public bool IsEmpty { get; }
|
||||
public string ConnectionString { get; }
|
||||
|
||||
public TestDbMeta(int id, bool isEmpty, string masterConnectionString)
|
||||
{
|
||||
Id = id;
|
||||
IsEmpty = isEmpty;
|
||||
ConnectionString = ConstructConnectionString(masterConnectionString, Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
var databaseFactory = serviceProvider.GetRequiredService<IUmbracoDatabaseFactory>();
|
||||
|
||||
// This will create a db, install the schema and ensure the app is configured to run
|
||||
InstallTestLocalDb(databaseFactory, TestHelper.ConsoleLoggerFactory, state, TestHelper.WorkingDirectory);
|
||||
InstallTestLocalDb(databaseFactory, serviceProvider.GetRequiredService<ILoggerFactory>(), state, TestHelper.WorkingDirectory);
|
||||
TestDBConnectionString = databaseFactory.ConnectionString;
|
||||
InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user