From a7e5759526f087cb24e1bc5aaf71d3e74f4aa74b Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 14 Dec 2016 18:40:16 +0100 Subject: [PATCH] Fix tests and cleanup --- src/Umbraco.Core/CoreRuntime.cs | 2 +- src/Umbraco.Core/Persistence/DatabaseScope.cs | 12 +- .../Persistence/IUmbracoDatabaseAccessor.cs | 10 - ...s => ThreadStaticDatabaseScopeAccessor.cs} | 12 - src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../Persistence/DatabaseContextTests.cs | 55 ++++- .../Persistence/DatabaseScopeTests.cs | 193 +++++++++++++++ src/Umbraco.Tests/Persistence/LocksTests.cs | 232 +++++++++--------- .../Migrations/PostMigrationTests.cs | 2 + .../Persistence/UnitOfWorkTests.cs | 5 - .../Services/ContentServiceTests.cs | 4 +- .../Services/ThreadSafetyServiceTest.cs | 138 ++++------- .../Templates/TemplateRepositoryTests.cs | 2 + .../TestHelpers/BaseUsingSqlCeSyntax.cs | 2 + src/Umbraco.Tests/TestHelpers/BaseWebTest.cs | 9 +- .../TestHelpers/NoHttpContextAccessor.cs | 10 + .../Stubs/TestUmbracoDatabaseAccessor.cs | 9 - .../TestHelpers/TestObjects-Mocks.cs | 16 +- src/Umbraco.Tests/TestHelpers/TestObjects.cs | 25 +- .../TestHelpers/TestWithApplicationBase.cs | 2 +- .../TestHelpers/TestWithDatabaseBase.cs | 5 + .../TestHelpers/UmbracoTestBase.cs | 4 + src/Umbraco.Tests/Umbraco.Tests.csproj | 3 +- ...RenderIndexActionSelectorAttributeTests.cs | 2 + .../Web/Mvc/SurfaceControllerTests.cs | 2 +- .../Web/Mvc/UmbracoViewPageTests.cs | 2 +- .../HybridUmbracoDatabaseAccessor.cs | 19 -- src/Umbraco.Web/Umbraco.Web.csproj | 1 - src/Umbraco.Web/WebRuntime.cs | 2 +- 29 files changed, 483 insertions(+), 300 deletions(-) delete mode 100644 src/Umbraco.Core/Persistence/IUmbracoDatabaseAccessor.cs rename src/Umbraco.Core/Persistence/{ThreadStaticUmbracoDatabaseAccessor.cs => ThreadStaticDatabaseScopeAccessor.cs} (55%) create mode 100644 src/Umbraco.Tests/Persistence/DatabaseScopeTests.cs create mode 100644 src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs delete mode 100644 src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoDatabaseAccessor.cs delete mode 100644 src/Umbraco.Web/HybridUmbracoDatabaseAccessor.cs diff --git a/src/Umbraco.Core/CoreRuntime.cs b/src/Umbraco.Core/CoreRuntime.cs index f62d7be387..3b94ba3a52 100644 --- a/src/Umbraco.Core/CoreRuntime.cs +++ b/src/Umbraco.Core/CoreRuntime.cs @@ -235,7 +235,7 @@ namespace Umbraco.Core // register a database accessor - required by database factory // will be replaced by HybridUmbracoDatabaseAccessor in the web runtime // fixme - we should NOT be using thread static at all + will NOT get replaced = wtf? - container.RegisterSingleton(); + container.RegisterSingleton(); // register MainDom container.RegisterSingleton(); diff --git a/src/Umbraco.Core/Persistence/DatabaseScope.cs b/src/Umbraco.Core/Persistence/DatabaseScope.cs index e0e3dc1f79..8cc0f1c74c 100644 --- a/src/Umbraco.Core/Persistence/DatabaseScope.cs +++ b/src/Umbraco.Core/Persistence/DatabaseScope.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace Umbraco.Core.Persistence { @@ -6,10 +7,10 @@ namespace Umbraco.Core.Persistence { private readonly DatabaseScope _parent; private readonly IDatabaseScopeAccessor _accessor; - private readonly UmbracoDatabaseFactory _factory; + private readonly UmbracoDatabaseFactory _factory; private UmbracoDatabase _database; private bool _isParent; - private bool _disposed; + private int _disposed; private bool _disposeDatabase; // can specify a database to create a "substitute" scope eg for deploy - oh my @@ -28,8 +29,9 @@ namespace Umbraco.Core.Persistence { get { - if (_disposed) + if (Interlocked.CompareExchange(ref _disposed, 0, 0) != 0) throw new ObjectDisposedException(null, "Cannot access a disposed object."); + if (_database != null) return _database; if (_parent != null) return _parent.Database; _database = _factory.CreateDatabase(); @@ -42,9 +44,9 @@ namespace Umbraco.Core.Persistence { if (_isParent) throw new InvalidOperationException("Cannot dispose a parent scope."); - if (_disposed) + + if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) throw new ObjectDisposedException(null, "Cannot access a disposed object."); - _disposed = true; // fixme race if (_disposeDatabase) _database.Dispose(); diff --git a/src/Umbraco.Core/Persistence/IUmbracoDatabaseAccessor.cs b/src/Umbraco.Core/Persistence/IUmbracoDatabaseAccessor.cs deleted file mode 100644 index 7f6d5530da..0000000000 --- a/src/Umbraco.Core/Persistence/IUmbracoDatabaseAccessor.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Umbraco.Core.Persistence -{ - /// - /// Provides access to UmbracoDatabase. - /// - public interface IUmbracoDatabaseAccessor - { - UmbracoDatabase UmbracoDatabase { get; set; } - } -} diff --git a/src/Umbraco.Core/Persistence/ThreadStaticUmbracoDatabaseAccessor.cs b/src/Umbraco.Core/Persistence/ThreadStaticDatabaseScopeAccessor.cs similarity index 55% rename from src/Umbraco.Core/Persistence/ThreadStaticUmbracoDatabaseAccessor.cs rename to src/Umbraco.Core/Persistence/ThreadStaticDatabaseScopeAccessor.cs index 5d900fa993..b002dbb4f9 100644 --- a/src/Umbraco.Core/Persistence/ThreadStaticUmbracoDatabaseAccessor.cs +++ b/src/Umbraco.Core/Persistence/ThreadStaticDatabaseScopeAccessor.cs @@ -4,18 +4,6 @@ namespace Umbraco.Core.Persistence { // fixme - move this to test or whatever! - internal class ThreadStaticUmbracoDatabaseAccessor : IUmbracoDatabaseAccessor - { - [ThreadStatic] - private static UmbracoDatabase _umbracoDatabase; - - public UmbracoDatabase UmbracoDatabase - { - get { return _umbracoDatabase; } - set { _umbracoDatabase = value; } - } - } - internal class ThreadStaticDatabaseScopeAccessor : IDatabaseScopeAccessor { [ThreadStatic] diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 120f8586ff..fd459016d0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -296,7 +296,6 @@ - @@ -306,8 +305,8 @@ - + diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs index 198eb18592..b7d5b96345 100644 --- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs +++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs @@ -51,24 +51,57 @@ namespace Umbraco.Tests.Persistence } [Test] - public void Can_Verify_Single_Database_Instance() + public void NoDatabaseWithoutScope() { - var db1 = _dbContext.Database; - var db2 = _dbContext.Database; + Assert.Throws(() => + { + var db = _dbContext.Database; + }); + } + + [Test] + public void SingleDatabaseInstancePerScope() + { + UmbracoDatabase db1, db2; + + using (_dbContext.CreateDatabaseScope()) + { + db1 = _dbContext.Database; + db2 = _dbContext.Database; + } Assert.AreSame(db1, db2); } [Test] - public void Can_Assert_DatabaseType() + public void DifferentDatabaseInstancePerScope() { - var databaseType = _dbContext.Database.DatabaseType; + UmbracoDatabase db1, db2; - Assert.AreEqual(DatabaseType.SQLCe, databaseType); + using (_dbContext.CreateDatabaseScope()) + { + db1 = _dbContext.Database; + } + using (_dbContext.CreateDatabaseScope()) + { + db2 = _dbContext.Database; + } + + Assert.AreNotSame(db1, db2); } [Test] - public void Can_Assert_Created_Database() + public void GetDatabaseType() + { + using (_dbContext.CreateDatabaseScope()) + { + var databaseType = _dbContext.Database.DatabaseType; + Assert.AreEqual(DatabaseType.SQLCe, databaseType); + } + } + + [Test] + public void CreateDatabase() // fixme - move to DatabaseBuilderTest! { var path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", path); @@ -102,8 +135,12 @@ namespace Umbraco.Tests.Persistence // new ProfilingLogger(Mock.Of(), Mock.Of())); // create the umbraco database - var schemaHelper = new DatabaseSchemaHelper(_dbContext.Database, _logger); - schemaHelper.CreateDatabaseSchema(_runtime, _migrationEntryService, false); + DatabaseSchemaHelper schemaHelper; + using (_dbContext.CreateDatabaseScope()) + { + schemaHelper = new DatabaseSchemaHelper(_dbContext.Database, _logger); + schemaHelper.CreateDatabaseSchema(_runtime, _migrationEntryService, false); + } var umbracoNodeTable = schemaHelper.TableExist("umbracoNode"); var umbracoUserTable = schemaHelper.TableExist("umbracoUser"); diff --git a/src/Umbraco.Tests/Persistence/DatabaseScopeTests.cs b/src/Umbraco.Tests/Persistence/DatabaseScopeTests.cs new file mode 100644 index 0000000000..fa18721c61 --- /dev/null +++ b/src/Umbraco.Tests/Persistence/DatabaseScopeTests.cs @@ -0,0 +1,193 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Mappers; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Tests.TestHelpers; +using Umbraco.Web; + +namespace Umbraco.Tests.Persistence +{ + [TestFixture] + public class DatabaseScopeTests + { + [TearDown] + public void TearDown() + { + SafeCallContext.Clear(); + } + + [Test] + public void CreateAndDispose() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + Assert.AreSame(scope, accessor.Scope); + scope.Dispose(); + Assert.IsNull(accessor.Scope); + } + + [Test] + public void DisposeDisposed() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + scope.Dispose(); + Assert.Throws(() => scope.Dispose()); + } + + [Test] + public void DisposeParent() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope1 = new DatabaseScope(accessor, null); + var scope2 = new DatabaseScope(accessor, null); + Assert.Throws(() => scope1.Dispose()); + scope2.Dispose(); + scope1.Dispose(); + } + + [Test] + public void DatabaseDisposed() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + scope.Dispose(); + Assert.Throws(() => + { + var database = scope.Database; + }); + } + + [Test] + public void NestScopes() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope1 = new DatabaseScope(accessor, null); + var scope2 = new DatabaseScope(accessor, null); + Assert.AreSame(scope2, accessor.Scope); + scope2.Dispose(); + Assert.AreSame(scope1, accessor.Scope); + scope1.Dispose(); + Assert.IsNull(accessor.Scope); + } + + [Test] + public void NestedScopeSameDatabase() + { + var logger = Mock.Of(); + var providers = new TestObjects(null).GetDefaultSqlSyntaxProviders(logger); + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var factory = new UmbracoDatabaseFactory(providers, logger, accessor, new MapperCollection(Enumerable.Empty())); + var scope1 = new DatabaseScope(accessor, factory); + var database1 = accessor.Scope.Database; + Assert.IsNotNull(database1); + var scope2 = new DatabaseScope(accessor, factory); + var database2 = accessor.Scope.Database; + Assert.AreSame(database1, database2); + scope2.Dispose(); + scope1.Dispose(); + } + + [Test] + public void NestedScopeForceDatabase() + { + var logger = Mock.Of(); + var providers = new TestObjects(null).GetDefaultSqlSyntaxProviders(logger); + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var factory = new UmbracoDatabaseFactory(providers, logger, accessor, new MapperCollection(Enumerable.Empty())); + var scope1 = new DatabaseScope(accessor, factory); + var database1 = accessor.Scope.Database; + Assert.IsNotNull(database1); + var database2 = factory.CreateDatabase(); + Assert.AreNotSame(database1, database2); + var scope2 = new DatabaseScope(accessor, factory, database2); + Assert.AreSame(database2, accessor.Scope.Database); + scope2.Dispose(); + Assert.IsNotNull(accessor.Scope); + Assert.AreSame(database1, accessor.Scope.Database); + scope1.Dispose(); + Assert.IsNull(accessor.Scope); + } + + [Test] + public void FlowsWithAsync() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + var ascope = GetScopeAsync(accessor).Result; + Assert.AreSame(scope, ascope); + } + + [Test] + public void FlowsWithThread() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + DatabaseScope tscope = null; + var thread = new Thread(() => tscope = accessor.Scope); + thread.Start(); + thread.Join(); + Assert.AreSame(scope, tscope); + } + + [Test] + public void DoesNotFlowWithAsyncAndSafeCallContext() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + DatabaseScope ascope; + using (new SafeCallContext()) + { + ascope = GetScopeAsync(accessor).Result; + } + Assert.IsNull(ascope); + + Assert.AreSame(scope, accessor.Scope); + scope.Dispose(); + Assert.IsNull(accessor.Scope); + } + + [Test] + public void DoesNotFlowWithThreadAndSafeCallContext() + { + var httpContextAccessor = new NoHttpContextAccessor(); + var accessor = new HybridDatabaseScopeAccessor(httpContextAccessor); + var scope = new DatabaseScope(accessor, null); + DatabaseScope tscope = null; + var thread = new Thread(() => tscope = accessor.Scope); + using (new SafeCallContext()) + { + thread.Start(); + } + thread.Join(); + Assert.IsNull(tscope); + + Assert.AreSame(scope, accessor.Scope); + scope.Dispose(); + Assert.IsNull(accessor.Scope); + } + + private static async Task GetScopeAsync(IDatabaseScopeAccessor accessor) + { + await Task.Delay(1); + return accessor.Scope; + } + } +} diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs index a21261ed68..0ade0f54ad 100644 --- a/src/Umbraco.Tests/Persistence/LocksTests.cs +++ b/src/Umbraco.Tests/Persistence/LocksTests.cs @@ -3,7 +3,6 @@ using System.Data; using System.Data.SqlServerCe; using System.Linq; using System.Threading; -using System.Web; using NPoco; using NUnit.Framework; using Umbraco.Core; @@ -31,18 +30,13 @@ namespace Umbraco.Tests.Persistence //Container.RegisterSingleton(); // but it should work with... - Container.RegisterSingleton(); + Container.RegisterSingleton(); Container.RegisterSingleton(); // + using SafeCallContext when starting threads // fixme - need to ensure that disposing of the DB properly removes it from context see 7.6 } - private class NoHttpContextAccessor : IHttpContextAccessor - { - public HttpContext HttpContext { get; set; } = null; - } - protected override void Initialize() { base.Initialize(); @@ -94,44 +88,10 @@ namespace Umbraco.Tests.Persistence for (var i = 0; i < threadCount; i++) { var index = i; // capture - threads[i] = new Thread(() => - { - var database = DatabaseContext.Database; - try - { - database.BeginTransaction(IsolationLevel.RepeatableRead); - } - catch (Exception e) - { - exceptions[index] = e; - return; - } - try - { - Console.WriteLine($"[{index}] WAIT"); - database.AcquireLockNodeReadLock(Constants.Locks.Servers); - Console.WriteLine($"[{index}] GRANT"); - lock (locker) - { - acquired++; - if (acquired == threadCount) ev.Set(); - } - ev.Wait(); - } - catch (Exception e) - { - exceptions[index] = e; - } - finally - { - Console.WriteLine($"[{index}] FREE"); - database.CompleteTransaction(); - database.Dispose(); - } - }); + threads[i] = new Thread(() => ConcurrentReadersTestThread(exceptions, index, locker, ref acquired, threadCount, ev)); } - // need safe call context else the current one leaks into *both* threads + // safe call context ensures that current scope does not leak into starting threads using (new SafeCallContext()) { foreach (var thread in threads) thread.Start(); @@ -143,6 +103,46 @@ namespace Umbraco.Tests.Persistence Assert.IsNull(exceptions[i]); } + private void ConcurrentReadersTestThread(Exception[] exceptions, int index, object locker, ref int acquired, int threadCount, ManualResetEventSlim ev) + { + // in a thread, must create a scope + using (DatabaseContext.CreateDatabaseScope()) + { + UmbracoDatabase database; + try + { + database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + } + catch (Exception e) + { + exceptions[index] = e; + return; + } + try + { + Console.WriteLine($"[{index}] WAIT"); + database.AcquireLockNodeReadLock(Constants.Locks.Servers); + Console.WriteLine($"[{index}] GRANT"); + lock (locker) + { + acquired++; + if (acquired == threadCount) ev.Set(); + } + ev.Wait(); + } + catch (Exception e) + { + exceptions[index] = e; + } + finally + { + Console.WriteLine($"[{index}] FREE"); + database.CompleteTransaction(); + } + } + } + [Test] public void ConcurrentWritersTest() { @@ -154,40 +154,10 @@ namespace Umbraco.Tests.Persistence for (var i = 0; i < threadCount; i++) { var index = i; - threads[i] = new Thread(() => - { - var database = DatabaseContext.Database; - try - { - database.BeginTransaction(IsolationLevel.RepeatableRead); - } - catch (Exception e) - { - exceptions[index] = e; - return; - } - try - { - Console.WriteLine($"[{index}] WAIT"); - database.AcquireLockNodeWriteLock(Constants.Locks.Servers); - Console.WriteLine($"[{index}] GRANT"); - lock (locker) - { - acquired++; - if (acquired > 0) throw new Exception("oops"); - } - Thread.Sleep(200); // keep the log for a little while - } - finally - { - Console.WriteLine($"[{index}] FREE"); - database.CompleteTransaction(); - database.Dispose(); - } - }); + threads[i] = new Thread(() => ConcurrentWritersTestThread(exceptions, index, locker, ref acquired)); } - // need safe call context else the current one leaks into *both* threads + // safe call context ensures that current scope does not leak into starting threads using (new SafeCallContext()) { foreach (var thread in threads) thread.Start(); @@ -199,6 +169,42 @@ namespace Umbraco.Tests.Persistence Assert.IsNull(exceptions[i]); } + private void ConcurrentWritersTestThread(Exception[] exceptions, int index, object locker, ref int acquired) + { + // in a thread, must create a scope + using (DatabaseContext.CreateDatabaseScope()) + { + UmbracoDatabase database; + try + { + database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + } + catch (Exception e) + { + exceptions[index] = e; + return; + } + try + { + Console.WriteLine($"[{index}] WAIT"); + database.AcquireLockNodeWriteLock(Constants.Locks.Servers); + Console.WriteLine($"[{index}] GRANT"); + lock (locker) + { + acquired++; + if (acquired > 0) throw new Exception("oops"); + } + Thread.Sleep(200); // keep the log for a little while + } + finally + { + Console.WriteLine($"[{index}] FREE"); + database.CompleteTransaction(); + } + } + } + [Test] public void DeadLockTest() { @@ -212,7 +218,7 @@ namespace Umbraco.Tests.Persistence var thread1 = new Thread(() => DeadLockTestThread(1, 2, ev1, ev2, ref e1)); var thread2 = new Thread(() => DeadLockTestThread(2, 1, ev2, ev1, ref e2)); - // need safe call context else the current one leaks into *both* threads + // safe call context ensures that current scope does not leak into starting threads using (new SafeCallContext()) { thread1.Start(); @@ -232,44 +238,48 @@ namespace Umbraco.Tests.Persistence private void DeadLockTestThread(int id1, int id2, EventWaitHandle myEv, WaitHandle otherEv, ref Exception exception) { - var database = DatabaseContext.Database; - try + // in a thread, must create a scope + using (DatabaseContext.CreateDatabaseScope()) { - database.BeginTransaction(IsolationLevel.RepeatableRead); - } - catch (Exception e) - { - exception = e; - return; - } - try - { - otherEv.WaitOne(); - Console.WriteLine($"[{id1}] WAIT {id1}"); - database.AcquireLockNodeWriteLock(id1); - Console.WriteLine($"[{id1}] GRANT {id1}"); - WriteLocks(database); - myEv.Set(); - - if (id1 == 1) + UmbracoDatabase database; + try + { + database = DatabaseContext.Database; + database.BeginTransaction(IsolationLevel.RepeatableRead); + } + catch (Exception e) + { + exception = e; + return; + } + try + { otherEv.WaitOne(); - else - Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time + Console.WriteLine($"[{id1}] WAIT {id1}"); + database.AcquireLockNodeWriteLock(id1); + Console.WriteLine($"[{id1}] GRANT {id1}"); + WriteLocks(database); + myEv.Set(); - Console.WriteLine($"[{id1}] WAIT {id2}"); - database.AcquireLockNodeWriteLock(id2); - Console.WriteLine($"[{id1}] GRANT {id2}"); - WriteLocks(database); - } - catch (Exception e) - { - Console.WriteLine($"[{id1}] EXCEPTION {e}"); - exception = e; - } - finally - { - database.CompleteTransaction(); - database.Dispose(); + if (id1 == 1) + otherEv.WaitOne(); + else + Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time + + Console.WriteLine($"[{id1}] WAIT {id2}"); + database.AcquireLockNodeWriteLock(id2); + Console.WriteLine($"[{id1}] GRANT {id2}"); + WriteLocks(database); + } + catch (Exception e) + { + Console.WriteLine($"[{id1}] EXCEPTION {e}"); + exception = e; + } + finally + { + database.CompleteTransaction(); + } } } diff --git a/src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs b/src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs index cfe08d304f..a462333114 100644 --- a/src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs +++ b/src/Umbraco.Tests/Persistence/Migrations/PostMigrationTests.cs @@ -14,6 +14,8 @@ namespace Umbraco.Tests.Persistence.Migrations [TestFixture] public class PostMigrationTests { + private TestObjects TestObjects = new TestObjects(null); + [Test] public void Executes_For_Any_Product_Name_When_Not_Specified() { diff --git a/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs b/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs index 86d743c625..68877b7a62 100644 --- a/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs +++ b/src/Umbraco.Tests/Persistence/UnitOfWorkTests.cs @@ -1,11 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.Persistence diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 0d43118331..c09ddb4def 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -1467,7 +1467,8 @@ namespace Umbraco.Tests.Services new TestDatabaseScopeAccessor(), Mappers); var repositoryFactory = MockRepositoryFactory(); - var provider = new NPocoUnitOfWorkProvider(new DatabaseContext(databaseFactory), repositoryFactory); + var databaseContext = new DatabaseContext(databaseFactory); + var provider = new NPocoUnitOfWorkProvider(databaseContext, repositoryFactory); var contentType = ServiceContext.ContentTypeService.Get("umbTextpage"); var root = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1); @@ -1475,6 +1476,7 @@ namespace Umbraco.Tests.Services var c2 = new Lazy(() => MockedContent.CreateSimpleContent(contentType, "Hierarchy Simple Text Subpage", c.Value.Id)); var list = new List> {c, c2}; + using (databaseContext.CreateDatabaseScope()) using (var unitOfWork = provider.CreateUnitOfWork()) { ContentTypeRepository contentTypeRepository; diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs index e98e549cc3..ebd01c254f 100644 --- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -14,7 +14,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; -using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -28,68 +27,18 @@ namespace Umbraco.Tests.Services [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class ThreadSafetyServiceTest : TestWithDatabaseBase { - //private IDatabaseUnitOfWorkProvider _uowProvider; - //private PerThreadSqlCeDatabaseFactory _dbFactory; - private IUmbracoDatabaseAccessor _accessor; - // private DatabaseContext _dbContext; - // private ServiceContext _services; - public override void SetUp() { base.SetUp(); - - // //we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that - // //a Database instance is created per thread, whereas the default implementation which will work in an HttpContext - // //threading environment, or a single apartment threading environment will not work for this test because - // //it is multi-threaded. - // _dbFactory = new PerThreadSqlCeDatabaseFactory(Logger, Mock.Of()); - // var repositoryFactory = new RepositoryFactory(Container); - // _uowProvider = new NPocoUnitOfWorkProvider(_dbFactory, repositoryFactory); - - // // overwrite the local object - // _dbContext = new DatabaseContext(_dbFactory, Logger, Mock.Of(), Mock.Of()); - - // //disable cache - // var cacheHelper = CacheHelper.CreateDisabledCacheHelper(); - - ////here we are going to override the ServiceContext because normally with our test cases we use a - ////global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. - ////we need a new Database object for each thread. - // var evtMsgs = new TransientEventMessagesFactory(); - // _services = TestObjects.GetServiceContext( - // repositoryFactory, - // _uowProvider, - // new FileUnitOfWorkProvider(), - // cacheHelper, - // Logger, - // evtMsgs, - // Enumerable.Empty()); - CreateTestData(); } protected override void Compose() { base.Compose(); - - //replace some services - //Container.Register(factory => _dbFactory); - //Container.Register(f => _dbFactory = new PerThreadSqlCeDatabaseFactory(f.GetInstance(), f.GetInstance())); - Container.RegisterSingleton(f => _accessor = new ThreadStaticUmbracoDatabaseAccessor()); // per-thread database - - //Container.Register(factory => _dbContext); - //Container.Register(factory => _services); + Container.RegisterSingleton(f => new ThreadStaticDatabaseScopeAccessor()); // per-thread database scope } - public override void TearDown() - { - // dispose! - // _dbFactory?.Dispose(); - _accessor.UmbracoDatabase?.Dispose(); - - base.TearDown(); - } - private const int MaxThreadCount = 20; [Test] @@ -106,34 +55,38 @@ namespace Umbraco.Tests.Services { var t = new Thread(() => { - try - { - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Create 1st content."); - var content1 = contentService.CreateContent("test" + Guid.NewGuid(), -1, "umbTextpage", 0); + using (DatabaseContext.CreateDatabaseScope()) + { + try + { + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Create 1st content."); + var content1 = contentService.CreateContent("test" + Guid.NewGuid(), -1, "umbTextpage", 0); - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Save 1st content."); - contentService.Save(content1); - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Saved 1st content."); + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Save 1st content."); + contentService.Save(content1); + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Saved 1st content."); - Thread.Sleep(100); //quick pause for maximum overlap! + Thread.Sleep(100); //quick pause for maximum overlap! - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Create 2nd content."); - var content2 = contentService.CreateContent("test" + Guid.NewGuid(), -1, "umbTextpage", 0); + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Create 2nd content."); + var content2 = contentService.CreateContent("test" + Guid.NewGuid(), -1, "umbTextpage", 0); - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Save 2nd content."); - contentService.Save(content2); - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Saved 2nd content."); + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Save 2nd content."); + contentService.Save(content2); + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Saved 2nd content."); + } + catch (Exception e) + { + //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Exception!"); + lock (exceptions) { exceptions.Add(e); } + } } - catch (Exception e) - { - //Debug.WriteLine($"[{Thread.CurrentThread.ManagedThreadId}] ({DateTime.Now.ToString("HH:mm:ss,FFF")}) Exception!"); - lock (exceptions) { exceptions.Add(e); } - } - }); + }); threads.Add(t); } // start all threads + // dont need SafeCallContext since db is threadstatic Debug.WriteLine("Starting threads"); threads.ForEach(x => x.Start()); @@ -173,35 +126,38 @@ namespace Umbraco.Tests.Services { var t = new Thread(() => { - try - { - //Debug.WriteLine("Created content on thread: " + Thread.CurrentThread.ManagedThreadId); + using (DatabaseContext.CreateDatabaseScope()) + { + try + { + //Debug.WriteLine("Created content on thread: " + Thread.CurrentThread.ManagedThreadId); - //create 2 content items + //create 2 content items - var folder1 = mediaService.CreateMedia("test" + Guid.NewGuid(), -1, Constants.Conventions.MediaTypes.Folder, 0); - //Debug.WriteLine("Saving folder1 on thread: " + Thread.CurrentThread.ManagedThreadId); - mediaService.Save(folder1, 0); + var folder1 = mediaService.CreateMedia("test" + Guid.NewGuid(), -1, Constants.Conventions.MediaTypes.Folder, 0); + //Debug.WriteLine("Saving folder1 on thread: " + Thread.CurrentThread.ManagedThreadId); + mediaService.Save(folder1, 0); - Thread.Sleep(100); //quick pause for maximum overlap! + Thread.Sleep(100); //quick pause for maximum overlap! - var folder2 = mediaService.CreateMedia("test" + Guid.NewGuid(), -1, Constants.Conventions.MediaTypes.Folder, 0); - //Debug.WriteLine("Saving folder2 on thread: " + Thread.CurrentThread.ManagedThreadId); - mediaService.Save(folder2, 0); - } - catch (Exception e) - { - lock (exceptions) { exceptions.Add(e); } + var folder2 = mediaService.CreateMedia("test" + Guid.NewGuid(), -1, Constants.Conventions.MediaTypes.Folder, 0); + //Debug.WriteLine("Saving folder2 on thread: " + Thread.CurrentThread.ManagedThreadId); + mediaService.Save(folder2, 0); + } + catch (Exception e) + { + lock (exceptions) { exceptions.Add(e); } + } } - }); + }); threads.Add(t); } - //start all threads - threads.ForEach(x => x.Start()); + //start all threads + threads.ForEach(x => x.Start()); - //wait for all to complete - threads.ForEach(x => x.Join()); + //wait for all to complete + threads.ForEach(x => x.Join()); //kill them all // uh? no! diff --git a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs index 8ec7e422e8..cd1b00f072 100644 --- a/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs +++ b/src/Umbraco.Tests/Templates/TemplateRepositoryTests.cs @@ -23,6 +23,8 @@ namespace Umbraco.Tests.Templates private readonly Mock _templateConfigMock = new Mock(); private TemplateRepository _templateRepository; + private TestObjects TestObjects = new TestObjects(null); + [SetUp] public void Setup() { diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index e810e9f317..fa6d2ef3c9 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -21,6 +21,8 @@ namespace Umbraco.Tests.TestHelpers protected SqlContext SqlContext { get; private set; } + internal TestObjects TestObjects = new TestObjects(null); + protected Sql Sql() { return NPoco.Sql.BuilderFor(SqlContext); diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs index c63c6b218b..c3650314ef 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs @@ -61,10 +61,15 @@ namespace Umbraco.Tests.TestHelpers "; } - internal static FacadeRouter CreateFacadeRouter(IServiceContainer container = null, ContentFinderCollection contentFinders = null) + internal FacadeRouter CreateFacadeRouter(IServiceContainer container = null, ContentFinderCollection contentFinders = null) + { + return CreateFacadeRouter(TestObjects.GetUmbracoSettings().WebRouting, container, contentFinders); + } + + internal static FacadeRouter CreateFacadeRouter(IWebRoutingSection webRoutingSection, IServiceContainer container = null, ContentFinderCollection contentFinders = null) { return new FacadeRouter( - TestObjects.GetUmbracoSettings().WebRouting, + webRoutingSection, contentFinders ?? new ContentFinderCollection(Enumerable.Empty()), new TestLastChanceFinder(), container?.TryGetInstance() ?? new ServiceContext(), diff --git a/src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs b/src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs new file mode 100644 index 0000000000..a2ee80a113 --- /dev/null +++ b/src/Umbraco.Tests/TestHelpers/NoHttpContextAccessor.cs @@ -0,0 +1,10 @@ +using System.Web; +using Umbraco.Web; + +namespace Umbraco.Tests.TestHelpers +{ + public class NoHttpContextAccessor : IHttpContextAccessor + { + public HttpContext HttpContext { get; set; } = null; + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoDatabaseAccessor.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoDatabaseAccessor.cs deleted file mode 100644 index 216467a4bc..0000000000 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestUmbracoDatabaseAccessor.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Umbraco.Core.Persistence; - -namespace Umbraco.Tests.TestHelpers.Stubs -{ - public class TestUmbracoDatabaseAccessor : IUmbracoDatabaseAccessor - { - public UmbracoDatabase UmbracoDatabase { get; set; } - } -} diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs index 730c934c42..ac86ffe529 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects-Mocks.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.TestHelpers /// /// Provides objects for tests. /// - internal static partial class TestObjects + internal partial class TestObjects { /// /// Gets a mocked IDatabaseFactory. @@ -29,7 +29,7 @@ namespace Umbraco.Tests.TestHelpers /// A value indicating whether the factory is configured. /// A value indicating whether the factory can connect to the database. /// This is just a void factory that has no actual database. - public static IDatabaseFactory GetDatabaseFactoryMock(bool configured = true, bool canConnect = true) + public IDatabaseFactory GetDatabaseFactoryMock(bool configured = true, bool canConnect = true) { var databaseFactoryMock = new Mock(); databaseFactoryMock.Setup(x => x.Configured).Returns(configured); @@ -37,7 +37,7 @@ namespace Umbraco.Tests.TestHelpers // can get a database - but don't try to use it! if (configured && canConnect) - databaseFactoryMock.Setup(x => x.GetDatabase()).Returns(TestObjects.GetUmbracoSqlCeDatabase(Mock.Of())); + databaseFactoryMock.Setup(x => x.GetDatabase()).Returns(GetUmbracoSqlCeDatabase(Mock.Of())); return databaseFactoryMock.Object; } @@ -46,7 +46,7 @@ namespace Umbraco.Tests.TestHelpers /// Gets a mocked service context built with mocked services. /// /// A ServiceContext. - public static ServiceContext GetServiceContextMock(IServiceFactory container = null) + public ServiceContext GetServiceContextMock(IServiceFactory container = null) { return new ServiceContext( MockService(), @@ -74,7 +74,7 @@ namespace Umbraco.Tests.TestHelpers MockService()); } - private static T MockService(IServiceFactory container = null) + private T MockService(IServiceFactory container = null) where T : class { return container?.TryGetInstance() ?? new Mock().Object; @@ -86,7 +86,7 @@ namespace Umbraco.Tests.TestHelpers /// A DbConnection. /// This is because NPoco wants a DbConnection, NOT an IDbConnection, /// and DbConnection is hard to mock so we create our own class here. - public static DbConnection GetDbConnection() + public DbConnection GetDbConnection() { return new MockDbConnection(); } @@ -96,7 +96,7 @@ namespace Umbraco.Tests.TestHelpers /// /// An Umbraco context. /// This should be the minimum Umbraco context. - public static UmbracoContext GetUmbracoContextMock(IUmbracoContextAccessor accessor = null) + public UmbracoContext GetUmbracoContextMock(IUmbracoContextAccessor accessor = null) { var httpContext = Mock.Of(); @@ -116,7 +116,7 @@ namespace Umbraco.Tests.TestHelpers return UmbracoContext.EnsureContext(accessor, httpContext, facadeService, webSecurity, settings, urlProviders, true); } - public static IUmbracoSettingsSection GetUmbracoSettings() + public IUmbracoSettingsSection GetUmbracoSettings() { var umbracoSettingsMock = new Mock(); var webRoutingSectionMock = new Mock(); diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index e6cc8f8e65..32df3534e2 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -27,15 +27,22 @@ namespace Umbraco.Tests.TestHelpers /// /// Provides objects for tests. /// - internal static partial class TestObjects + internal partial class TestObjects { + private readonly IServiceContainer _container; + + public TestObjects(IServiceContainer container) + { + _container = container; + } + /// /// Gets the default ISqlSyntaxProvider objects. /// /// A logger. /// A (lazy) database factory. /// The default ISqlSyntaxProvider objects. - public static IEnumerable GetDefaultSqlSyntaxProviders(ILogger logger, Lazy lazyFactory = null) + public IEnumerable GetDefaultSqlSyntaxProviders(ILogger logger, Lazy lazyFactory = null) { return new ISqlSyntaxProvider[] { @@ -52,7 +59,7 @@ namespace Umbraco.Tests.TestHelpers /// An UmbracoDatabase. /// This is just a void database that has no actual database but pretends to have an open connection /// that can begin a transaction. - public static UmbracoDatabase GetUmbracoSqlCeDatabase(ILogger logger) + public UmbracoDatabase GetUmbracoSqlCeDatabase(ILogger logger) { var syntax = new SqlCeSyntaxProvider(); var connection = GetDbConnection(); @@ -67,7 +74,7 @@ namespace Umbraco.Tests.TestHelpers /// An UmbracoDatabase. /// This is just a void database that has no actual database but pretends to have an open connection /// that can begin a transaction. - public static UmbracoDatabase GetUmbracoSqlServerDatabase(ILogger logger) + public UmbracoDatabase GetUmbracoSqlServerDatabase(ILogger logger) { var syntax = new SqlServerSyntaxProvider(new Lazy(() => null)); // do NOT try to get the server's version! var connection = GetDbConnection(); @@ -75,7 +82,7 @@ namespace Umbraco.Tests.TestHelpers return new UmbracoDatabase(connection, sqlContext, logger); } - public static void RegisterServices(IServiceContainer container) + public void RegisterServices(IServiceContainer container) { } /// @@ -92,7 +99,7 @@ namespace Umbraco.Tests.TestHelpers /// A ServiceContext. /// Should be used sparingly for integration tests only - for unit tests /// just mock the services to be passed to the ctor of the ServiceContext. - public static ServiceContext GetServiceContext(RepositoryFactory repositoryFactory, + public ServiceContext GetServiceContext(RepositoryFactory repositoryFactory, IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, CacheHelper cache, @@ -211,17 +218,17 @@ namespace Umbraco.Tests.TestHelpers redirectUrlService); } - private static Lazy GetLazyService(IServiceFactory container, Func ctor) + private Lazy GetLazyService(IServiceFactory container, Func ctor) where T : class { return new Lazy(() => container?.TryGetInstance() ?? ctor()); } - public static IDatabaseUnitOfWorkProvider GetDatabaseUnitOfWorkProvider(ILogger logger, IDatabaseFactory databaseFactory = null, RepositoryFactory repositoryFactory = null) + public IDatabaseUnitOfWorkProvider GetDatabaseUnitOfWorkProvider(ILogger logger, IDatabaseFactory databaseFactory = null, RepositoryFactory repositoryFactory = null) { if (databaseFactory == null) { - var accessor = new TestDatabaseScopeAccessor(); + var accessor = _container.TryGetInstance() ?? new TestDatabaseScopeAccessor(); //var mappersBuilder = new MapperCollectionBuilder(Current.Container); // fixme //mappersBuilder.AddCore(); //var mappers = mappersBuilder.CreateCollection(); diff --git a/src/Umbraco.Tests/TestHelpers/TestWithApplicationBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithApplicationBase.cs index 4cc246aefd..e18bffa5b6 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithApplicationBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithApplicationBase.cs @@ -118,7 +118,7 @@ namespace Umbraco.Tests.TestHelpers .AddCore(); Container.RegisterSingleton(_ => new TransientEventMessagesFactory()); - Container.RegisterSingleton(); + Container.RegisterSingleton(); var sqlSyntaxProviders = TestObjects.GetDefaultSqlSyntaxProviders(Logger); Container.RegisterSingleton(_ => sqlSyntaxProviders.OfType().First()); Container.RegisterSingleton(f => new UmbracoDatabaseFactory( diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 747e37f0d8..59e49e0794 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -51,6 +51,7 @@ namespace Umbraco.Tests.TestHelpers { private CacheHelper _disabledCacheHelper; private IFacadeService _facadeService; + private IDisposable _databaseScope; // note: a fixture class is created once for all the tests in that fixture // these flags are used to ensure a new database file is used when appropriate @@ -136,6 +137,8 @@ namespace Umbraco.Tests.TestHelpers timer?.Dispose(); } + _databaseScope?.Dispose(); + base.TearDown(); } @@ -284,6 +287,8 @@ namespace Umbraco.Tests.TestHelpers if (Options.Database == UmbracoTestOptions.Database.None || Options.Database == UmbracoTestOptions.Database.NewEmptyPerTest) return; + _databaseScope = Core.DI.Current.DatabaseContext.CreateDatabaseScope(); + //create the schema and load default data if: // - is the first test in the session // - NewDbFileAndSchemaPerTest diff --git a/src/Umbraco.Tests/TestHelpers/UmbracoTestBase.cs b/src/Umbraco.Tests/TestHelpers/UmbracoTestBase.cs index f7edc7fc99..d96b443e8e 100644 --- a/src/Umbraco.Tests/TestHelpers/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/TestHelpers/UmbracoTestBase.cs @@ -54,6 +54,8 @@ namespace Umbraco.Tests.TestHelpers protected UmbracoTestAttribute Options { get; private set; } + internal TestObjects TestObjects { get; private set; } + private static PluginManager _pluginManager; private static bool _firstDatabaseInSession = true; private bool _firstDatabaseInFixture = true; @@ -68,6 +70,8 @@ namespace Umbraco.Tests.TestHelpers Container = new ServiceContainer(); Container.ConfigureUmbracoCore(); + TestObjects = new TestObjects(Container); + // get/merge the attributes marking the method and/or the classes var testName = TestContext.CurrentContext.Test.Name; var pos = testName.IndexOf('('); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7a9e18f5d5..e687b2aa1c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -234,6 +234,8 @@ + + @@ -252,7 +254,6 @@ - diff --git a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs index f95cf09499..d488ab021f 100644 --- a/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/RenderIndexActionSelectorAttributeTests.cs @@ -39,6 +39,8 @@ namespace Umbraco.Tests.Web.Mvc Current.Reset(); } + private TestObjects TestObjects = new TestObjects(null); + private MethodInfo GetRenderMvcControllerIndexMethodFromCurrentType(Type currType) { return currType.GetMethods().Single(x => diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs index 690a3a709e..8639e591da 100644 --- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs @@ -140,7 +140,7 @@ namespace Umbraco.Tests.Web.Mvc var content = Mock.Of(publishedContent => publishedContent.Id == 12345); var contextBase = umbracoContext.HttpContext; - var facadeRouter = BaseWebTest.CreateFacadeRouter(); + var facadeRouter = BaseWebTest.CreateFacadeRouter(TestObjects.GetUmbracoSettings().WebRouting); var frequest = facadeRouter.CreateRequest(umbracoContext, new Uri("http://localhost/test")); frequest.PublishedContent = content; diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs index a900320fcd..1709a267ae 100644 --- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs +++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs @@ -390,7 +390,7 @@ namespace Umbraco.Tests.Web.Mvc logger, settings, "/dang", 0); - var facadeRouter = BaseWebTest.CreateFacadeRouter(); + var facadeRouter = BaseWebTest.CreateFacadeRouter(TestObjects.GetUmbracoSettings().WebRouting); var frequest = facadeRouter.CreateRequest(umbracoContext, new Uri("http://localhost/dang")); frequest.Culture = CultureInfo.InvariantCulture; diff --git a/src/Umbraco.Web/HybridUmbracoDatabaseAccessor.cs b/src/Umbraco.Web/HybridUmbracoDatabaseAccessor.cs deleted file mode 100644 index b137df9a7a..0000000000 --- a/src/Umbraco.Web/HybridUmbracoDatabaseAccessor.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Umbraco.Core.Persistence; - -namespace Umbraco.Web -{ - internal class HybridUmbracoDatabaseAccessor : HybridAccessorBase, IUmbracoDatabaseAccessor - { - protected override string ItemKey => "Umbraco.Core.Persistence.HybridUmbracoDatabaseAccessor"; - - public HybridUmbracoDatabaseAccessor(IHttpContextAccessor httpContextAccessor) - : base(httpContextAccessor) - { } - - public UmbracoDatabase UmbracoDatabase - { - get { return Value; } - set { Value = value; } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 89d3c0d4f1..1dc4ea36e1 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -171,7 +171,6 @@ - diff --git a/src/Umbraco.Web/WebRuntime.cs b/src/Umbraco.Web/WebRuntime.cs index cd27f06cbe..fc0c7adabb 100644 --- a/src/Umbraco.Web/WebRuntime.cs +++ b/src/Umbraco.Web/WebRuntime.cs @@ -73,7 +73,7 @@ namespace Umbraco.Web new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())))); container.RegisterSingleton(); // required for hybrid accessors - container.RegisterSingleton(); // replace CoreRuntime's accessor + container.RegisterSingleton(); // replace CoreRuntime's accessor } #region Getters