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
+}