using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Persistence;
namespace Umbraco.Tests.Integration.Testing
{
///
/// Manages a pool of LocalDb databases for integration testing
///
public class LocalDbTestDatabase : BaseTestDatabase, ITestDatabase
{
public const string InstanceName = "UmbracoTests";
public const string DatabaseName = "UmbracoTests";
private readonly LocalDb _localDb;
private static LocalDb.Instance _localDbInstance;
private static string _filesPath;
public static LocalDbTestDatabase Instance { get; private set; }
//It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
internal LocalDbTestDatabase(ILoggerFactory loggerFactory, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory)
{
_loggerFactory = loggerFactory;
_databaseFactory = dbFactory;
_localDb = localDb;
_filesPath = filesPath;
Instance = this; // For GlobalSetupTeardown.cs
_testDatabases = new[]
{
// With Schema
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-1", false),
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-2", false),
// Empty (for migration testing etc)
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-3", true),
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-4", true),
};
_localDbInstance = _localDb.GetInstance(InstanceName);
if (_localDbInstance != null)
{
return;
}
if (_localDb.CreateInstance(InstanceName) == false)
{
throw new Exception("Failed to create a LocalDb instance.");
}
_localDbInstance = _localDb.GetInstance(InstanceName);
}
protected override void Initialize()
{
var tempName = Guid.NewGuid().ToString("N");
_localDbInstance.CreateDatabase(tempName, _filesPath);
_localDbInstance.DetachDatabase(tempName);
_prepareQueue = new BlockingCollection();
_readySchemaQueue = new BlockingCollection();
_readyEmptyQueue = new BlockingCollection();
for (var i = 0; i < _testDatabases.Count; i++)
{
var meta = _testDatabases[i];
var isLast = i == _testDatabases.Count - 1;
_localDb.CopyDatabaseFiles(tempName, _filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: isLast);
meta.ConnectionString = _localDbInstance.GetAttachedConnectionString(meta.Name, _filesPath);
_prepareQueue.Add(meta);
}
for (var i = 0; i < _threadCount; i++)
{
var thread = new Thread(PrepareDatabase);
thread.Start();
}
}
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 _))
{ }
if (_filesPath == null)
{
return;
}
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
foreach (var database in _localDbInstance.GetDatabases())
{
if (database.StartsWith(filename))
{
_localDbInstance.DropDatabase(database);
}
}
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
}
}
}
}
}