Merge remote-tracking branch 'origin/netcore/netcore' into netcore/netcore

This commit is contained in:
Bjarke Berg
2020-12-14 19:21:52 +01:00
22 changed files with 645 additions and 396 deletions

1
.gitattributes vendored
View File

@@ -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

View File

@@ -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'

View File

@@ -937,7 +937,7 @@ namespace Umbraco.Core.Persistence
/// This is a C# implementation of T-SQL QUOTEDNAME.
/// <paramref name="quote"/> is optional, it can be '[' (default), ']', '\'' or '"'.
/// </remarks>
private static string QuotedName(string name, char quote = '[')
internal static string QuotedName(string name, char quote = '[')
{
switch (quote)
{

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -132,11 +132,12 @@ namespace Umbraco.Tests.Integration.TestServerTest
public override void ConfigureServices(IServiceCollection services)
{
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
var typeLoader = services.AddTypeLoader(GetType().Assembly, TestHelper.GetWebHostEnvironment(), TestHelper.GetHostingEnvironment(),
TestHelper.ConsoleLoggerFactory, AppCaches.NoCache, Configuration, TestHelper.Profiler);
var builder = new UmbracoBuilder(services, Configuration, typeLoader);
builder
.AddConfiguration()
.AddTestCore(TestHelper) // This is the important one!

View File

@@ -0,0 +1,218 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Configuration;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
namespace Umbraco.Tests.Integration.Testing
{
public abstract class BaseTestDatabase
{
protected ILoggerFactory _loggerFactory;
protected IUmbracoDatabaseFactory _databaseFactory;
protected IEnumerable<TestDbMeta> _testDatabases;
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands;
protected BlockingCollection<TestDbMeta> _prepareQueue;
protected BlockingCollection<TestDbMeta> _readySchemaQueue;
protected BlockingCollection<TestDbMeta> _readyEmptyQueue;
protected abstract void Initialize();
public TestDbMeta AttachEmpty()
{
if (_prepareQueue == null)
{
Initialize();
}
return _readyEmptyQueue.Take();
}
public TestDbMeta AttachSchema()
{
if (_prepareQueue == null)
{
Initialize();
}
return _readySchemaQueue.Take();
}
public void Detach(TestDbMeta meta)
{
_prepareQueue.TryAdd(meta);
}
protected void PrepareDatabase()
{
Retry(10, () =>
{
while (_prepareQueue.IsCompleted == false)
{
TestDbMeta meta;
try
{
meta = _prepareQueue.Take();
}
catch (InvalidOperationException)
{
continue;
}
using (var conn = new SqlConnection(meta.ConnectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
ResetTestDatabase(cmd);
if (!meta.IsEmpty)
{
RebuildSchema(cmd, meta);
}
}
if (!meta.IsEmpty)
{
_readySchemaQueue.TryAdd(meta);
}
else
{
_readyEmptyQueue.TryAdd(meta);
}
}
});
}
protected void RebuildSchema(IDbCommand command, TestDbMeta meta)
{
if (_cachedDatabaseInitCommands != null)
{
foreach (var dbCommand in _cachedDatabaseInitCommands)
{
if (dbCommand.Text.StartsWith("SELECT "))
{
continue;
}
command.CommandText = dbCommand.Text;
command.Parameters.Clear();
foreach (var parameterInfo in dbCommand.Parameters)
{
AddParameter(command, parameterInfo);
}
command.ExecuteNonQuery();
}
}
else
{
_databaseFactory.Configure(meta.ConnectionString, Core.Constants.DatabaseProviders.SqlServer);
using (var database = (UmbracoDatabase)_databaseFactory.CreateDatabase())
{
database.LogCommands = true;
using (var transaction = database.GetTransaction())
{
var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, new UmbracoVersion());
schemaCreator.InitializeDatabaseSchema();
transaction.Complete();
_cachedDatabaseInitCommands = database.Commands.ToArray();
}
}
}
}
protected static void SetCommand(SqlCommand command, string sql, params object[] args)
{
command.CommandType = CommandType.Text;
command.CommandText = sql;
command.Parameters.Clear();
for (var i = 0; i < args.Length; i++)
{
command.Parameters.AddWithValue("@" + i, args[i]);
}
}
protected static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
{
var p = cmd.CreateParameter();
p.ParameterName = parameterInfo.Name;
p.Value = parameterInfo.Value;
p.DbType = parameterInfo.DbType;
p.Size = parameterInfo.Size;
cmd.Parameters.Add(p);
}
protected static void ResetTestDatabase(IDbCommand cmd)
{
// https://stackoverflow.com/questions/536350
cmd.CommandType = CommandType.Text;
cmd.CommandText = @"
declare @n char(1);
set @n = char(10);
declare @stmt nvarchar(max);
-- check constraints
select @stmt = isnull( @stmt + @n, '' ) +
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.check_constraints;
-- foreign keys
select @stmt = isnull( @stmt + @n, '' ) +
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.foreign_keys;
-- tables
select @stmt = isnull( @stmt + @n, '' ) +
'drop table [' + schema_name(schema_id) + '].[' + name + ']'
from sys.tables;
exec sp_executesql @stmt;
";
// rudimentary retry policy since a db can still be in use when we try to drop
Retry(10, () => cmd.ExecuteNonQuery());
}
protected static void Retry(int maxIterations, Action action)
{
for (var i = 0; i < maxIterations; i++)
{
try
{
action();
return;
}
catch (SqlException)
{
//Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
// This can occur when there's a transaction deadlock which means (i think) that the database is still in use and hasn't been closed properly yet
// so we need to just wait a little bit
Thread.Sleep(100 * i);
if (i == maxIterations - 1)
{
Debugger.Launch();
throw;
}
}
catch (InvalidOperationException)
{
// Ignore
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Tests.Integration.Testing
{
public interface ITestDatabase
{
TestDbMeta AttachEmpty();
TestDbMeta AttachSchema();
void Detach(TestDbMeta id);
}
}

View File

@@ -49,8 +49,10 @@ namespace Umbraco.Tests.Integration.Testing
// we don't want persisted nucache files in tests
builder.Services.AddTransient(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true });
#if IS_WINDOWS
// ensure all lucene indexes are using RAM directory (no file system)
builder.Services.AddUnique<ILuceneDirectoryFactory, LuceneRAMDirectoryFactory>();
#endif
// replace this service so that it can lookup the correct file locations
builder.Services.AddUnique<ILocalizedTextService>(GetLocalizedTextService);

View File

@@ -1,18 +1,8 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Migrations.Install;
using Umbraco.Core.Persistence;
namespace Umbraco.Tests.Integration.Testing
@@ -20,197 +10,104 @@ namespace Umbraco.Tests.Integration.Testing
/// <summary>
/// Manages a pool of LocalDb databases for integration testing
/// </summary>
public class LocalDbTestDatabase
public class LocalDbTestDatabase : BaseTestDatabase, ITestDatabase
{
public const string InstanceName = "UmbracoTests";
public const string DatabaseName = "UmbracoTests";
private readonly ILoggerFactory _loggerFactory;
private readonly LocalDb _localDb;
private readonly IUmbracoVersion _umbracoVersion;
private static LocalDb.Instance _instance;
private static LocalDb.Instance _localDbInstance;
private static string _filesPath;
private readonly IUmbracoDatabaseFactory _dbFactory;
private UmbracoDatabase.CommandInfo[] _dbCommands;
private string _currentCstr;
private static DatabasePool _emptyPool;
private static DatabasePool _schemaPool;
private DatabasePool _currentPool;
private const int _threadCount = 2;
public static LocalDbTestDatabase Instance { get; private set; }
//It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
internal LocalDbTestDatabase(ILoggerFactory loggerFactory, LocalDb localDb, string filesPath, IUmbracoDatabaseFactory dbFactory)
{
_umbracoVersion = new UmbracoVersion();
_loggerFactory = loggerFactory;
_databaseFactory = dbFactory;
_localDb = localDb;
_filesPath = filesPath;
_dbFactory = dbFactory;
_instance = _localDb.GetInstance(InstanceName);
if (_instance != null) return;
Instance = this; // For GlobalSetupTeardown.cs
_testDatabases = new[]
{
// With Schema
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-1", false),
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-2", false),
// Empty (for migration testing etc)
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-3", true),
TestDbMeta.CreateWithoutConnectionString($"{DatabaseName}-4", true),
};
_localDbInstance = _localDb.GetInstance(InstanceName);
if (_localDbInstance != null)
{
return;
}
if (_localDb.CreateInstance(InstanceName) == false)
{
throw new Exception("Failed to create a LocalDb instance.");
_instance = _localDb.GetInstance(InstanceName);
}
_localDbInstance = _localDb.GetInstance(InstanceName);
}
public string ConnectionString => _currentCstr ?? _instance.GetAttachedConnectionString("XXXXXX", _filesPath);
private void Create()
protected override void Initialize()
{
var tempName = Guid.NewGuid().ToString("N");
_instance.CreateDatabase(tempName, _filesPath);
_instance.DetachDatabase(tempName);
_localDbInstance.CreateDatabase(tempName, _filesPath);
_localDbInstance.DetachDatabase(tempName);
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
// there's probably a sweet spot to be found for size / parallel...
var s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.EmptyPoolSize"];
var emptySize = s == null ? 1 : int.Parse(s);
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.EmptyPoolThreadCount"];
var emptyParallel = s == null ? 1 : int.Parse(s);
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.SchemaPoolSize"];
var schemaSize = s == null ? 1 : int.Parse(s);
s = ConfigurationManager.AppSettings["Umbraco.Tests.LocalDbTestDatabase.SchemaPoolThreadCount"];
var schemaParallel = s == null ? 1 : int.Parse(s);
_emptyPool = new DatabasePool(_localDb, _instance, DatabaseName + "-Empty", tempName, _filesPath, emptySize, emptyParallel);
_schemaPool = new DatabasePool(_localDb, _instance, DatabaseName + "-Schema", tempName, _filesPath, schemaSize, schemaParallel, delete: true, prepare: RebuildSchema);
}
public int AttachEmpty()
{
if (_emptyPool == null)
Create();
_currentCstr = _emptyPool.AttachDatabase(out var id);
_currentPool = _emptyPool;
return id;
}
public int AttachSchema()
{
if (_schemaPool == null)
Create();
_currentCstr = _schemaPool.AttachDatabase(out var id);
_currentPool = _schemaPool;
return id;
}
public void Detach(int id)
{
_currentPool.DetachDatabase(id);
}
private void RebuildSchema(DbConnection conn, IDbCommand cmd)
{
if (_dbCommands != null)
foreach (var meta in _testDatabases)
{
foreach (var dbCommand in _dbCommands)
{
if (dbCommand.Text.StartsWith("SELECT ")) continue;
cmd.CommandText = dbCommand.Text;
cmd.Parameters.Clear();
foreach (var parameterInfo in dbCommand.Parameters)
AddParameter(cmd, parameterInfo);
cmd.ExecuteNonQuery();
}
}
else
{
_dbFactory.Configure(conn.ConnectionString, Constants.DatabaseProviders.SqlServer);
using var database = (UmbracoDatabase)_dbFactory.CreateDatabase();
// track each db command ran as part of creating the database so we can replay these
database.LogCommands = true;
using var trans = database.GetTransaction();
var creator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, _umbracoVersion);
creator.InitializeDatabaseSchema();
trans.Complete(); // commit it
_dbCommands = database.Commands.ToArray();
_localDb.CopyDatabaseFiles(tempName, _filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: false);
meta.ConnectionString = _localDbInstance.GetAttachedConnectionString(meta.Name, _filesPath);
_prepareQueue.Add(meta);
}
}
private static void AddParameter(IDbCommand cmd, UmbracoDatabase.ParameterInfo parameterInfo)
{
var p = cmd.CreateParameter();
p.ParameterName = parameterInfo.Name;
p.Value = parameterInfo.Value;
p.DbType = parameterInfo.DbType;
p.Size = parameterInfo.Size;
cmd.Parameters.Add(p);
}
public void Clear()
{
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
foreach (var database in _instance.GetDatabases())
for (var i = 0; i < _threadCount; i++)
{
if (database.StartsWith(filename))
_instance.DropDatabase(database);
}
foreach (var file in Directory.EnumerateFiles(_filesPath))
{
if (file.EndsWith(".mdf") == false && file.EndsWith(".ldf") == false) continue;
File.Delete(file);
var thread = new Thread(PrepareDatabase);
thread.Start();
}
}
private static void ResetLocalDb(IDbCommand cmd)
public void Finish()
{
// https://stackoverflow.com/questions/536350
if (_prepareQueue == null)
return;
cmd.CommandType = CommandType.Text;
cmd.CommandText = @"
declare @n char(1);
set @n = char(10);
declare @stmt nvarchar(max);
-- check constraints
select @stmt = isnull( @stmt + @n, '' ) +
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.check_constraints;
-- foreign keys
select @stmt = isnull( @stmt + @n, '' ) +
'alter table [' + schema_name(schema_id) + '].[' + object_name( parent_object_id ) + '] drop constraint [' + name + ']'
from sys.foreign_keys;
-- tables
select @stmt = isnull( @stmt + @n, '' ) +
'drop table [' + schema_name(schema_id) + '].[' + name + ']'
from sys.tables;
exec sp_executesql @stmt;
";
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _))
{ }
// rudimentary retry policy since a db can still be in use when we try to drop
Retry(10, () =>
{
cmd.ExecuteNonQuery();
});
}
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _))
{ }
public static void KillLocalDb()
{
_emptyPool?.Stop();
_schemaPool?.Stop();
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _))
{ }
if (_filesPath == null)
return;
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
foreach (var database in _instance.GetDatabases())
foreach (var database in _localDbInstance.GetDatabases())
{
if (database.StartsWith(filename))
_instance.DropDatabase(database);
_localDbInstance.DropDatabase(database);
}
foreach (var file in Directory.EnumerateFiles(_filesPath))
@@ -226,145 +123,5 @@ namespace Umbraco.Tests.Integration.Testing
}
}
}
private static void Retry(int maxIterations, Action action)
{
for (var i = 0; i < maxIterations; i++)
{
try
{
action();
return;
}
catch (SqlException)
{
//Console.Error.WriteLine($"SqlException occured, but we try again {i+1}/{maxIterations}.\n{e}");
// This can occur when there's a transaction deadlock which means (i think) that the database is still in use and hasn't been closed properly yet
// so we need to just wait a little bit
Thread.Sleep(100 * i);
if (i == maxIterations - 1)
{
Debugger.Launch();
throw;
}
}
catch (InvalidOperationException)
{
}
}
}
private class DatabasePool
{
private readonly LocalDb _localDb;
private readonly LocalDb.Instance _instance;
private readonly string _filesPath;
private readonly string _name;
private readonly int _size;
private readonly string[] _cstrs;
private readonly BlockingCollection<int> _prepareQueue, _readyQueue;
private readonly Action<DbConnection, IDbCommand> _prepare;
private int _current;
public DatabasePool(LocalDb localDb, LocalDb.Instance instance, string name, string tempName, string filesPath, int size, int parallel = 1, Action<DbConnection, IDbCommand> prepare = null, bool delete = false)
{
_localDb = localDb;
_instance = instance;
_filesPath = filesPath;
_name = name;
_size = size;
_prepare = prepare;
_prepareQueue = new BlockingCollection<int>();
_readyQueue = new BlockingCollection<int>();
_cstrs = new string[_size];
for (var i = 0; i < size; i++)
localDb.CopyDatabaseFiles(tempName, filesPath, targetDatabaseName: name + "-" + i, overwrite: true, delete: delete && i == size - 1);
if (prepare == null)
{
for (var i = 0; i < size; i++)
_readyQueue.Add(i);
}
else
{
for (var i = 0; i < size; i++)
_prepareQueue.Add(i);
}
for (var i = 0; i < parallel; i++)
{
var thread = new Thread(PrepareThread);
thread.Start();
}
}
public string AttachDatabase(out int id)
{
_current = _readyQueue.Take();
id = _current;
return ConnectionString(_current);
}
public void DetachDatabase(int id)
{
if (id != _current)
throw new InvalidOperationException("Cannot detatch the non-current db");
_prepareQueue.Add(_current);
}
private string ConnectionString(int i)
{
return _cstrs[i] ?? (_cstrs[i] = _instance.GetAttachedConnectionString(_name + "-" + i, _filesPath));
}
private void PrepareThread()
{
Retry(10, () =>
{
while (_prepareQueue.IsCompleted == false)
{
int i;
try
{
i = _prepareQueue.Take();
}
catch (InvalidOperationException)
{
continue;
}
using (var conn = new SqlConnection(ConnectionString(i)))
using (var cmd = conn.CreateCommand())
{
conn.Open();
ResetLocalDb(cmd);
_prepare?.Invoke(conn, cmd);
}
if (!_readyQueue.IsAddingCompleted)
{
_readyQueue.Add(i);
}
}
});
}
public void Stop()
{
int i;
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out i)) { }
_readyQueue.CompleteAdding();
while (_readyQueue.TryTake(out i)) { }
}
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Collections.Concurrent;
using System.Data.SqlClient;
using System.Threading;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Persistence;
// ReSharper disable ConvertToUsingDeclaration
namespace Umbraco.Tests.Integration.Testing
{
/// <remarks>
/// It's not meant to be pretty, rushed port of LocalDb.cs + LocalDbTestDatabase.cs
/// </remarks>
public class SqlDeveloperTestDatabase : BaseTestDatabase, ITestDatabase
{
private readonly string _masterConnectionString;
public const string DatabaseName = "UmbracoTests";
private const int _threadCount = 2;
public static SqlDeveloperTestDatabase Instance { get; private set; }
public SqlDeveloperTestDatabase(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory databaseFactory, string masterConnectionString)
{
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
_databaseFactory = databaseFactory ?? throw new ArgumentNullException(nameof(databaseFactory));
_masterConnectionString = masterConnectionString;
_testDatabases = new[]
{
// With Schema
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-1", false, masterConnectionString),
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-2", false, masterConnectionString),
// Empty (for migration testing etc)
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-3", true, masterConnectionString),
TestDbMeta.CreateWithMasterConnectionString($"{DatabaseName}-4", true, masterConnectionString),
};
Instance = this; // For GlobalSetupTeardown.cs
}
protected override void Initialize()
{
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
foreach (var meta in _testDatabases)
{
CreateDatabase(meta);
_prepareQueue.Add(meta);
}
for (var i = 0; i < _threadCount; i++)
{
var thread = new Thread(PrepareDatabase);
thread.Start();
}
}
private void CreateDatabase(TestDbMeta meta)
{
using (var connection = new SqlConnection(_masterConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
SetCommand(command, $@"CREATE DATABASE {LocalDb.QuotedName(meta.Name)}");
command.ExecuteNonQuery();
}
}
}
private void Drop(TestDbMeta meta)
{
using (var connection = new SqlConnection(_masterConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
SetCommand(command, $@"
ALTER DATABASE{LocalDb.QuotedName(meta.Name)}
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE
");
command.ExecuteNonQuery();
SetCommand(command, $@"DROP DATABASE {LocalDb.QuotedName(meta.Name)}");
command.ExecuteNonQuery();
}
}
}
public void Finish()
{
if (_prepareQueue == null)
return;
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _)) { }
_readyEmptyQueue.CompleteAdding();
while (_readyEmptyQueue.TryTake(out _)) { }
_readySchemaQueue.CompleteAdding();
while (_readySchemaQueue.TryTake(out _)) { }
foreach (var testDatabase in _testDatabases)
{
Drop(testDatabase);
}
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Persistence;
namespace Umbraco.Tests.Integration.Testing
{
public class TestDatabaseFactory
{
public static ITestDatabase Create(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
return string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString"))
? CreateLocalDb(filesPath, loggerFactory, dbFactory.Create())
: CreateSqlDeveloper(loggerFactory, dbFactory.Create());
}
private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
{
var localDb = new LocalDb();
if (!localDb.IsAvailable)
{
throw new InvalidOperationException("LocalDB is not available.");
}
return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
}
private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
{
// $ export SA_PASSWORD=Foobar123!
// $ export UmbracoIntegrationTestConnectionString="Server=localhost,1433;User Id=sa;Password=$SA_PASSWORD;"
// $ docker run -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=$SA_PASSWORD" -e 'MSSQL_PID=Developer' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
if (string.IsNullOrEmpty(connectionString))
{
throw new InvalidOperationException("ENV: UmbracoIntegrationTestConnectionString is not set");
}
return new SqlDeveloperTestDatabase(loggerFactory, dbFactory, connectionString);
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Text.RegularExpressions;
namespace Umbraco.Tests.Integration.Testing
{
public class TestDbMeta
{
public string Name { get; }
public bool IsEmpty { get; }
public string ConnectionString { get; set; }
private TestDbMeta(string name, bool isEmpty, string connectionString)
{
IsEmpty = isEmpty;
Name = name;
ConnectionString = connectionString;
}
private static string ConstructConnectionString(string masterConnectionString, string databaseName)
{
var prefix = Regex.Replace(masterConnectionString, "Database=.+?;", string.Empty);
var connectionString = $"{prefix};Database={databaseName};";
return connectionString.Replace(";;", ";");
}
public static TestDbMeta CreateWithMasterConnectionString(string name, bool isEmpty, string masterConnectionString)
{
return new TestDbMeta(name, isEmpty, ConstructConnectionString(masterConnectionString, name));
}
// LocalDb mdf funtimes
public static TestDbMeta CreateWithoutConnectionString(string name, bool isEmpty)
{
return new TestDbMeta(name, isEmpty, null);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
namespace Umbraco.Tests.Integration.Testing
{
/// <summary>
/// I want to be able to create a database for integration testsing without setting the connection string on the
/// singleton database factory forever.
/// </summary>
public class TestUmbracoDatabaseFactoryProvider
{
private readonly ILoggerFactory _loggerFactory;
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly IOptions<ConnectionStrings> _connectionStrings;
private readonly Lazy<IMapperCollection> _mappers;
private readonly IDbProviderFactoryCreator _dbProviderFactoryCreator;
public TestUmbracoDatabaseFactoryProvider(
ILoggerFactory loggerFactory,
IOptions<GlobalSettings> globalSettings,
IOptions<ConnectionStrings> connectionStrings,
Lazy<IMapperCollection> mappers,
IDbProviderFactoryCreator dbProviderFactoryCreator)
{
_loggerFactory = loggerFactory;
_globalSettings = globalSettings;
_connectionStrings = connectionStrings;
_mappers = mappers;
_dbProviderFactoryCreator = dbProviderFactoryCreator;
}
public IUmbracoDatabaseFactory Create()
{
// ReSharper disable once ArrangeMethodOrOperatorBody
return new UmbracoDatabaseFactory(
_loggerFactory.CreateLogger<UmbracoDatabaseFactory>(),
_loggerFactory,
_globalSettings.Value,
_connectionStrings.Value,
_mappers,
_dbProviderFactoryCreator);
}
}
}

View File

@@ -165,6 +165,7 @@ namespace Umbraco.Tests.Integration.Testing
public virtual void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(TestHelper.DbProviderFactoryCreator);
services.AddTransient<TestUmbracoDatabaseFactoryProvider>();
var webHostEnvironment = TestHelper.GetWebHostEnvironment();
services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
@@ -242,17 +243,17 @@ namespace Umbraco.Tests.Integration.Testing
#region LocalDb
private static readonly object _dbLocker = new object();
private static LocalDbTestDatabase _dbInstance;
private static ITestDatabase _dbInstance;
private static TestDbMeta _fixtureDbMeta;
protected void UseTestLocalDb(IServiceProvider serviceProvider)
{
var state = serviceProvider.GetRequiredService<IRuntimeState>();
var testDatabaseFactoryProvider = serviceProvider.GetRequiredService<TestUmbracoDatabaseFactoryProvider>();
var databaseFactory = serviceProvider.GetRequiredService<IUmbracoDatabaseFactory>();
// This will create a db, install the schema and ensure the app is configured to run
InstallTestLocalDb(databaseFactory, TestHelper.ConsoleLoggerFactory, state, TestHelper.WorkingDirectory);
TestDBConnectionString = databaseFactory.ConnectionString;
InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString;
InstallTestLocalDb(testDatabaseFactoryProvider, databaseFactory, serviceProvider.GetRequiredService<ILoggerFactory>(), state, TestHelper.WorkingDirectory);
}
/// <summary>
@@ -267,17 +268,14 @@ namespace Umbraco.Tests.Integration.Testing
/// <remarks>
/// There must only be ONE instance shared between all tests in a session
/// </remarks>
private static LocalDbTestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
private static ITestDatabase GetOrCreateDatabase(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
lock (_dbLocker)
{
if (_dbInstance != null)
return _dbInstance;
var localDb = new LocalDb();
if (localDb.IsAvailable == false)
throw new InvalidOperationException("LocalDB is not available.");
_dbInstance = new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
_dbInstance = TestDatabaseFactory.Create(filesPath, loggerFactory, dbFactory);
return _dbInstance;
}
}
@@ -285,16 +283,12 @@ namespace Umbraco.Tests.Integration.Testing
/// <summary>
/// Creates a LocalDb instance to use for the test
/// </summary>
/// <param name="runtimeState"></param>
/// <param name="workingDirectory"></param>
/// <param name="connectionString"></param>
/// <param name="databaseFactory"></param>
/// <param name="loggerFactory"></param>
/// <param name="globalSettings"></param>
/// <returns></returns>
private void InstallTestLocalDb(
IUmbracoDatabaseFactory databaseFactory, ILoggerFactory loggerFactory,
IRuntimeState runtimeState, string workingDirectory)
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
IUmbracoDatabaseFactory databaseFactory,
ILoggerFactory loggerFactory,
IRuntimeState runtimeState,
string workingDirectory)
{
var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
@@ -310,22 +304,22 @@ namespace Umbraco.Tests.Integration.Testing
if (!Directory.Exists(dbFilePath))
Directory.CreateDirectory(dbFilePath);
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, databaseFactory);
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider);
switch (testOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
// New DB + Schema
var newSchemaDbId = db.AttachSchema();
var newSchemaDbMeta = db.AttachSchema();
// Add teardown callback
OnTestTearDown(() => db.Detach(newSchemaDbId));
OnTestTearDown(() => db.Detach(newSchemaDbMeta));
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
databaseFactory.Configure(newSchemaDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
@@ -335,15 +329,15 @@ namespace Umbraco.Tests.Integration.Testing
break;
case UmbracoTestOptions.Database.NewEmptyPerTest:
var newEmptyDbId = db.AttachEmpty();
var newEmptyDbMeta = db.AttachEmpty();
// Add teardown callback
OnTestTearDown(() => db.Detach(newEmptyDbId));
OnTestTearDown(() => db.Detach(newEmptyDbMeta));
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
databaseFactory.Configure(newEmptyDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
@@ -359,16 +353,17 @@ namespace Umbraco.Tests.Integration.Testing
if (FirstTestInFixture)
{
// New DB + Schema
var newSchemaFixtureDbId = db.AttachSchema();
var newSchemaFixtureDbMeta = db.AttachSchema();
_fixtureDbMeta = newSchemaFixtureDbMeta;
// Add teardown callback
OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId));
OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta));
}
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
databaseFactory.Configure(_fixtureDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
@@ -382,16 +377,17 @@ namespace Umbraco.Tests.Integration.Testing
if (FirstTestInFixture)
{
// New DB + Schema
var newEmptyFixtureDbId = db.AttachEmpty();
var newEmptyFixtureDbMeta = db.AttachEmpty();
_fixtureDbMeta = newEmptyFixtureDbMeta;
// Add teardown callback
OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbId));
OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta));
}
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
databaseFactory.Configure(_fixtureDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
break;
@@ -412,8 +408,6 @@ namespace Umbraco.Tests.Integration.Testing
public TestHelper TestHelper = new TestHelper();
protected virtual string TestDBConnectionString { get; private set; }
protected virtual Action<IServiceCollection> CustomTestSetup => services => { };
/// <summary>

View File

@@ -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" };

View File

@@ -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" };

View File

@@ -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;}" };

View File

@@ -119,6 +119,6 @@
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="Dictionary_Package" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>dictionary-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
<value>Dictionary-Package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
</root>
</root>

View File

@@ -7,6 +7,10 @@
<LangVersion>8</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<DefineConstants>IS_WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Remove="TEMP\**" />
<EmbeddedResource Remove="TEMP\**" />

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Configuration;
using System.Data.SqlServerCe;
using System.Threading;
@@ -91,7 +91,6 @@ namespace Umbraco.Tests.TestHelpers
var lazyMappers = new Lazy<IMapperCollection>(f.GetRequiredService<IMapperCollection>);
var factory = new UmbracoDatabaseFactory(f.GetRequiredService<ILogger<UmbracoDatabaseFactory>>(), f.GetRequiredService<ILoggerFactory>(), GetDbConnectionString(), GetDbProviderName(), lazyMappers, TestHelper.DbProviderFactoryCreator);
factory.ResetForTests();
return factory;
});
}

View File

@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"umbracoDbDSN": "Server=(LocalDB)\\Umbraco;Database=NetCore;Integrated Security=true"
"umbracoDbDSN": ""
},
"Serilog": {
"MinimumLevel": {
@@ -71,4 +71,4 @@
}
}
}
}
}