diff --git a/.gitattributes b/.gitattributes index c8987ade67..3241b6511c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 8a99f941b0..c545d6884e 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -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' diff --git a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs index 4ec233e17f..89fce803b2 100644 --- a/src/Umbraco.Infrastructure/Persistence/LocalDb.cs +++ b/src/Umbraco.Infrastructure/Persistence/LocalDb.cs @@ -937,7 +937,7 @@ namespace Umbraco.Core.Persistence /// This is a C# implementation of T-SQL QUOTEDNAME. /// is optional, it can be '[' (default), ']', '\'' or '"'. /// - private static string QuotedName(string name, char quote = '[') + internal static string QuotedName(string name, char quote = '[') { switch (quote) { diff --git a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs index 7b98bd150e..5c3c984677 100644 --- a/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/UmbracoDatabaseFactory.cs @@ -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; - } } } diff --git a/src/Umbraco.Tests.Integration/GlobalSetupTeardown.cs b/src/Umbraco.Tests.Integration/GlobalSetupTeardown.cs index fe1d604dd9..6e86e97770 100644 --- a/src/Umbraco.Tests.Integration/GlobalSetupTeardown.cs +++ b/src/Umbraco.Tests.Integration/GlobalSetupTeardown.cs @@ -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); } } diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs index d60f49971a..93769eaaed 100644 --- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs +++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs @@ -132,11 +132,12 @@ namespace Umbraco.Tests.Integration.TestServerTest public override void ConfigureServices(IServiceCollection services) { + services.AddTransient(); 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! diff --git a/src/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs b/src/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs new file mode 100644 index 0000000000..02a3da676a --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/BaseTestDatabase.cs @@ -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 _testDatabases; + + protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands; + + protected BlockingCollection _prepareQueue; + protected BlockingCollection _readySchemaQueue; + protected BlockingCollection _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(), _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 + } + } + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/ITestDatabase.cs b/src/Umbraco.Tests.Integration/Testing/ITestDatabase.cs new file mode 100644 index 0000000000..28d7e9c8bc --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/ITestDatabase.cs @@ -0,0 +1,9 @@ +namespace Umbraco.Tests.Integration.Testing +{ + public interface ITestDatabase + { + TestDbMeta AttachEmpty(); + TestDbMeta AttachSchema(); + void Detach(TestDbMeta id); + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs index dacfd950e0..39d74f8869 100644 --- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs +++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs @@ -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(); + #endif // replace this service so that it can lookup the correct file locations builder.Services.AddUnique(GetLocalizedTextService); diff --git a/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs b/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs index 39f9ca5592..a9a842cdcd 100644 --- a/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs +++ b/src/Umbraco.Tests.Integration/Testing/LocalDbTestDatabase.cs @@ -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 /// /// Manages a pool of LocalDb databases for integration testing /// - 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(); + _readySchemaQueue = new BlockingCollection(); + _readyEmptyQueue = new BlockingCollection(); - // 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(), _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 _prepareQueue, _readyQueue; - private readonly Action _prepare; - private int _current; - - public DatabasePool(LocalDb localDb, LocalDb.Instance instance, string name, string tempName, string filesPath, int size, int parallel = 1, Action prepare = null, bool delete = false) - { - _localDb = localDb; - _instance = instance; - _filesPath = filesPath; - _name = name; - _size = size; - _prepare = prepare; - _prepareQueue = new BlockingCollection(); - _readyQueue = new BlockingCollection(); - _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)) { } - } - } - } } diff --git a/src/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs b/src/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs new file mode 100644 index 0000000000..4a7f602ac6 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/SqlDeveloperTestDatabase.cs @@ -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 +{ + /// + /// It's not meant to be pretty, rushed port of LocalDb.cs + LocalDbTestDatabase.cs + /// + 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(); + _readySchemaQueue = new BlockingCollection(); + _readyEmptyQueue = new BlockingCollection(); + + 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); + } + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs b/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs new file mode 100644 index 0000000000..9bcbfa4d3a --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs @@ -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); + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/TestDbMeta.cs b/src/Umbraco.Tests.Integration/Testing/TestDbMeta.cs new file mode 100644 index 0000000000..83702db8e5 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/TestDbMeta.cs @@ -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); + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs b/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs new file mode 100644 index 0000000000..3eb3757207 --- /dev/null +++ b/src/Umbraco.Tests.Integration/Testing/TestUmbracoDatabaseFactoryProvider.cs @@ -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 +{ + /// + /// I want to be able to create a database for integration testsing without setting the connection string on the + /// singleton database factory forever. + /// + public class TestUmbracoDatabaseFactoryProvider + { + private readonly ILoggerFactory _loggerFactory; + private readonly IOptions _globalSettings; + private readonly IOptions _connectionStrings; + private readonly Lazy _mappers; + private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator; + + public TestUmbracoDatabaseFactoryProvider( + ILoggerFactory loggerFactory, + IOptions globalSettings, + IOptions connectionStrings, + Lazy 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(), + _loggerFactory, + _globalSettings.Value, + _connectionStrings.Value, + _mappers, + _dbProviderFactoryCreator); + } + } +} diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index a8875de286..43b2d236c7 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -165,6 +165,7 @@ namespace Umbraco.Tests.Integration.Testing public virtual void ConfigureServices(IServiceCollection services) { services.AddSingleton(TestHelper.DbProviderFactoryCreator); + services.AddTransient(); 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(); + var testDatabaseFactoryProvider = serviceProvider.GetRequiredService(); var databaseFactory = serviceProvider.GetRequiredService(); // 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(), state, TestHelper.WorkingDirectory); } /// @@ -267,17 +268,14 @@ namespace Umbraco.Tests.Integration.Testing /// /// There must only be ONE instance shared between all tests in a session /// - 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 /// /// Creates a LocalDb instance to use for the test /// - /// - /// - /// - /// - /// - /// - /// 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 CustomTestSetup => services => { }; /// diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs index 1ae46faa76..ffda46ed0d 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PartialViewRepositoryTests.cs @@ -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" }; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs index 9e4ae80ec6..f9084332b7 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ScriptRepositoryTest.cs @@ -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" }; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs index a576666e6e..b4b8316f83 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -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;}" }; diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/ImportResources.resx b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/ImportResources.resx index 5823fa1245..fdf7880297 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/ImportResources.resx +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/ImportResources.resx @@ -119,6 +119,6 @@ - dictionary-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + Dictionary-Package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 - \ No newline at end of file + diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj index c5b42f9848..b996205712 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj +++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj @@ -7,6 +7,10 @@ 8 + + IS_WINDOWS + + diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index faf387528d..8c7b9a00e2 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -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(f.GetRequiredService); var factory = new UmbracoDatabaseFactory(f.GetRequiredService>(), f.GetRequiredService(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator); - factory.ResetForTests(); return factory; }); } diff --git a/src/Umbraco.Web.UI.NetCore/appsettings.json b/src/Umbraco.Web.UI.NetCore/appsettings.json index caa563fe8e..44d148acdc 100644 --- a/src/Umbraco.Web.UI.NetCore/appsettings.json +++ b/src/Umbraco.Web.UI.NetCore/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "umbracoDbDSN": "Server=(LocalDB)\\Umbraco;Database=NetCore;Integrated Security=true" + "umbracoDbDSN": "" }, "Serilog": { "MinimumLevel": { @@ -71,4 +71,4 @@ } } } -} \ No newline at end of file +}