2020-12-23 11:35:49 +01:00
|
|
|
// Copyright (c) Umbraco.
|
|
|
|
|
// See LICENSE for more details.
|
|
|
|
|
|
2020-12-12 11:33:57 +00:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Data;
|
|
|
|
|
using System.Data.SqlClient;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2022-03-25 08:58:07 +01:00
|
|
|
using Microsoft.Extensions.Options;
|
2021-04-16 11:37:01 +02:00
|
|
|
using Moq;
|
2021-02-09 10:22:42 +01:00
|
|
|
using Umbraco.Cms.Core;
|
|
|
|
|
using Umbraco.Cms.Core.Configuration;
|
2022-03-25 08:58:07 +01:00
|
|
|
using Umbraco.Cms.Core.Configuration.Models;
|
2021-04-16 11:37:01 +02:00
|
|
|
using Umbraco.Cms.Core.Events;
|
2021-02-12 12:40:08 +01:00
|
|
|
using Umbraco.Cms.Infrastructure.Migrations.Install;
|
2021-02-12 13:36:50 +01:00
|
|
|
using Umbraco.Cms.Infrastructure.Persistence;
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2021-02-11 08:30:27 +01:00
|
|
|
namespace Umbraco.Cms.Tests.Integration.Testing
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
|
|
|
|
public abstract class BaseTestDatabase
|
|
|
|
|
{
|
|
|
|
|
protected ILoggerFactory _loggerFactory;
|
|
|
|
|
protected IUmbracoDatabaseFactory _databaseFactory;
|
2020-12-17 11:15:58 +00:00
|
|
|
protected IList<TestDbMeta> _testDatabases;
|
|
|
|
|
|
2020-12-18 14:10:11 +00:00
|
|
|
|
|
|
|
|
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands = new UmbracoDatabase.CommandInfo[0];
|
2020-12-12 11:33:57 +00:00
|
|
|
|
|
|
|
|
protected BlockingCollection<TestDbMeta> _prepareQueue;
|
|
|
|
|
protected BlockingCollection<TestDbMeta> _readySchemaQueue;
|
|
|
|
|
protected BlockingCollection<TestDbMeta> _readyEmptyQueue;
|
|
|
|
|
|
|
|
|
|
protected abstract void Initialize();
|
|
|
|
|
|
|
|
|
|
public TestDbMeta AttachEmpty()
|
|
|
|
|
{
|
|
|
|
|
if (_prepareQueue == null)
|
|
|
|
|
{
|
|
|
|
|
Initialize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _readyEmptyQueue.Take();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public TestDbMeta AttachSchema()
|
|
|
|
|
{
|
|
|
|
|
if (_prepareQueue == null)
|
|
|
|
|
{
|
|
|
|
|
Initialize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return _readySchemaQueue.Take();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Detach(TestDbMeta meta)
|
|
|
|
|
{
|
|
|
|
|
_prepareQueue.TryAdd(meta);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
protected void PrepareDatabase() =>
|
2020-12-12 11:33:57 +00:00
|
|
|
Retry(10, () =>
|
|
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
while (_prepareQueue.IsCompleted == false)
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
TestDbMeta meta;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
meta = _prepareQueue.Take();
|
|
|
|
|
}
|
|
|
|
|
catch (InvalidOperationException)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
using (var conn = new SqlConnection(meta.ConnectionString))
|
|
|
|
|
using (SqlCommand cmd = conn.CreateCommand())
|
|
|
|
|
{
|
|
|
|
|
conn.Open();
|
|
|
|
|
ResetTestDatabase(cmd);
|
|
|
|
|
|
|
|
|
|
if (!meta.IsEmpty)
|
|
|
|
|
{
|
|
|
|
|
RebuildSchema(cmd, meta);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-12 11:33:57 +00:00
|
|
|
|
|
|
|
|
if (!meta.IsEmpty)
|
|
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
_readySchemaQueue.TryAdd(meta);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_readyEmptyQueue.TryAdd(meta);
|
2020-12-12 11:33:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-23 11:35:49 +01:00
|
|
|
});
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-18 14:10:11 +00:00
|
|
|
private void RebuildSchema(IDbCommand command, TestDbMeta meta)
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
2020-12-18 14:10:11 +00:00
|
|
|
lock (_cachedDatabaseInitCommands)
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
2020-12-18 14:10:11 +00:00
|
|
|
if (!_cachedDatabaseInitCommands.Any())
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
RebuildSchemaFirstTime(meta);
|
2020-12-18 14:10:11 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
foreach (UmbracoDatabase.CommandInfo dbCommand in _cachedDatabaseInitCommands)
|
2020-12-18 14:10:11 +00:00
|
|
|
{
|
|
|
|
|
if (dbCommand.Text.StartsWith("SELECT "))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-18 14:10:11 +00:00
|
|
|
command.CommandText = dbCommand.Text;
|
|
|
|
|
command.Parameters.Clear();
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
foreach (UmbracoDatabase.ParameterInfo parameterInfo in dbCommand.Parameters)
|
2020-12-18 14:10:11 +00:00
|
|
|
{
|
|
|
|
|
AddParameter(command, parameterInfo);
|
2020-12-12 11:33:57 +00:00
|
|
|
}
|
2020-12-18 14:10:11 +00:00
|
|
|
|
|
|
|
|
command.ExecuteNonQuery();
|
2020-12-12 11:33:57 +00:00
|
|
|
}
|
2020-12-18 14:10:11 +00:00
|
|
|
}
|
|
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
private void RebuildSchemaFirstTime(TestDbMeta meta)
|
2020-12-18 14:10:11 +00:00
|
|
|
{
|
2021-02-09 10:22:42 +01:00
|
|
|
_databaseFactory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
2020-12-18 14:10:11 +00:00
|
|
|
|
|
|
|
|
using (var database = (UmbracoDatabase)_databaseFactory.CreateDatabase())
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
2020-12-18 14:10:11 +00:00
|
|
|
database.LogCommands = true;
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
using (NPoco.ITransaction transaction = database.GetTransaction())
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
2022-03-25 08:58:07 +01:00
|
|
|
var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, new UmbracoVersion(), Mock.Of<IEventAggregator>(), Mock.Of<IOptionsMonitor<InstallDefaultDataSettings>>(x => x.CurrentValue == new InstallDefaultDataSettings()));
|
2020-12-18 14:10:11 +00:00
|
|
|
schemaCreator.InitializeDatabaseSchema();
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-18 14:10:11 +00:00
|
|
|
transaction.Complete();
|
2020-12-12 11:33:57 +00:00
|
|
|
|
2020-12-18 14:10:11 +00:00
|
|
|
_cachedDatabaseInitCommands = database.Commands.ToArray();
|
2020-12-12 11:33:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static void SetCommand(SqlCommand command, string sql, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
command.CommandType = CommandType.Text;
|
|
|
|
|
command.CommandText = sql;
|
|
|
|
|
command.Parameters.Clear();
|
|
|
|
|
|
2020-12-23 11:35:49 +01:00
|
|
|
for (int i = 0; i < args.Length; i++)
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
|
|
|
|
command.Parameters.AddWithValue("@" + i, args[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
|
|
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
IDbDataParameter p = cmd.CreateParameter();
|
2020-12-12 11:33:57 +00:00
|
|
|
p.ParameterName = parameterInfo.Name;
|
|
|
|
|
p.Value = parameterInfo.Value;
|
|
|
|
|
p.DbType = parameterInfo.DbType;
|
|
|
|
|
p.Size = parameterInfo.Size;
|
|
|
|
|
cmd.Parameters.Add(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static void ResetTestDatabase(IDbCommand cmd)
|
|
|
|
|
{
|
|
|
|
|
// https://stackoverflow.com/questions/536350
|
|
|
|
|
cmd.CommandType = CommandType.Text;
|
|
|
|
|
cmd.CommandText = @"
|
|
|
|
|
declare @n char(1);
|
|
|
|
|
set @n = char(10);
|
|
|
|
|
declare @stmt nvarchar(max);
|
|
|
|
|
-- check constraints
|
|
|
|
|
select @stmt = isnull( @stmt + @n, '' ) +
|
|
|
|
|
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
|
|
|
|
|
from sys.check_constraints;
|
|
|
|
|
-- foreign keys
|
|
|
|
|
select @stmt = isnull( @stmt + @n, '' ) +
|
|
|
|
|
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
|
|
|
|
|
from sys.foreign_keys;
|
|
|
|
|
-- tables
|
|
|
|
|
select @stmt = isnull( @stmt + @n, '' ) +
|
|
|
|
|
'drop table [' + schema_name(schema_id) + '].[' + name + ']'
|
|
|
|
|
from sys.tables;
|
|
|
|
|
exec sp_executesql @stmt;
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
// rudimentary retry policy since a db can still be in use when we try to drop
|
|
|
|
|
Retry(10, () => cmd.ExecuteNonQuery());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected static void Retry(int maxIterations, Action action)
|
|
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
for (int i = 0; i < maxIterations; i++)
|
2020-12-12 11:33:57 +00:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
action();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
catch (SqlException)
|
|
|
|
|
{
|
2020-12-23 11:35:49 +01:00
|
|
|
// Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
|
2020-12-12 11:33:57 +00:00
|
|
|
// This can occur when there's a transaction deadlock which means (i think) that the database is still in use and hasn't been closed properly yet
|
|
|
|
|
// so we need to just wait a little bit
|
|
|
|
|
Thread.Sleep(100 * i);
|
|
|
|
|
if (i == maxIterations - 1)
|
|
|
|
|
{
|
|
|
|
|
Debugger.Launch();
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (InvalidOperationException)
|
|
|
|
|
{
|
|
|
|
|
// Ignore
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|