diff --git a/.gitignore b/.gitignore
index 022acb5db7..ea2ddfbb68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -191,6 +191,7 @@ src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb
/src/Umbraco.Web.UI.NetCore/App_Data/Smidge/Cache/*
/src/Umbraco.Web.UI.NetCore/umbraco/logs
+src/Umbraco.Tests.Integration/umbraco/Data/
src/Umbraco.Tests.Integration/umbraco/logs/
src/Umbraco.Tests.Integration/Views/
diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
index 7c17a8a338..e2be3569d6 100644
--- a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
+++ b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
@@ -1,19 +1,29 @@
-using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
namespace Umbraco.Core.Composing
{
///
- /// Extends the to enable Umbraco to be used as the service container.
+ /// Extends the to add CoreRuntime as a HostedService
///
public static class HostBuilderExtensions
{
///
- /// Assigns a custom service provider factory to use Umbraco's container
+ /// Adds CoreRuntime as HostedService
///
- ///
- ///
+ ///
+ /// When running the site should be called before ConfigureWebDefaults.
+ ///
+ ///
+ /// When testing should be called after ConfigureWebDefaults to ensure UseTestDatabase is called before CoreRuntime
+ /// starts or we initialize components with incorrect run level.
+ ///
+ ///
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
{
+ _ = builder.ConfigureServices((context, services) =>
+ services.AddSingleton(factory => factory.GetRequiredService()));
+
return builder;
}
}
diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
index 97e3df16a6..82c2fa8dd1 100644
--- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
+++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
@@ -135,16 +135,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
_entitySerializer = entitySerializer;
_publishedModelFactory = publishedModelFactory;
- // we always want to handle repository events, configured or not
- // assuming no repository event will trigger before the whole db is ready
- // (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
- InitializeRepositoryEvents();
-
_lifeTime.ApplicationInit += OnApplicationInit;
}
internal void OnApplicationInit(object sender, EventArgs e)
{
+ // we always want to handle repository events, configured or not
+ // assuming no repository event will trigger before the whole db is ready
+ // (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
+ InitializeRepositoryEvents();
+
// however, the cache is NOT available until we are configured, because loading
// content (and content types) from database cannot be consistent (see notes in "Handle
// Notifications" region), so
diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs
index 1e3317262a..5d721ad176 100644
--- a/src/Umbraco.Tests.Integration/RuntimeTests.cs
+++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs
@@ -43,7 +43,6 @@ namespace Umbraco.Tests.Integration
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
- .UseUmbraco()
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
@@ -61,14 +60,16 @@ namespace Umbraco.Tests.Integration
hostContext.Configuration,
testHelper.Profiler);
- var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader, testHelper.ConsoleLoggerFactory);
+ var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader,
+ testHelper.ConsoleLoggerFactory);
builder.Services.AddUnique(AppCaches.NoCache);
builder.AddConfiguration()
- .AddUmbracoCore()
- .Build();
+ .AddUmbracoCore()
+ .Build();
services.AddRouting(); // LinkGenerator
- });
+ })
+ .UseUmbraco();
var host = await hostBuilder.StartAsync();
var app = new ApplicationBuilder(host.Services);
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
index c3c01ed977..87ac4505e5 100644
--- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
+++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
@@ -1,8 +1,6 @@
-
using System;
using System.Linq.Expressions;
using System.Net.Http;
-using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -11,7 +9,6 @@ using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Extensions;
@@ -22,8 +19,7 @@ using Umbraco.Core.DependencyInjection;
using Umbraco.Web.Common.Controllers;
using Microsoft.Extensions.Hosting;
using Umbraco.Core.Cache;
-using Umbraco.Core.Persistence;
-using Umbraco.Core.Runtime;
+using Umbraco.Core.Composing;
using Umbraco.Web.BackOffice.Controllers;
namespace Umbraco.Tests.Integration.TestServerTest
@@ -75,11 +71,14 @@ namespace Umbraco.Tests.Integration.TestServerTest
// call startup
builder.Configure(app =>
{
- UseTestLocalDb(app.ApplicationServices);
+ UseTestDatabase(app.ApplicationServices);
Services = app.ApplicationServices;
Configure(app);
});
- }).UseEnvironment(Environments.Development);
+
+ }).UseEnvironment(Environments.Development);
+
+ builder.UseUmbraco(); // Ensures CoreRuntime.StartAsync is called, must be after ConfigureWebHost
return builder;
}
diff --git a/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs b/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs
index 9bcbfa4d3a..60c767e17e 100644
--- a/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs
+++ b/src/Umbraco.Tests.Integration/Testing/TestDatabaseFactory.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Persistence;
@@ -8,13 +9,20 @@ namespace Umbraco.Tests.Integration.Testing
{
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());
+ var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
+
+ return string.IsNullOrEmpty(connectionString)
+ ? CreateLocalDb(filesPath, loggerFactory, dbFactory)
+ : CreateSqlDeveloper(loggerFactory, dbFactory, connectionString);
}
- private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
+ private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
+ if (!Directory.Exists(filesPath))
+ {
+ Directory.CreateDirectory(filesPath);
+ }
+
var localDb = new LocalDb();
if (!localDb.IsAvailable)
@@ -22,22 +30,21 @@ namespace Umbraco.Tests.Integration.Testing
throw new InvalidOperationException("LocalDB is not available.");
}
- return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
+ return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory.Create());
}
- private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
+ private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory, string connectionString)
{
+ // NOTE: Example setup for Linux box.
// $ 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);
+ return new SqlDeveloperTestDatabase(loggerFactory, dbFactory.Create(), connectionString);
}
}
}
diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
index f72139576e..5fb3c50cc8 100644
--- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
+++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
@@ -97,10 +97,10 @@ namespace Umbraco.Tests.Integration.Testing
public virtual void Setup()
{
InMemoryConfiguration[Constants.Configuration.ConfigGlobal + ":" + nameof(GlobalSettings.InstallEmptyDatabase)] = "true";
- var hostBuilder = CreateHostBuilder();
+ var hostBuilder = CreateHostBuilder()
+ .UseUmbraco(); // This ensures CoreRuntime.StartAsync will be called (however it's a mock if boot = false)
var host = hostBuilder.Start();
-
Services = host.Services;
var app = new ApplicationBuilder(host.Services);
@@ -113,8 +113,7 @@ namespace Umbraco.Tests.Integration.Testing
{
try
{
- var testOptions = TestOptionAttributeBase.GetTestOptions();
- switch (testOptions.Logger)
+ switch (TestOptions.Logger)
{
case UmbracoTestOptions.Logger.Mock:
return NullLoggerFactory.Instance;
@@ -138,15 +137,13 @@ namespace Umbraco.Tests.Integration.Testing
///
public virtual IHostBuilder CreateHostBuilder()
{
-
- var testOptions = TestOptionAttributeBase.GetTestOptions();
var hostBuilder = Host.CreateDefaultBuilder()
// IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work
// if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will
// create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves,
// and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly.
//.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); })
- .UseUmbraco()
+
.ConfigureAppConfiguration((context, configBuilder) =>
{
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
@@ -157,12 +154,13 @@ namespace Umbraco.Tests.Integration.Testing
})
.ConfigureServices((hostContext, services) =>
{
- services.AddTransient(_ => CreateLoggerFactory());
ConfigureServices(services);
+ services.AddUnique(CreateLoggerFactory());
- if (!testOptions.Boot)
+ if (!TestOptions.Boot)
{
// If boot is false, we don't want the CoreRuntime hosted service to start
+ // So we replace it with a Mock
services.AddUnique(Mock.Of());
}
});
@@ -224,11 +222,15 @@ namespace Umbraco.Tests.Integration.Testing
public virtual void Configure(IApplicationBuilder app)
{
- UseTestLocalDb(app.ApplicationServices);
+ UseTestDatabase(app.ApplicationServices);
- Services.GetRequiredService().EnsureBackOfficeSecurity();
- Services.GetRequiredService().EnsureUmbracoContext();
- app.UseUmbracoCore();
+ if (TestOptions.Boot)
+ {
+ Services.GetRequiredService().EnsureBackOfficeSecurity();
+ Services.GetRequiredService().EnsureUmbracoContext();
+ }
+
+ app.UseUmbracoCore(); // This no longer starts CoreRuntime, it's very fast
}
#endregion
@@ -239,25 +241,20 @@ namespace Umbraco.Tests.Integration.Testing
private static ITestDatabase _dbInstance;
private static TestDbMeta _fixtureDbMeta;
- protected void UseTestLocalDb(IServiceProvider serviceProvider)
+ protected void UseTestDatabase(IServiceProvider serviceProvider)
{
var state = serviceProvider.GetRequiredService();
var testDatabaseFactoryProvider = serviceProvider.GetRequiredService();
var databaseFactory = serviceProvider.GetRequiredService();
+ var loggerFactory = serviceProvider.GetRequiredService();
// This will create a db, install the schema and ensure the app is configured to run
- InstallTestLocalDb(testDatabaseFactoryProvider, databaseFactory, serviceProvider.GetRequiredService(), state, TestHelper.WorkingDirectory);
+ SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory);
}
///
- /// Get or create an instance of
+ /// Get or create an instance of
///
- ///
- ///
- ///
- ///
- ///
- ///
///
/// There must only be ONE instance shared between all tests in a session
///
@@ -266,9 +263,12 @@ namespace Umbraco.Tests.Integration.Testing
lock (_dbLocker)
{
if (_dbInstance != null)
+ {
return _dbInstance;
+ }
_dbInstance = TestDatabaseFactory.Create(filesPath, loggerFactory, dbFactory);
+
return _dbInstance;
}
}
@@ -276,30 +276,26 @@ namespace Umbraco.Tests.Integration.Testing
///
/// Creates a LocalDb instance to use for the test
///
- private void InstallTestLocalDb(
+ private void SetupTestDatabase(
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
IUmbracoDatabaseFactory databaseFactory,
ILoggerFactory loggerFactory,
IRuntimeState runtimeState,
string workingDirectory)
{
- var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
-
- // get the currently set db options
- var testOptions = TestOptionAttributeBase.GetTestOptions();
-
- if (testOptions.Database == UmbracoTestOptions.Database.None)
+ if (TestOptions.Database == UmbracoTestOptions.Database.None)
+ {
return;
+ }
// need to manually register this factory
DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
- if (!Directory.Exists(dbFilePath))
- Directory.CreateDirectory(dbFilePath);
+ var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider);
- switch (testOptions.Database)
+ switch (TestOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
@@ -385,7 +381,7 @@ namespace Umbraco.Tests.Integration.Testing
break;
default:
- throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
+ throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null);
}
}
@@ -393,6 +389,8 @@ namespace Umbraco.Tests.Integration.Testing
#region Common services
+ protected UmbracoTestAttribute TestOptions => TestOptionAttributeBase.GetTestOptions();
+
protected virtual T GetRequiredService() => Services.GetRequiredService();
public Dictionary InMemoryConfiguration { get; } = new Dictionary();
diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
index 9ecda715ee..b8dccfbe33 100644
--- a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
@@ -146,9 +146,7 @@ namespace Umbraco.Core.DependencyInjection
builder.Services.AddUnique();
builder.Services.AddUnique();
builder.Services.AddUnique();
-
builder.Services.AddUnique();
- builder.Services.AddHostedService(factory => factory.GetRequiredService());
builder.AddCoreInitialServices();
builder.AddComposers();
diff --git a/src/Umbraco.Web.UI.NetCore/Program.cs b/src/Umbraco.Web.UI.NetCore/Program.cs
index 4a7722597d..95322cb1b0 100644
--- a/src/Umbraco.Web.UI.NetCore/Program.cs
+++ b/src/Umbraco.Web.UI.NetCore/Program.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Web.UI.NetCore
{
x.ClearProviders();
})
- .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
- .UseUmbraco();
+ .UseUmbraco()
+ .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); });
}
}