Merge remote-tracking branch 'origin/netcore/netcore' into netcore/netcore
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -43,6 +43,7 @@
|
||||
*.hs text=auto
|
||||
*.json text=auto
|
||||
*.xml text=auto
|
||||
*.resx text=auto
|
||||
|
||||
*.csproj text=auto merge=union
|
||||
*.vbproj text=auto merge=union
|
||||
|
||||
@@ -8,6 +8,19 @@
|
||||
# Variables & their default values
|
||||
variables:
|
||||
buildConfiguration: 'Release'
|
||||
SA_PASSWORD: UmbracoIntegration123!
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: mssql
|
||||
image: mcr.microsoft.com/mssql/server:2017-latest
|
||||
env:
|
||||
ACCEPT_EULA: Y
|
||||
SA_PASSWORD: $(SA_PASSWORD)
|
||||
MSSQL_PID: Developer
|
||||
ports:
|
||||
- 1433:1433
|
||||
options: --name mssql
|
||||
|
||||
stages:
|
||||
- stage: Linux
|
||||
@@ -31,27 +44,48 @@ stages:
|
||||
command: test
|
||||
projects: '**/*.Tests.UnitTests.csproj'
|
||||
|
||||
- stage: macOS_X
|
||||
- job: Integration_Tests
|
||||
services:
|
||||
mssql: mssql
|
||||
timeoutInMinutes: 120
|
||||
displayName: 'Integration Tests'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .Net Core sdk 3.1.x'
|
||||
inputs:
|
||||
version: 3.1.x
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'dotnet test'
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/Umbraco.Tests.Integration.csproj'
|
||||
env:
|
||||
UmbracoIntegrationTestConnectionString: 'Server=localhost,1433;User Id=sa;Password=$(SA_PASSWORD);'
|
||||
|
||||
- stage: MacOS
|
||||
dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel
|
||||
jobs:
|
||||
|
||||
- job: Unit_Tests
|
||||
displayName: 'Unit Tests'
|
||||
pool:
|
||||
vmImage: 'macOS-latest'
|
||||
steps:
|
||||
- job: Unit_Tests
|
||||
displayName: 'Unit Tests'
|
||||
pool:
|
||||
vmImage: 'macOS-latest'
|
||||
steps:
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .Net Core sdk 3.1.x'
|
||||
inputs:
|
||||
version: 3.1.x
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'dotnet test'
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.UnitTests.csproj'
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .Net Core sdk 3.1.x'
|
||||
inputs:
|
||||
version: 3.1.x
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'dotnet test'
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**/*.Tests.UnitTests.csproj'
|
||||
|
||||
- stage: Windows
|
||||
dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel
|
||||
@@ -74,7 +108,6 @@ stages:
|
||||
command: test
|
||||
projects: '**\*.Tests.UnitTests.csproj'
|
||||
|
||||
|
||||
- job: Integration_Tests
|
||||
timeoutInMinutes: 120
|
||||
displayName: 'Integration Tests'
|
||||
@@ -87,11 +120,6 @@ stages:
|
||||
inputs:
|
||||
version: 3.1.x
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'dotnet build'
|
||||
inputs:
|
||||
projects: '**\Umbraco.Tests.Integration.csproj'
|
||||
|
||||
- powershell: 'sqllocaldb start mssqllocaldb'
|
||||
displayName: 'Start MSSQL LocalDb'
|
||||
|
||||
@@ -100,8 +128,6 @@ stages:
|
||||
inputs:
|
||||
command: test
|
||||
projects: '**\Umbraco.Tests.Integration.csproj'
|
||||
arguments: '--no-build'
|
||||
|
||||
|
||||
- job: Build_Artifacts
|
||||
displayName: 'Build Artifacts'
|
||||
|
||||
@@ -937,7 +937,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// This is a C# implementation of T-SQL QUOTEDNAME.
|
||||
/// <paramref name="quote"/> is optional, it can be '[' (default), ']', '\'' or '"'.
|
||||
/// </remarks>
|
||||
private static string QuotedName(string name, char quote = '[')
|
||||
internal static string QuotedName(string name, char quote = '[')
|
||||
{
|
||||
switch (quote)
|
||||
{
|
||||
|
||||
@@ -327,16 +327,5 @@ namespace Umbraco.Core.Persistence
|
||||
//db?.Dispose();
|
||||
Volatile.Write(ref _initialized, false);
|
||||
}
|
||||
|
||||
// during tests, the thread static var can leak between tests
|
||||
// this method provides a way to force-reset the variable
|
||||
internal void ResetForTests()
|
||||
{
|
||||
// TODO: remove all this eventually
|
||||
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
|
||||
//_umbracoDatabaseAccessor.UmbracoDatabase = null;
|
||||
//db?.Dispose();
|
||||
//_databaseScopeAccessor.Scope = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
@@ -23,7 +23,8 @@ public class TestsSetup
|
||||
[OneTimeTearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
LocalDbTestDatabase.KillLocalDb();
|
||||
LocalDbTestDatabase.Instance?.Finish();
|
||||
SqlDeveloperTestDatabase.Instance?.Finish();
|
||||
Console.WriteLine("TOTAL TESTS DURATION: {0}", _stopwatch.Elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +132,12 @@ namespace Umbraco.Tests.Integration.TestServerTest
|
||||
|
||||
public override void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
|
||||
var typeLoader = services.AddTypeLoader(GetType().Assembly, TestHelper.GetWebHostEnvironment(), TestHelper.GetHostingEnvironment(),
|
||||
TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, Configuration, TestHelper.Profiler);
|
||||
|
||||
var builder = new UmbracoBuilder(services, Configuration, typeLoader);
|
||||
|
||||
|
||||
builder
|
||||
.AddConfiguration()
|
||||
.AddTestCore(TestHelper) // This is the important one!
|
||||
|
||||
218
src/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs
Normal file
218
src/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
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;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
public abstract class BaseTestDatabase
|
||||
{
|
||||
protected ILoggerFactory _loggerFactory;
|
||||
protected IUmbracoDatabaseFactory _databaseFactory;
|
||||
protected IEnumerable<TestDbMeta> _testDatabases;
|
||||
|
||||
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected void PrepareDatabase()
|
||||
{
|
||||
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();
|
||||
ResetTestDatabase(cmd);
|
||||
|
||||
if (!meta.IsEmpty)
|
||||
{
|
||||
RebuildSchema(cmd, meta);
|
||||
}
|
||||
}
|
||||
|
||||
if (!meta.IsEmpty)
|
||||
{
|
||||
_readySchemaQueue.TryAdd(meta);
|
||||
}
|
||||
else
|
||||
{
|
||||
_readyEmptyQueue.TryAdd(meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void RebuildSchema(IDbCommand command, TestDbMeta meta)
|
||||
{
|
||||
if (_cachedDatabaseInitCommands != null)
|
||||
{
|
||||
foreach (var dbCommand in _cachedDatabaseInitCommands)
|
||||
{
|
||||
|
||||
if (dbCommand.Text.StartsWith("SELECT "))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
command.CommandText = dbCommand.Text;
|
||||
command.Parameters.Clear();
|
||||
|
||||
foreach (var parameterInfo in dbCommand.Parameters)
|
||||
{
|
||||
AddParameter(command, parameterInfo);
|
||||
}
|
||||
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseFactory.Configure(meta.ConnectionString, Core.Constants.DatabaseProviders.SqlServer);
|
||||
|
||||
using (var database = (UmbracoDatabase)_databaseFactory.CreateDatabase())
|
||||
{
|
||||
database.LogCommands = true;
|
||||
|
||||
using (var transaction = database.GetTransaction())
|
||||
{
|
||||
var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, new UmbracoVersion());
|
||||
schemaCreator.InitializeDatabaseSchema();
|
||||
|
||||
transaction.Complete();
|
||||
|
||||
_cachedDatabaseInitCommands = database.Commands.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void SetCommand(SqlCommand command, string sql, params object[] args)
|
||||
{
|
||||
command.CommandType = CommandType.Text;
|
||||
command.CommandText = sql;
|
||||
command.Parameters.Clear();
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
command.Parameters.AddWithValue("@" + i, args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
|
||||
{
|
||||
var p = cmd.CreateParameter();
|
||||
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)
|
||||
{
|
||||
for (var i = 0; i < maxIterations; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
catch (SqlException)
|
||||
{
|
||||
|
||||
//Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Umbraco.Tests.Integration/Testing/ITestDatabase.cs
Normal file
9
src/Umbraco.Tests.Integration/Testing/ITestDatabase.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
public interface ITestDatabase
|
||||
{
|
||||
TestDbMeta AttachEmpty();
|
||||
TestDbMeta AttachSchema();
|
||||
void Detach(TestDbMeta id);
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,10 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
// we don't want persisted nucache files in tests
|
||||
builder.Services.AddTransient(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true });
|
||||
|
||||
#if IS_WINDOWS
|
||||
// ensure all lucene indexes are using RAM directory (no file system)
|
||||
builder.Services.AddUnique<ILuceneDirectoryFactory, LuceneRAMDirectoryFactory>();
|
||||
#endif
|
||||
|
||||
// replace this service so that it can lookup the correct file locations
|
||||
builder.Services.AddUnique<ILocalizedTextService>(GetLocalizedTextService);
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
@@ -20,197 +10,104 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
/// <summary>
|
||||
/// Manages a pool of LocalDb databases for integration testing
|
||||
/// </summary>
|
||||
public class LocalDbTestDatabase
|
||||
public class LocalDbTestDatabase : BaseTestDatabase, ITestDatabase
|
||||
{
|
||||
public const string InstanceName = "UmbracoTests";
|
||||
public const string DatabaseName = "UmbracoTests";
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly LocalDb _localDb;
|
||||
private readonly IUmbracoVersion _umbracoVersion;
|
||||
private static LocalDb.Instance _instance;
|
||||
private static LocalDb.Instance _localDbInstance;
|
||||
private static string _filesPath;
|
||||
private readonly IUmbracoDatabaseFactory _dbFactory;
|
||||
private UmbracoDatabase.CommandInfo[] _dbCommands;
|
||||
private string _currentCstr;
|
||||
private static DatabasePool _emptyPool;
|
||||
private static DatabasePool _schemaPool;
|
||||
private DatabasePool _currentPool;
|
||||
|
||||
private const int _threadCount = 2;
|
||||
|
||||
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)
|
||||
{
|
||||
_umbracoVersion = new UmbracoVersion();
|
||||
_loggerFactory = loggerFactory;
|
||||
_databaseFactory = dbFactory;
|
||||
|
||||
_localDb = localDb;
|
||||
_filesPath = filesPath;
|
||||
_dbFactory = dbFactory;
|
||||
|
||||
_instance = _localDb.GetInstance(InstanceName);
|
||||
if (_instance != null) return;
|
||||
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.");
|
||||
_instance = _localDb.GetInstance(InstanceName);
|
||||
}
|
||||
|
||||
_localDbInstance = _localDb.GetInstance(InstanceName);
|
||||
}
|
||||
|
||||
public string ConnectionString => _currentCstr ?? _instance.GetAttachedConnectionString("XXXXXX", _filesPath);
|
||||
|
||||
private void Create()
|
||||
protected override void Initialize()
|
||||
{
|
||||
var tempName = Guid.NewGuid().ToString("N");
|
||||
_instance.CreateDatabase(tempName, _filesPath);
|
||||
_instance.DetachDatabase(tempName);
|
||||
_localDbInstance.CreateDatabase(tempName, _filesPath);
|
||||
_localDbInstance.DetachDatabase(tempName);
|
||||
_prepareQueue = new BlockingCollection<TestDbMeta>();
|
||||
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
|
||||
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
|
||||
|
||||
// there's probably a sweet spot to be found for size / parallel...
|
||||
|
||||
var s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.EmptyPoolSize"];
|
||||
var emptySize = s == null ? 1 : int.Parse(s);
|
||||
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.EmptyPoolThreadCount"];
|
||||
var emptyParallel = s == null ? 1 : int.Parse(s);
|
||||
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.SchemaPoolSize"];
|
||||
var schemaSize = s == null ? 1 : int.Parse(s);
|
||||
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.SchemaPoolThreadCount"];
|
||||
var schemaParallel = s == null ? 1 : int.Parse(s);
|
||||
|
||||
_emptyPool = new DatabasePool(_localDb, _instance, DatabaseName + "-Empty", tempName, _filesPath, emptySize, emptyParallel);
|
||||
_schemaPool = new DatabasePool(_localDb, _instance, DatabaseName + "-Schema", tempName, _filesPath, schemaSize, schemaParallel, delete: true, prepare: RebuildSchema);
|
||||
}
|
||||
|
||||
public int AttachEmpty()
|
||||
{
|
||||
if (_emptyPool == null)
|
||||
Create();
|
||||
|
||||
_currentCstr = _emptyPool.AttachDatabase(out var id);
|
||||
_currentPool = _emptyPool;
|
||||
return id;
|
||||
}
|
||||
|
||||
public int AttachSchema()
|
||||
{
|
||||
if (_schemaPool == null)
|
||||
Create();
|
||||
|
||||
_currentCstr = _schemaPool.AttachDatabase(out var id);
|
||||
_currentPool = _schemaPool;
|
||||
return id;
|
||||
}
|
||||
|
||||
public void Detach(int id)
|
||||
{
|
||||
_currentPool.DetachDatabase(id);
|
||||
}
|
||||
|
||||
private void RebuildSchema(DbConnection conn, IDbCommand cmd)
|
||||
{
|
||||
|
||||
if (_dbCommands != null)
|
||||
foreach (var meta in _testDatabases)
|
||||
{
|
||||
foreach (var dbCommand in _dbCommands)
|
||||
{
|
||||
|
||||
if (dbCommand.Text.StartsWith("SELECT ")) continue;
|
||||
|
||||
cmd.CommandText = dbCommand.Text;
|
||||
cmd.Parameters.Clear();
|
||||
foreach (var parameterInfo in dbCommand.Parameters)
|
||||
AddParameter(cmd, parameterInfo);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_dbFactory.Configure(conn.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
|
||||
using var database = (UmbracoDatabase)_dbFactory.CreateDatabase();
|
||||
// track each db command ran as part of creating the database so we can replay these
|
||||
database.LogCommands = true;
|
||||
|
||||
using var trans = database.GetTransaction();
|
||||
|
||||
var creator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, _umbracoVersion);
|
||||
creator.InitializeDatabaseSchema();
|
||||
|
||||
trans.Complete(); // commit it
|
||||
|
||||
_dbCommands = database.Commands.ToArray();
|
||||
_localDb.CopyDatabaseFiles(tempName, _filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: false);
|
||||
meta.ConnectionString = _localDbInstance.GetAttachedConnectionString(meta.Name, _filesPath);
|
||||
_prepareQueue.Add(meta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
|
||||
{
|
||||
var p = cmd.CreateParameter();
|
||||
p.ParameterName = parameterInfo.Name;
|
||||
p.Value = parameterInfo.Value;
|
||||
p.DbType = parameterInfo.DbType;
|
||||
p.Size = parameterInfo.Size;
|
||||
cmd.Parameters.Add(p);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
|
||||
|
||||
foreach (var database in _instance.GetDatabases())
|
||||
for (var i = 0; i < _threadCount; i++)
|
||||
{
|
||||
if (database.StartsWith(filename))
|
||||
_instance.DropDatabase(database);
|
||||
}
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(_filesPath))
|
||||
{
|
||||
if (file.EndsWith(".mdf") == false && file.EndsWith(".ldf") == false) continue;
|
||||
File.Delete(file);
|
||||
var thread = new Thread(PrepareDatabase);
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResetLocalDb(IDbCommand cmd)
|
||||
public void Finish()
|
||||
{
|
||||
// https://stackoverflow.com/questions/536350
|
||||
if (_prepareQueue == null)
|
||||
return;
|
||||
|
||||
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;
|
||||
";
|
||||
_prepareQueue.CompleteAdding();
|
||||
while (_prepareQueue.TryTake(out _))
|
||||
{ }
|
||||
|
||||
// rudimentary retry policy since a db can still be in use when we try to drop
|
||||
Retry(10, () =>
|
||||
{
|
||||
cmd.ExecuteNonQuery();
|
||||
});
|
||||
}
|
||||
_readyEmptyQueue.CompleteAdding();
|
||||
while (_readyEmptyQueue.TryTake(out _))
|
||||
{ }
|
||||
|
||||
public static void KillLocalDb()
|
||||
{
|
||||
_emptyPool?.Stop();
|
||||
_schemaPool?.Stop();
|
||||
_readySchemaQueue.CompleteAdding();
|
||||
while (_readySchemaQueue.TryTake(out _))
|
||||
{ }
|
||||
|
||||
if (_filesPath == null)
|
||||
return;
|
||||
|
||||
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
|
||||
|
||||
foreach (var database in _instance.GetDatabases())
|
||||
foreach (var database in _localDbInstance.GetDatabases())
|
||||
{
|
||||
if (database.StartsWith(filename))
|
||||
_instance.DropDatabase(database);
|
||||
_localDbInstance.DropDatabase(database);
|
||||
}
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(_filesPath))
|
||||
@@ -226,145 +123,5 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Retry(int maxIterations, Action action)
|
||||
{
|
||||
for (var i = 0; i < maxIterations; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
catch (SqlException)
|
||||
{
|
||||
|
||||
//Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
|
||||
// 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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DatabasePool
|
||||
{
|
||||
private readonly LocalDb _localDb;
|
||||
private readonly LocalDb.Instance _instance;
|
||||
private readonly string _filesPath;
|
||||
private readonly string _name;
|
||||
private readonly int _size;
|
||||
private readonly string[] _cstrs;
|
||||
private readonly BlockingCollection<int> _prepareQueue, _readyQueue;
|
||||
private readonly Action<DbConnection, IDbCommand> _prepare;
|
||||
private int _current;
|
||||
|
||||
public DatabasePool(LocalDb localDb, LocalDb.Instance instance, string name, string tempName, string filesPath, int size, int parallel = 1, Action<DbConnection, IDbCommand> prepare = null, bool delete = false)
|
||||
{
|
||||
_localDb = localDb;
|
||||
_instance = instance;
|
||||
_filesPath = filesPath;
|
||||
_name = name;
|
||||
_size = size;
|
||||
_prepare = prepare;
|
||||
_prepareQueue = new BlockingCollection<int>();
|
||||
_readyQueue = new BlockingCollection<int>();
|
||||
_cstrs = new string[_size];
|
||||
|
||||
for (var i = 0; i < size; i++)
|
||||
localDb.CopyDatabaseFiles(tempName, filesPath, targetDatabaseName: name + "-" + i, overwrite: true, delete: delete && i == size - 1);
|
||||
|
||||
if (prepare == null)
|
||||
{
|
||||
for (var i = 0; i < size; i++)
|
||||
_readyQueue.Add(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < size; i++)
|
||||
_prepareQueue.Add(i);
|
||||
}
|
||||
|
||||
for (var i = 0; i < parallel; i++)
|
||||
{
|
||||
var thread = new Thread(PrepareThread);
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public string AttachDatabase(out int id)
|
||||
{
|
||||
_current = _readyQueue.Take();
|
||||
id = _current;
|
||||
|
||||
return ConnectionString(_current);
|
||||
}
|
||||
|
||||
public void DetachDatabase(int id)
|
||||
{
|
||||
if (id != _current)
|
||||
throw new InvalidOperationException("Cannot detatch the non-current db");
|
||||
|
||||
_prepareQueue.Add(_current);
|
||||
}
|
||||
|
||||
private string ConnectionString(int i)
|
||||
{
|
||||
return _cstrs[i] ?? (_cstrs[i] = _instance.GetAttachedConnectionString(_name + "-" + i, _filesPath));
|
||||
}
|
||||
|
||||
private void PrepareThread()
|
||||
{
|
||||
Retry(10, () =>
|
||||
{
|
||||
while (_prepareQueue.IsCompleted == false)
|
||||
{
|
||||
int i;
|
||||
try
|
||||
{
|
||||
i = _prepareQueue.Take();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using (var conn = new SqlConnection(ConnectionString(i)))
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
conn.Open();
|
||||
ResetLocalDb(cmd);
|
||||
|
||||
_prepare?.Invoke(conn, cmd);
|
||||
|
||||
}
|
||||
|
||||
if (!_readyQueue.IsAddingCompleted)
|
||||
{
|
||||
_readyQueue.Add(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
int i;
|
||||
_prepareQueue.CompleteAdding();
|
||||
while (_prepareQueue.TryTake(out i)) { }
|
||||
_readyQueue.CompleteAdding();
|
||||
while (_readyQueue.TryTake(out i)) { }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
// ReSharper disable ConvertToUsingDeclaration
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
/// <remarks>
|
||||
/// It's not meant to be pretty, rushed port of LocalDb.cs + LocalDbTestDatabase.cs
|
||||
/// </remarks>
|
||||
public class SqlDeveloperTestDatabase : BaseTestDatabase, ITestDatabase
|
||||
{
|
||||
private readonly string _masterConnectionString;
|
||||
public const string DatabaseName = "UmbracoTests";
|
||||
|
||||
private const int _threadCount = 2;
|
||||
|
||||
public static SqlDeveloperTestDatabase Instance { get; private set; }
|
||||
|
||||
public SqlDeveloperTestDatabase(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory databaseFactory, string masterConnectionString)
|
||||
{
|
||||
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
_databaseFactory = databaseFactory ?? throw new ArgumentNullException(nameof(databaseFactory));
|
||||
|
||||
_masterConnectionString = masterConnectionString;
|
||||
|
||||
_testDatabases = new[]
|
||||
{
|
||||
// With Schema
|
||||
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-1", false, masterConnectionString),
|
||||
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-2", false, masterConnectionString),
|
||||
|
||||
// Empty (for migration testing etc)
|
||||
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-3", true, masterConnectionString),
|
||||
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-4", true, masterConnectionString),
|
||||
};
|
||||
|
||||
Instance = this; // For GlobalSetupTeardown.cs
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
_prepareQueue = new BlockingCollection<TestDbMeta>();
|
||||
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
|
||||
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
|
||||
|
||||
foreach (var meta in _testDatabases)
|
||||
{
|
||||
CreateDatabase(meta);
|
||||
_prepareQueue.Add(meta);
|
||||
}
|
||||
|
||||
for (var i = 0; i < _threadCount; i++)
|
||||
{
|
||||
var thread = new Thread(PrepareDatabase);
|
||||
thread.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDatabase(TestDbMeta meta)
|
||||
{
|
||||
using (var connection = new SqlConnection(_masterConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
SetCommand(command, $@"CREATE DATABASE {LocalDb.QuotedName(meta.Name)}");
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Drop(TestDbMeta meta)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Drop(testDatabase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs
Normal file
43
src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
public class TestDatabaseFactory
|
||||
{
|
||||
public static ITestDatabase Create(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
|
||||
{
|
||||
return string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString"))
|
||||
? CreateLocalDb(filesPath, loggerFactory, dbFactory.Create())
|
||||
: CreateSqlDeveloper(loggerFactory, dbFactory.Create());
|
||||
}
|
||||
|
||||
private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
|
||||
{
|
||||
var localDb = new LocalDb();
|
||||
|
||||
if (!localDb.IsAvailable)
|
||||
{
|
||||
throw new InvalidOperationException("LocalDB is not available.");
|
||||
}
|
||||
|
||||
return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
|
||||
}
|
||||
|
||||
private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
|
||||
{
|
||||
// $ export SA_PASSWORD=Foobar123!
|
||||
// $ export UmbracoIntegrationTestConnectionString="Server=localhost,1433;User Id=sa;Password=$SA_PASSWORD;"
|
||||
// $ docker run -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=$SA_PASSWORD" -e 'MSSQL_PID=Developer' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
|
||||
var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new InvalidOperationException("ENV: UmbracoIntegrationTestConnectionString is not set");
|
||||
}
|
||||
|
||||
return new SqlDeveloperTestDatabase(loggerFactory, dbFactory, connectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Umbraco.Tests.Integration/Testing/TestDbMeta.cs
Normal file
39
src/Umbraco.Tests.Integration/Testing/TestDbMeta.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
public class TestDbMeta
|
||||
{
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public bool IsEmpty { get; }
|
||||
|
||||
public string ConnectionString { get; set; }
|
||||
|
||||
private TestDbMeta(string name, bool isEmpty, string connectionString)
|
||||
{
|
||||
IsEmpty = isEmpty;
|
||||
Name = name;
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
|
||||
private static string ConstructConnectionString(string masterConnectionString, string databaseName)
|
||||
{
|
||||
var prefix = Regex.Replace(masterConnectionString, "Database=.+?;", string.Empty);
|
||||
var connectionString = $"{prefix};Database={databaseName};";
|
||||
return connectionString.Replace(";;", ";");
|
||||
}
|
||||
|
||||
public static TestDbMeta CreateWithMasterConnectionString(string name, bool isEmpty, string masterConnectionString)
|
||||
{
|
||||
return new TestDbMeta(name, isEmpty, ConstructConnectionString(masterConnectionString, name));
|
||||
}
|
||||
|
||||
// LocalDb mdf funtimes
|
||||
public static TestDbMeta CreateWithoutConnectionString(string name, bool isEmpty)
|
||||
{
|
||||
return new TestDbMeta(name, isEmpty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
|
||||
namespace Umbraco.Tests.Integration.Testing
|
||||
{
|
||||
/// <summary>
|
||||
/// I want to be able to create a database for integration testsing without setting the connection string on the
|
||||
/// singleton database factory forever.
|
||||
/// </summary>
|
||||
public class TestUmbracoDatabaseFactoryProvider
|
||||
{
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly IOptions<GlobalSettings> _globalSettings;
|
||||
private readonly IOptions<ConnectionStrings> _connectionStrings;
|
||||
private readonly Lazy<IMapperCollection> _mappers;
|
||||
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
|
||||
|
||||
public TestUmbracoDatabaseFactoryProvider(
|
||||
ILoggerFactory loggerFactory,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
IOptions<ConnectionStrings> connectionStrings,
|
||||
Lazy<IMapperCollection> mappers,
|
||||
IDbProviderFactoryCreator dbProviderFactoryCreator)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_globalSettings = globalSettings;
|
||||
_connectionStrings = connectionStrings;
|
||||
_mappers = mappers;
|
||||
_dbProviderFactoryCreator = dbProviderFactoryCreator;
|
||||
}
|
||||
|
||||
public IUmbracoDatabaseFactory Create()
|
||||
{
|
||||
// ReSharper disable once ArrangeMethodOrOperatorBody
|
||||
return new UmbracoDatabaseFactory(
|
||||
_loggerFactory.CreateLogger<UmbracoDatabaseFactory>(),
|
||||
_loggerFactory,
|
||||
_globalSettings.Value,
|
||||
_connectionStrings.Value,
|
||||
_mappers,
|
||||
_dbProviderFactoryCreator);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,7 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
public virtual void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton(TestHelper.DbProviderFactoryCreator);
|
||||
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
|
||||
var webHostEnvironment = TestHelper.GetWebHostEnvironment();
|
||||
services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
|
||||
|
||||
@@ -242,17 +243,17 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
#region LocalDb
|
||||
|
||||
private static readonly object _dbLocker = new object();
|
||||
private static LocalDbTestDatabase _dbInstance;
|
||||
private static ITestDatabase _dbInstance;
|
||||
private static TestDbMeta _fixtureDbMeta;
|
||||
|
||||
protected void UseTestLocalDb(IServiceProvider serviceProvider)
|
||||
{
|
||||
var state = serviceProvider.GetRequiredService<IRuntimeState>();
|
||||
var testDatabaseFactoryProvider = serviceProvider.GetRequiredService<TestUmbracoDatabaseFactoryProvider>();
|
||||
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);
|
||||
TestDBConnectionString = databaseFactory.ConnectionString;
|
||||
InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString;
|
||||
InstallTestLocalDb(testDatabaseFactoryProvider, databaseFactory, serviceProvider.GetRequiredService<ILoggerFactory>(), state, TestHelper.WorkingDirectory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -267,17 +268,14 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
/// <remarks>
|
||||
/// There must only be ONE instance shared between all tests in a session
|
||||
/// </remarks>
|
||||
private static LocalDbTestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
|
||||
private static ITestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
|
||||
{
|
||||
lock (_dbLocker)
|
||||
{
|
||||
if (_dbInstance != null)
|
||||
return _dbInstance;
|
||||
|
||||
var localDb = new LocalDb();
|
||||
if (localDb.IsAvailable == false)
|
||||
throw new InvalidOperationException("LocalDB is not available.");
|
||||
_dbInstance = new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
|
||||
_dbInstance = TestDatabaseFactory.Create(filesPath, loggerFactory, dbFactory);
|
||||
return _dbInstance;
|
||||
}
|
||||
}
|
||||
@@ -285,16 +283,12 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
/// <summary>
|
||||
/// Creates a LocalDb instance to use for the test
|
||||
/// </summary>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="workingDirectory"></param>
|
||||
/// <param name="connectionString"></param>
|
||||
/// <param name="databaseFactory"></param>
|
||||
/// <param name="loggerFactory"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <returns></returns>
|
||||
private void InstallTestLocalDb(
|
||||
IUmbracoDatabaseFactory databaseFactory, ILoggerFactory loggerFactory,
|
||||
IRuntimeState runtimeState, string workingDirectory)
|
||||
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
|
||||
IUmbracoDatabaseFactory databaseFactory,
|
||||
ILoggerFactory loggerFactory,
|
||||
IRuntimeState runtimeState,
|
||||
string workingDirectory)
|
||||
{
|
||||
var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
|
||||
|
||||
@@ -310,22 +304,22 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
if (!Directory.Exists(dbFilePath))
|
||||
Directory.CreateDirectory(dbFilePath);
|
||||
|
||||
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, databaseFactory);
|
||||
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider);
|
||||
|
||||
switch (testOptions.Database)
|
||||
{
|
||||
case UmbracoTestOptions.Database.NewSchemaPerTest:
|
||||
|
||||
// New DB + Schema
|
||||
var newSchemaDbId = db.AttachSchema();
|
||||
var newSchemaDbMeta = db.AttachSchema();
|
||||
|
||||
// Add teardown callback
|
||||
OnTestTearDown(() => db.Detach(newSchemaDbId));
|
||||
OnTestTearDown(() => db.Detach(newSchemaDbMeta));
|
||||
|
||||
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
|
||||
if (!databaseFactory.Configured)
|
||||
{
|
||||
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
databaseFactory.Configure(newSchemaDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
}
|
||||
|
||||
// re-run the runtime level check
|
||||
@@ -335,15 +329,15 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
break;
|
||||
case UmbracoTestOptions.Database.NewEmptyPerTest:
|
||||
var newEmptyDbId = db.AttachEmpty();
|
||||
var newEmptyDbMeta = db.AttachEmpty();
|
||||
|
||||
// Add teardown callback
|
||||
OnTestTearDown(() => db.Detach(newEmptyDbId));
|
||||
OnTestTearDown(() => db.Detach(newEmptyDbMeta));
|
||||
|
||||
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
|
||||
if (!databaseFactory.Configured)
|
||||
{
|
||||
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
databaseFactory.Configure(newEmptyDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
}
|
||||
|
||||
// re-run the runtime level check
|
||||
@@ -359,16 +353,17 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
if (FirstTestInFixture)
|
||||
{
|
||||
// New DB + Schema
|
||||
var newSchemaFixtureDbId = db.AttachSchema();
|
||||
var newSchemaFixtureDbMeta = db.AttachSchema();
|
||||
_fixtureDbMeta = newSchemaFixtureDbMeta;
|
||||
|
||||
// Add teardown callback
|
||||
OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId));
|
||||
OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta));
|
||||
}
|
||||
|
||||
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
|
||||
if (!databaseFactory.Configured)
|
||||
{
|
||||
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
databaseFactory.Configure(_fixtureDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
}
|
||||
|
||||
// re-run the runtime level check
|
||||
@@ -382,16 +377,17 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
if (FirstTestInFixture)
|
||||
{
|
||||
// New DB + Schema
|
||||
var newEmptyFixtureDbId = db.AttachEmpty();
|
||||
var newEmptyFixtureDbMeta = db.AttachEmpty();
|
||||
_fixtureDbMeta = newEmptyFixtureDbMeta;
|
||||
|
||||
// Add teardown callback
|
||||
OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbId));
|
||||
OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta));
|
||||
}
|
||||
|
||||
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
|
||||
if (!databaseFactory.Configured)
|
||||
{
|
||||
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
databaseFactory.Configure(_fixtureDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -412,8 +408,6 @@ namespace Umbraco.Tests.Integration.Testing
|
||||
|
||||
public TestHelper TestHelper = new TestHelper();
|
||||
|
||||
protected virtual string TestDBConnectionString { get; private set; }
|
||||
|
||||
protected virtual Action<IServiceCollection> CustomTestSetup => services => { };
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Tests.Testing;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Tests.Integration.Testing;
|
||||
|
||||
@@ -55,28 +56,28 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor
|
||||
partialView = new PartialView(PartialViewType.PartialView, "path-2/test-path-2.cshtml") { Content = "// partialView" };
|
||||
repository.Save(partialView);
|
||||
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.cshtml"));
|
||||
Assert.AreEqual("path-2\\test-path-2.cshtml", partialView.Path); // fixed in 7.3 - 7.2.8 does not update the path
|
||||
Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path); // fixed in 7.3 - 7.2.8 does not update the path
|
||||
Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath);
|
||||
|
||||
partialView = (PartialView) repository.Get("path-2/test-path-2.cshtml");
|
||||
Assert.IsNotNull(partialView);
|
||||
Assert.AreEqual("path-2\\test-path-2.cshtml", partialView.Path);
|
||||
Assert.AreEqual("path-2\\test-path-2.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
|
||||
Assert.AreEqual("/Views/Partials/path-2/test-path-2.cshtml", partialView.VirtualPath);
|
||||
|
||||
partialView = new PartialView(PartialViewType.PartialView, "path-2\\test-path-3.cshtml") { Content = "// partialView" };
|
||||
repository.Save(partialView);
|
||||
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.cshtml"));
|
||||
Assert.AreEqual("path-2\\test-path-3.cshtml", partialView.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
|
||||
Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath);
|
||||
|
||||
partialView = (PartialView) repository.Get("path-2/test-path-3.cshtml");
|
||||
Assert.IsNotNull(partialView);
|
||||
Assert.AreEqual("path-2\\test-path-3.cshtml", partialView.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
|
||||
Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath);
|
||||
|
||||
partialView = (PartialView) repository.Get("path-2\\test-path-3.cshtml");
|
||||
Assert.IsNotNull(partialView);
|
||||
Assert.AreEqual("path-2\\test-path-3.cshtml", partialView.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.cshtml".Replace("\\", $"{Path.DirectorySeparatorChar}"), partialView.Path);
|
||||
Assert.AreEqual("/Views/Partials/path-2/test-path-3.cshtml", partialView.VirtualPath);
|
||||
|
||||
partialView = new PartialView(PartialViewType.PartialView, "\\test-path-4.cshtml") { Content = "// partialView" };
|
||||
|
||||
@@ -280,36 +280,36 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor
|
||||
repository.Save(script);
|
||||
|
||||
Assert.IsTrue(_fileSystem.FileExists("scripts/path-2/test-path-2.js"));
|
||||
Assert.AreEqual("scripts\\path-2\\test-path-2.js", script.Path);
|
||||
Assert.AreEqual("scripts\\path-2\\test-path-2.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);
|
||||
Assert.AreEqual("/scripts/scripts/path-2/test-path-2.js", script.VirtualPath);
|
||||
|
||||
script = new Script("path-2/test-path-2.js") { Content = "// script" };
|
||||
repository.Save(script);
|
||||
|
||||
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.js"));
|
||||
Assert.AreEqual("path-2\\test-path-2.js", script.Path); // fixed in 7.3 - 7.2.8 does not update the path
|
||||
Assert.AreEqual("path-2\\test-path-2.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);// fixed in 7.3 - 7.2.8 does not update the path
|
||||
Assert.AreEqual("/scripts/path-2/test-path-2.js", script.VirtualPath);
|
||||
|
||||
script = repository.Get("path-2/test-path-2.js");
|
||||
Assert.IsNotNull(script);
|
||||
Assert.AreEqual("path-2\\test-path-2.js", script.Path);
|
||||
Assert.AreEqual("path-2\\test-path-2.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);
|
||||
Assert.AreEqual("/scripts/path-2/test-path-2.js", script.VirtualPath);
|
||||
|
||||
script = new Script("path-2\\test-path-3.js") { Content = "// script" };
|
||||
repository.Save(script);
|
||||
|
||||
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.js"));
|
||||
Assert.AreEqual("path-2\\test-path-3.js", script.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);
|
||||
Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath);
|
||||
|
||||
script = repository.Get("path-2/test-path-3.js");
|
||||
Assert.IsNotNull(script);
|
||||
Assert.AreEqual("path-2\\test-path-3.js", script.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);
|
||||
Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath);
|
||||
|
||||
script = repository.Get("path-2\\test-path-3.js");
|
||||
Assert.IsNotNull(script);
|
||||
Assert.AreEqual("path-2\\test-path-3.js", script.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.js".Replace("\\", $"{Path.DirectorySeparatorChar}"), script.Path);
|
||||
Assert.AreEqual("/scripts/path-2/test-path-3.js", script.VirtualPath);
|
||||
|
||||
script = new Script("\\test-path-4.js") { Content = "// script" };
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor
|
||||
stylesheet = repository.Get(stylesheet.Name);
|
||||
|
||||
//Assert
|
||||
Assert.That(stylesheet.Content, Is.EqualTo("body { color:#000; } .bold {font-weight:bold;}\r\n\r\n/**umb_name:Test*/\r\np {\r\n\tfont-size:2em;\r\n}"));
|
||||
Assert.That(stylesheet.Content, Is.EqualTo("body { color:#000; } .bold {font-weight:bold;}\r\n\r\n/**umb_name:Test*/\r\np {\r\n\tfont-size:2em;\r\n}".Replace("\r\n", Environment.NewLine)));
|
||||
Assert.AreEqual(1, stylesheet.Properties.Count());
|
||||
}
|
||||
}
|
||||
@@ -281,29 +281,29 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Persistence.Repositor
|
||||
repository.Save(stylesheet);
|
||||
|
||||
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-2.css"));
|
||||
Assert.AreEqual("path-2\\test-path-2.css", stylesheet.Path); // fixed in 7.3 - 7.2.8 does not update the path
|
||||
Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);// fixed in 7.3 - 7.2.8 does not update the path
|
||||
Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath);
|
||||
|
||||
stylesheet = repository.Get("path-2/test-path-2.css");
|
||||
Assert.IsNotNull(stylesheet);
|
||||
Assert.AreEqual("path-2\\test-path-2.css", stylesheet.Path);
|
||||
Assert.AreEqual("path-2\\test-path-2.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);
|
||||
Assert.AreEqual("/css/path-2/test-path-2.css", stylesheet.VirtualPath);
|
||||
|
||||
stylesheet = new Stylesheet("path-2\\test-path-3.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" };
|
||||
repository.Save(stylesheet);
|
||||
|
||||
Assert.IsTrue(_fileSystem.FileExists("path-2/test-path-3.css"));
|
||||
Assert.AreEqual("path-2\\test-path-3.css", stylesheet.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);
|
||||
Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath);
|
||||
|
||||
stylesheet = repository.Get("path-2/test-path-3.css");
|
||||
Assert.IsNotNull(stylesheet);
|
||||
Assert.AreEqual("path-2\\test-path-3.css", stylesheet.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);
|
||||
Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath);
|
||||
|
||||
stylesheet = repository.Get("path-2\\test-path-3.css");
|
||||
Assert.IsNotNull(stylesheet);
|
||||
Assert.AreEqual("path-2\\test-path-3.css", stylesheet.Path);
|
||||
Assert.AreEqual("path-2\\test-path-3.css".Replace("\\", $"{Path.DirectorySeparatorChar}"), stylesheet.Path);
|
||||
Assert.AreEqual("/css/path-2/test-path-3.css", stylesheet.VirtualPath);
|
||||
|
||||
stylesheet = new Stylesheet("\\test-path-4.css") { Content = "body { color:#000; } .bold {font-weight:bold;}" };
|
||||
|
||||
@@ -119,6 +119,6 @@
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="Dictionary_Package" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>dictionary-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
<value>Dictionary-Package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
|
||||
<DefineConstants>IS_WINDOWS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="TEMP\**" />
|
||||
<EmbeddedResource Remove="TEMP\**" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Threading;
|
||||
@@ -91,7 +91,6 @@ namespace Umbraco.Tests.TestHelpers
|
||||
|
||||
var lazyMappers = new Lazy<IMapperCollection>(f.GetRequiredService<IMapperCollection>);
|
||||
var factory = new UmbracoDatabaseFactory(f.GetRequiredService<ILogger<UmbracoDatabaseFactory>>(), f.GetRequiredService<ILoggerFactory>(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator);
|
||||
factory.ResetForTests();
|
||||
return factory;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"umbracoDbDSN": "Server=(LocalDB)\\Umbraco;Database=NetCore;Integrated Security=true"
|
||||
"umbracoDbDSN": ""
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
@@ -71,4 +71,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user