2020-12-12 11:33:57 +00:00
|
|
|
using System;
|
2020-03-30 17:25:29 +11:00
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Threading;
|
2020-09-17 12:52:25 +02:00
|
|
|
using Microsoft.Extensions.Logging;
|
2020-03-30 17:25:29 +11:00
|
|
|
using Umbraco.Core.Persistence;
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Tests.Integration.Testing
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manages a pool of LocalDb databases for integration testing
|
|
|
|
|
/// </summary>
|
2020-12-12 11:33:57 +00:00
|
|
|
public class LocalDbTestDatabase : BaseTestDatabase, ITestDatabase
|
2020-03-30 17:25:29 +11:00
|
|
|
{
|
|
|
|
|
public const string InstanceName = "UmbracoTests";
|
|
|
|
|
public const string DatabaseName = "UmbracoTests";
|
|
|
|
|
|
|
|
|
|
private readonly LocalDb _localDb;
|
2020-12-12 11:33:57 +00:00
|
|
|
private static LocalDb.Instance _localDbInstance;
|
2020-03-30 17:25:29 +11:00
|
|
|
private static string _filesPath;
|
2020-12-12 11:33:57 +00:00
|
|
|
|
|
|
|
|
private const int _threadCount = 2;
|
|
|
|
|
|
|
|
|
|
public static LocalDbTestDatabase Instance { get; private set; }
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-03-30 20:55:13 +11:00
|
|
|
//It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
|
2020-09-18 12:53:06 +02:00
|
|
|
internal LocalDbTestDatabase(ILoggerFactory loggerFactory, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory)
|
2020-03-30 17:25:29 +11:00
|
|
|
{
|
2020-09-17 12:52:25 +02:00
|
|
|
_loggerFactory = loggerFactory;
|
2020-12-12 11:33:57 +00:00
|
|
|
_databaseFactory = dbFactory;
|
|
|
|
|
|
2020-03-30 17:25:29 +11:00
|
|
|
_localDb = localDb;
|
|
|
|
|
_filesPath = filesPath;
|
|
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
Instance = this; // For GlobalSetupTeardown.cs
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
_testDatabases = new[]
|
|
|
|
|
{
|
|
|
|
|
// With Schema
|
|
|
|
|
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-1", false),
|
|
|
|
|
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-2", false),
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
// Empty (for migration testing etc)
|
|
|
|
|
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-3", true),
|
|
|
|
|
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-4", true),
|
|
|
|
|
};
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
_localDbInstance = _localDb.GetInstance(InstanceName);
|
|
|
|
|
if (_localDbInstance != null)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
if (_localDb.CreateInstance(InstanceName) == false)
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("Failed to create a LocalDb instance.");
|
|
|
|
|
}
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
_localDbInstance = _localDb.GetInstance(InstanceName);
|
2020-03-30 17:25:29 +11:00
|
|
|
}
|
|
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
protected override void Initialize()
|
2020-03-30 17:25:29 +11:00
|
|
|
{
|
2020-12-12 11:33:57 +00:00
|
|
|
var tempName = Guid.NewGuid().ToString("N");
|
|
|
|
|
_localDbInstance.CreateDatabase(tempName, _filesPath);
|
|
|
|
|
_localDbInstance.DetachDatabase(tempName);
|
|
|
|
|
_prepareQueue = new BlockingCollection<TestDbMeta>();
|
|
|
|
|
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
|
|
|
|
|
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
foreach (var meta in _testDatabases)
|
2020-03-30 17:25:29 +11:00
|
|
|
{
|
2020-12-12 11:33:57 +00:00
|
|
|
_localDb.CopyDatabaseFiles(tempName, _filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: false);
|
|
|
|
|
meta.ConnectionString = _localDbInstance.GetAttachedConnectionString(meta.Name, _filesPath);
|
|
|
|
|
_prepareQueue.Add(meta);
|
2020-03-30 17:25:29 +11:00
|
|
|
}
|
|
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
for (var i = 0; i < _threadCount; i++)
|
|
|
|
|
{
|
|
|
|
|
var thread = new Thread(PrepareDatabase);
|
|
|
|
|
thread.Start();
|
2020-03-30 17:25:29 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
public void Finish()
|
2020-03-30 17:25:29 +11:00
|
|
|
{
|
2020-12-12 11:33:57 +00:00
|
|
|
if (_prepareQueue == null)
|
|
|
|
|
return;
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
_prepareQueue.CompleteAdding();
|
|
|
|
|
while (_prepareQueue.TryTake(out _))
|
|
|
|
|
{ }
|
2020-04-03 13:54:28 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
_readyEmptyQueue.CompleteAdding();
|
|
|
|
|
while (_readyEmptyQueue.TryTake(out _))
|
|
|
|
|
{ }
|
2020-03-30 17:25:29 +11:00
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
_readySchemaQueue.CompleteAdding();
|
|
|
|
|
while (_readySchemaQueue.TryTake(out _))
|
|
|
|
|
{ }
|
2020-03-30 17:25:29 +11:00
|
|
|
|
|
|
|
|
if (_filesPath == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
|
|
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
foreach (var database in _localDbInstance.GetDatabases())
|
2020-03-30 17:25:29 +11:00
|
|
|
{
|
|
|
|
|
if (database.StartsWith(filename))
|
2020-12-12 11:33:57 +00:00
|
|
|
_localDbInstance.DropDatabase(database);
|
2020-03-30 17:25:29 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var file in Directory.EnumerateFiles(_filesPath))
|
|
|
|
|
{
|
|
|
|
|
if (file.EndsWith(".mdf") == false && file.EndsWith(".ldf") == false) continue;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
File.Delete(file);
|
|
|
|
|
}
|
|
|
|
|
catch (IOException)
|
|
|
|
|
{
|
|
|
|
|
// ignore, must still be in use but nothing we can do
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|