From 3e082f1a94ae36df84c79a59bd5fd15ae5045614 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 12 Dec 2018 14:28:57 +0100 Subject: [PATCH] Restructure registration of essential services, validate --- src/Umbraco.Core/Components/Components.cs | 3 +- src/Umbraco.Core/Components/Composition.cs | 12 +- .../Composing/CompositionExtensions.cs | 30 +- src/Umbraco.Core/Composing/IFactory.cs | 5 + src/Umbraco.Core/Composing/IRegister.cs | 2 +- .../LightInject/LightInjectContainer.cs | 5 +- src/Umbraco.Core/Composing/TypeLoader.cs | 47 ++- src/Umbraco.Core/Runtime/CoreRuntime.cs | 17 +- .../DistributedCache/DistributedCacheTests.cs | 3 +- .../Components/ComponentTests.cs | 65 ++-- .../Composing/CollectionBuildersTests.cs | 3 +- .../Composing/ComposingTestBase.cs | 3 +- .../Composing/LazyCollectionBuilderTests.cs | 11 +- .../Composing/LightInjectValidation.cs | 351 ++++++++++++++++++ .../Composing/PackageActionCollectionTests.cs | 3 +- .../Composing/TypeLoaderTests.cs | 3 +- src/Umbraco.Tests/CoreThings/UdiTests.cs | 3 +- .../FrontEnd/UmbracoHelperTests.cs | 3 +- src/Umbraco.Tests/IO/FileSystemsTests.cs | 3 +- .../PropertyEditors/ImageCropperTest.cs | 3 +- .../PropertyEditorValueEditorTests.cs | 3 +- .../Published/ConvertersTests.cs | 3 +- src/Umbraco.Tests/Runtimes/StandaloneTests.cs | 181 +++++++++ ...meTests.cs => WebRuntimeComponentTests.cs} | 9 +- .../Scoping/ScopeEventDispatcherTests.cs | 3 +- .../TestHelpers/BaseUsingSqlCeSyntax.cs | 5 +- src/Umbraco.Tests/Testing/UmbracoTestBase.cs | 5 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +- .../Web/TemplateUtilitiesTests.cs | 2 +- src/Umbraco.Web/Runtime/WebRuntime.cs | 11 - .../Runtime/WebRuntimeComponent.cs | 2 +- .../Search/UmbracoIndexesCreator.cs | 10 +- src/Umbraco.Web/UmbracoApplicationBase.cs | 1 - 33 files changed, 688 insertions(+), 126 deletions(-) create mode 100644 src/Umbraco.Tests/Composing/LightInjectValidation.cs create mode 100644 src/Umbraco.Tests/Runtimes/StandaloneTests.cs rename src/Umbraco.Tests/Runtimes/{WebRuntimeTests.cs => WebRuntimeComponentTests.cs} (92%) diff --git a/src/Umbraco.Core/Components/Components.cs b/src/Umbraco.Core/Components/Components.cs index dffa471be6..d4d9226f9a 100644 --- a/src/Umbraco.Core/Components/Components.cs +++ b/src/Umbraco.Core/Components/Components.cs @@ -77,7 +77,7 @@ namespace Umbraco.Core.Components // otherwise, user components have Run min level, anything else is Unknown (always run) var attr = x.GetCustomAttribute(); var minLevel = attr?.MinLevel ?? (x.Implements() ? RuntimeLevel.Run : RuntimeLevel.Unknown); - return _composition.RuntimeLevel >= minLevel; + return _composition.RuntimeState.Level >= minLevel; }) .ToList(); @@ -114,7 +114,6 @@ namespace Umbraco.Core.Components // bit verbose but should help for troubleshooting var text = "Ordered Components: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComponentTypes) + Environment.NewLine; - Console.WriteLine(text); _logger.Debug("Ordered Components: {SortedComponentTypes}", sortedComponentTypes); return sortedComponentTypes; diff --git a/src/Umbraco.Core/Components/Composition.cs b/src/Umbraco.Core/Components/Composition.cs index 2d8b89fcda..cc52b2d814 100644 --- a/src/Umbraco.Core/Components/Composition.cs +++ b/src/Umbraco.Core/Components/Composition.cs @@ -25,13 +25,13 @@ namespace Umbraco.Core.Components /// A register. /// A type loader. /// A logger. - /// The runtime level. - public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, RuntimeLevel level) + /// The runtime state. + public Composition(IRegister register, TypeLoader typeLoader, IProfilingLogger logger, IRuntimeState runtimeState) { _register = register; TypeLoader = typeLoader; Logger = logger; - RuntimeLevel = level; + RuntimeState = runtimeState; } #region Services @@ -47,16 +47,16 @@ namespace Umbraco.Core.Components public TypeLoader TypeLoader { get; } /// - /// Gets the runtime level. + /// Gets the runtime state. /// - public RuntimeLevel RuntimeLevel { get; } + public IRuntimeState RuntimeState { get; } #endregion #region IRegister /// - public object ConcreteContainer => _register.ConcreteContainer; + public object Concrete => _register.Concrete; /// public void Register(Type serviceType, Lifetime lifetime = Lifetime.Transient) diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs index bab3bb7fa5..9c0b84cf95 100644 --- a/src/Umbraco.Core/Composing/CompositionExtensions.cs +++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs @@ -1,4 +1,7 @@ -using Umbraco.Core.Components; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; namespace Umbraco.Core.Composing { @@ -7,6 +10,31 @@ namespace Umbraco.Core.Composing /// public static class CompositionExtensions { + #region Essentials + + /// + /// Registers essential services. + /// + public static void RegisterEssentials(this Composition composition, + ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger, + CacheHelper appCaches, + IUmbracoDatabaseFactory databaseFactory, + TypeLoader typeLoader, + IRuntimeState state) + { + composition.RegisterUnique(logger); + composition.RegisterUnique(profiler); + composition.RegisterUnique(profilingLogger); + composition.RegisterUnique(appCaches); + composition.RegisterUnique(factory => factory.GetInstance().RuntimeCache); + composition.RegisterUnique(databaseFactory); + composition.RegisterUnique(factory => factory.GetInstance().SqlContext); + composition.RegisterUnique(typeLoader); + composition.RegisterUnique(state); + } + + #endregion + #region Unique /// diff --git a/src/Umbraco.Core/Composing/IFactory.cs b/src/Umbraco.Core/Composing/IFactory.cs index b753b3a1df..9a59b1c052 100644 --- a/src/Umbraco.Core/Composing/IFactory.cs +++ b/src/Umbraco.Core/Composing/IFactory.cs @@ -15,6 +15,11 @@ namespace Umbraco.Core.Composing /// public interface IFactory { + /// + /// Gets the concrete factory. + /// + object Concrete { get; } + /// /// Gets an instance of a service. /// diff --git a/src/Umbraco.Core/Composing/IRegister.cs b/src/Umbraco.Core/Composing/IRegister.cs index 747f3447b6..8ad3db5409 100644 --- a/src/Umbraco.Core/Composing/IRegister.cs +++ b/src/Umbraco.Core/Composing/IRegister.cs @@ -21,7 +21,7 @@ namespace Umbraco.Core.Composing /// /// Gets the concrete container. /// - object ConcreteContainer { get; } + object Concrete { get; } /// /// Registers a service as its own implementation. diff --git a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs index 58bc8d2d44..b39622f66a 100644 --- a/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs +++ b/src/Umbraco.Core/Composing/LightInject/LightInjectContainer.cs @@ -86,8 +86,9 @@ namespace Umbraco.Core.Composing.LightInject /// protected ServiceContainer Container { get; } - /// - public object ConcreteContainer => Container; + /// + /// + public object Concrete => Container; /// public void Dispose() diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index cba92ada64..2a7bf69e8b 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -29,30 +29,29 @@ namespace Umbraco.Core.Composing private const string CacheKey = "umbraco-types.list"; private readonly IRuntimeCacheProvider _runtimeCache; - private readonly IGlobalSettings _globalSettings; private readonly IProfilingLogger _logger; - private readonly object _typesLock = new object(); + private readonly object _locko = new object(); private readonly Dictionary _types = new Dictionary(); private string _cachedAssembliesHash; private string _currentAssembliesHash; private IEnumerable _assemblies; private bool _reportedChange; - private static LocalTempStorage _localTempStorage = LocalTempStorage.Unknown; + private static LocalTempStorage _localTempStorage; private static string _fileBasePath; /// /// Initializes a new instance of the class. /// /// The application runtime cache. - /// + /// Files storage mode. /// A profiling logger. /// Whether to detect changes using hashes. - internal TypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger, bool detectChanges = true) + internal TypeLoader(IRuntimeCacheProvider runtimeCache, LocalTempStorage localTempStorage, IProfilingLogger logger, bool detectChanges = true) { _runtimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); + _localTempStorage = localTempStorage == LocalTempStorage.Unknown ? LocalTempStorage.Default : localTempStorage; _logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (detectChanges) @@ -370,14 +369,15 @@ namespace Umbraco.Core.Composing private string GetFileBasePath() { - var localTempStorage = _globalSettings.LocalTempStorageLocation; - if (_localTempStorage != localTempStorage) + lock (_locko) { - string path; - switch (_globalSettings.LocalTempStorageLocation) + if (_fileBasePath != null) + return _fileBasePath; + + switch (_localTempStorage) { case LocalTempStorage.AspNetTemp: - path = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types"); + _fileBasePath = Path.Combine(HttpRuntime.CodegenDir, "UmbracoData", "umbraco-types"); break; case LocalTempStorage.EnvironmentTemp: // include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back @@ -385,27 +385,24 @@ namespace Umbraco.Core.Composing // utilizing an old path - assuming we cannot have SHA1 collisions on AppDomainAppId var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1(); var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoData", appDomainHash); - path = Path.Combine(cachePath, "umbraco-types"); + _fileBasePath = Path.Combine(cachePath, "umbraco-types"); break; case LocalTempStorage.Default: default: var tempFolder = IOHelper.MapPath("~/App_Data/TEMP/TypesCache"); - path = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName); + _fileBasePath = Path.Combine(tempFolder, "umbraco-types." + NetworkHelper.FileSafeMachineName); break; } - _fileBasePath = path; - _localTempStorage = localTempStorage; + // ensure that the folder exists + var directory = Path.GetDirectoryName(_fileBasePath); + if (directory == null) + throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\"."); + if (Directory.Exists(directory) == false) + Directory.CreateDirectory(directory); + + return _fileBasePath; } - - // ensure that the folder exists - var directory = Path.GetDirectoryName(_fileBasePath); - if (directory == null) - throw new InvalidOperationException($"Could not determine folder for path \"{_fileBasePath}\"."); - if (Directory.Exists(directory) == false) - Directory.CreateDirectory(directory); - - return _fileBasePath; } //private string GetFilePath(string extension) @@ -638,7 +635,7 @@ namespace Umbraco.Core.Composing var name = GetName(baseType, attributeType); - lock (_typesLock) + lock (_locko) using (_logger.TraceDuration( "Getting " + name, "Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index d08d431221..c677f16909 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -70,7 +70,8 @@ namespace Umbraco.Core.Runtime // type loader var globalSettings = UmbracoConfig.For.GlobalSettings(); - var typeLoader = new TypeLoader(runtimeCache, globalSettings, profilingLogger); + var localTempStorage = globalSettings.LocalTempStorageLocation; + var typeLoader = new TypeLoader(runtimeCache, localTempStorage, profilingLogger); // runtime state // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance' @@ -84,19 +85,11 @@ namespace Umbraco.Core.Runtime }; // create the composition - var composition = new Composition(register, typeLoader, profilingLogger, RuntimeLevel.Boot); - - composition.RegisterUnique(logger); - composition.RegisterUnique(profiler); - composition.RegisterUnique(profilingLogger); - composition.RegisterUnique(appCaches); - composition.RegisterUnique(runtimeCache); - composition.RegisterUnique(databaseFactory); - composition.RegisterUnique(_ => databaseFactory.SqlContext); - composition.RegisterUnique(typeLoader); - composition.RegisterUnique(_state); + var composition = new Composition(register, typeLoader, profilingLogger, _state); + composition.RegisterEssentials(logger, profiler, profilingLogger, appCaches, databaseFactory, typeLoader, _state); // register runtime-level services + // there should be none, really - this is here "just in case" Compose(composition); // the boot loader boots using a container scope, so anything that is PerScope will diff --git a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs index a70a3062b0..68b666632c 100644 --- a/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs +++ b/src/Umbraco.Tests/Cache/DistributedCache/DistributedCacheTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Sync; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Cache.DistributedCache { @@ -25,7 +26,7 @@ namespace Umbraco.Tests.Cache.DistributedCache { var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.RegisterUnique(_ => new TestServerRegistrar()); composition.RegisterUnique(_ => new TestServerMessenger()); diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index e68dae8a45..c2cc0c5038 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -20,7 +20,7 @@ namespace Umbraco.Tests.Components private static readonly List Composed = new List(); private static readonly List Initialized = new List(); - private static IFactory GetMockFactory(Action> setup = null) + private static IFactory MockFactory(Action> setup = null) { // fixme use IUmbracoDatabaseFactory vs UmbracoDatabaseFactory, clean it all up! @@ -40,21 +40,28 @@ namespace Umbraco.Tests.Components return mock.Object; } - private static IRegister GetMockRegister() + private static IRegister MockRegister() { return Mock.Of(); } - private static TypeLoader GetMockTypeLoader() + private static TypeLoader MockTypeLoader() { return new TypeLoader(); } + public static IRuntimeState MockRuntimeState(RuntimeLevel level) + { + var runtimeState = Mock.Of(); + Mock.Get(runtimeState).Setup(x => x.Level).Returns(level); + return runtimeState; + } + [Test] public void Boot1A() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -69,8 +76,8 @@ namespace Umbraco.Tests.Components [Test] public void Boot1B() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Run); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = TypeArray(); var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -85,8 +92,8 @@ namespace Umbraco.Tests.Components [Test] public void Boot2() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -100,8 +107,8 @@ namespace Umbraco.Tests.Components [Test] public void Boot3() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -117,8 +124,8 @@ namespace Umbraco.Tests.Components [Test] public void BrokenRequire() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -140,8 +147,8 @@ namespace Umbraco.Tests.Components [Test] public void BrokenRequired() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = TypeArray(); var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -157,12 +164,12 @@ namespace Umbraco.Tests.Components [Test] public void Initialize() { - var register = GetMockRegister(); - var factory = GetMockFactory(m => + var register = MockRegister(); + var factory = MockFactory(m => { m.Setup(x => x.TryGetInstance(It.Is(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource()); }); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Component1), typeof(Component5) }; var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -180,8 +187,8 @@ namespace Umbraco.Tests.Components [Test] public void Requires1() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Component6), typeof(Component7), typeof(Component8) }; var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -195,8 +202,8 @@ namespace Umbraco.Tests.Components [Test] public void Requires2A() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Component9), typeof(Component2), typeof(Component4) }; var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -211,9 +218,9 @@ namespace Umbraco.Tests.Components [Test] public void Requires2B() { - var register = GetMockRegister(); - var factory = GetMockFactory(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Run); + var register = MockRegister(); + var factory = MockFactory(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Run)); var types = new[] { typeof(Component9), typeof(Component2), typeof(Component4) }; var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -229,8 +236,8 @@ namespace Umbraco.Tests.Components [Test] public void WeakDependencies() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Component10) }; var components = new Core.Components.Components(composition, types, Mock.Of()); @@ -260,8 +267,8 @@ namespace Umbraco.Tests.Components [Test] public void DisableMissing() { - var register = GetMockRegister(); - var composition = new Composition(register, GetMockTypeLoader(), Mock.Of(), RuntimeLevel.Unknown); + var register = MockRegister(); + var composition = new Composition(register, MockTypeLoader(), Mock.Of(), MockRuntimeState(RuntimeLevel.Unknown)); var types = new[] { typeof(Component6), typeof(Component8) }; // 8 disables 7 which is not in the list var components = new Core.Components.Components(composition, types, Mock.Of()); diff --git a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs index 59230c356c..7b04c52150 100644 --- a/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs +++ b/src/Umbraco.Tests/Composing/CollectionBuildersTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Composing; using Umbraco.Core; using Umbraco.Core.Components; using Umbraco.Core.Logging; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Composing Current.Reset(); var register = RegisterFactory.Create(); - _composition = new Composition(register, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + _composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); } [TearDown] diff --git a/src/Umbraco.Tests/Composing/ComposingTestBase.cs b/src/Umbraco.Tests/Composing/ComposingTestBase.cs index 7380a5968a..48850afd97 100644 --- a/src/Umbraco.Tests/Composing/ComposingTestBase.cs +++ b/src/Umbraco.Tests/Composing/ComposingTestBase.cs @@ -4,6 +4,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Tests.TestHelpers; @@ -20,7 +21,7 @@ namespace Umbraco.Tests.Composing { ProfilingLogger = new ProfilingLogger(Mock.Of(), Mock.Of()); - TypeLoader = new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), ProfilingLogger, detectChanges: false) + TypeLoader = new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, ProfilingLogger, detectChanges: false) { AssembliesToScan = AssembliesToScan }; diff --git a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs index 656adedf52..b9edb96f1c 100644 --- a/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs +++ b/src/Umbraco.Tests/Composing/LazyCollectionBuilderTests.cs @@ -9,6 +9,7 @@ using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -40,7 +41,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesProducers() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add(() => new[] { typeof(TransientObject3), typeof(TransientObject2) }) @@ -91,7 +92,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderHandlesTypesAndProducers() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -117,7 +118,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderThrowsOnIllegalTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() @@ -141,7 +142,7 @@ namespace Umbraco.Tests.Composing public void LazyCollectionBuilderCanExcludeTypes() { var container = CreateRegister(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add() diff --git a/src/Umbraco.Tests/Composing/LightInjectValidation.cs b/src/Umbraco.Tests/Composing/LightInjectValidation.cs new file mode 100644 index 0000000000..ea603a29f2 --- /dev/null +++ b/src/Umbraco.Tests/Composing/LightInjectValidation.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LightInject; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.Reflection; +using ServiceMap = System.Collections.Generic.Dictionary>; + +/********************************************************************************* + The MIT License (MIT) + + Copyright (c) 2017 bernhard.richter@gmail.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +****************************************************************************** + LightInject.Validation version 1.0.1 + http://www.lightinject.net/ + http://twitter.com/bernhardrichter +******************************************************************************/ + +namespace Umbraco.Tests.Composing +{ + public static class LightInjectValidation + { + private static readonly ConcurrentDictionary LifeSpans = new ConcurrentDictionary(); + + private const string NotDisposeMessageServiceType = + @"The service {0} is being injected as a constructor argument into {1} implements IDisposable, " + + "but is registered without a lifetime (transient). LightInject will not be able to dispose the instance represented by {0}. " + + "If the intent was to manually control the instantiation and destruction, inject Func<{0}> instead. " + + "Otherwise register `{0}` with a lifetime (PerContainer, PerRequest or PerScope)."; + + private const string NotDisposeMessageImplementingType = + @"The service {0} represented by {1} is being injected as a constructor argument into {2} implements IDisposable, " + + "but is registered without a lifetime (transient). LightInject will not be able to dispose the instance represented by {0}. " + + "If the intent was to manually control the instantiation and destruction, inject Func<{0}> instead. " + + "Otherwise register `{0}` with a lifetime (PerContainer, PerRequest or PerScope)."; + + + private const string MissingDeferredDependency = + @"The injected '{0}' does not contain a registration for the underlying type '{1}'. " + + "Ensure that '{1}' is registered so that the service can be resolved by '{0}'"; + + /* + The service 'NameSpace.IBar' that is being injected into 'NameSpace.Foo' is registered with +with a 'Transient' lifetime while the 'NameSpace.Foo' is registered with the 'PerScope' lifetime. +Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or has a longer lifetime than the 'PerScope' lifetime. + */ + private const string CaptiveDependency = + @"The service '{0}' that is being injected into {1} is registered with " + + "a '{2}' lifetime while the {1} is registered with the '{3}' lifetime. " + + "Ensure that '{0}' is registered with a lifetime that is equal to or has a longer lifetime than the '{3}' lifetime. " + + "Alternatively ensure that `{1}` is registered with a lifetime that is equal to or " + + "has a shorter lifetime than `{2}` lifetime."; + + private const string MissingDependency = + "Class: 'NameSpace.Foo', Parameter 'NameSpace.IBar bar' -> The injected service NameSpace IBar is not registered." + ; + + + static LightInjectValidation() + { + LifeSpans.TryAdd(typeof(PerRequestLifeTime), 10); + LifeSpans.TryAdd(typeof(PerScopeLifetime), 20); + LifeSpans.TryAdd(typeof(PerContainerLifetime), 30); + } + + public static IEnumerable Validate(this ServiceContainer container) + { + var serviceMap = container.AvailableServices.GroupBy(sr => sr.ServiceType).ToDictionary(gr => gr.Key, + gr => gr.ToDictionary(sr => sr.ServiceName, sr => sr, StringComparer.OrdinalIgnoreCase)); + + var verifyableServices = container.AvailableServices.Where(sr => sr.ImplementingType != null); + + return verifyableServices.SelectMany(sr => + ValidateConstructor(serviceMap, sr, container.ConstructorSelector.Execute(sr.ImplementingType))); + } + + private static IReadOnlyCollection ValidateConstructor(ServiceMap serviceMap, + ServiceRegistration serviceRegistration, ConstructorInfo constructorInfo) + { + var result = new Collection(); + + foreach (var parameter in constructorInfo.GetParameters()) + { + var validationTarget = new ValidationTarget(serviceRegistration, parameter); + Validate(validationTarget, serviceMap, result); + } + return result; + } + + private static void Validate(ValidationTarget validationTarget, ServiceMap serviceMap, ICollection result) + { + var registration = GetServiceRegistration(serviceMap, validationTarget); + if (registration == null) + { + if (validationTarget.ServiceType.IsFunc() || validationTarget.ServiceType.IsLazy()) + { + var serviceType = validationTarget.ServiceType.GenericTypeArguments[0]; + var underlyingvalidationTarget = validationTarget.WithServiceDescription(serviceType, string.Empty); + registration = GetServiceRegistration(serviceMap, underlyingvalidationTarget); + + if (registration != null) + { + return; + } + + if (serviceMap.ContainsAmbiguousRegistrationFor(serviceType)) + { + result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, underlyingvalidationTarget)); + } + else + { + string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType); + result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget)); + } + } + else if (validationTarget.ServiceType.IsGenericType && validationTarget.ServiceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + var serviceType = validationTarget.ServiceType.GenericTypeArguments[0]; + var underlyingvalidationTarget = validationTarget.WithServiceDescription(serviceType, string.Empty); + registration = GetServiceRegistration(serviceMap, underlyingvalidationTarget); + if (registration != null) return; + + // strict: there has to be at least 1 + string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType); + result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget)); + } + else + { + if (serviceMap.ContainsAmbiguousRegistrationFor(validationTarget.ServiceType)) + { + result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, validationTarget)); + } + else + { + result.Add(new ValidationResult("", ValidationSeverity.MissingDependency, validationTarget)); + } + } + } + else + { + ValidateDisposable(validationTarget, result, registration); + ValidateLifetime(validationTarget, registration, result); + } + } + + private static void ValidateDisposable(ValidationTarget validationTarget, ICollection result, + ServiceRegistration registration) + { + if (registration.ServiceType.Implements()) + { + var message = string.Format(NotDisposeMessageServiceType, registration.ServiceType, + validationTarget.DeclaringService.ImplementingType); + result.Add(new ValidationResult(message, ValidationSeverity.NotDisposed, validationTarget)); + } + + else if (registration.ImplementingType != null && registration.ImplementingType.Implements()) + { + var message = string.Format(NotDisposeMessageImplementingType, registration.ImplementingType, + registration.ServiceType, + validationTarget.DeclaringService.ImplementingType); + result.Add(new ValidationResult(message, ValidationSeverity.NotDisposed, validationTarget)); + } + } + + + private static void ValidateLifetime(ValidationTarget validationTarget, ServiceRegistration dependencyRegistration, ICollection result) + { + if (GetLifespan(validationTarget.DeclaringService.Lifetime) > GetLifespan(dependencyRegistration.Lifetime)) + { + var message = string.Format(CaptiveDependency, dependencyRegistration.ServiceType, + validationTarget.DeclaringService.ServiceType, GetLifetimeName(dependencyRegistration.Lifetime), + GetLifetimeName(validationTarget.DeclaringService.Lifetime)); + result.Add(new ValidationResult(message, ValidationSeverity.Captive, validationTarget)); + } + } + + public static void SetLifespan(int lifeSpan) where TLifetime : ILifetime + { + LifeSpans.TryAdd(typeof(TLifetime), lifeSpan); + } + + private static ServiceRegistration GetServiceRegistration(ServiceMap serviceMap, ValidationTarget validationTarget) + { + + if (!serviceMap.TryGetValue(validationTarget.ServiceType, out var registrations)) + { + return null; + } + + if (registrations.TryGetValue(string.Empty, out var registration)) + { + return registration; + } + + if (registrations.Count == 1) + { + return registrations.Values.First(); + } + + if (registrations.TryGetValue(validationTarget.ServiceName, out registration)) + { + return registration; + } + + return null; + } + + + + private static string GetLifetimeName(ILifetime lifetime) + { + if (lifetime == null) + { + return "Transient"; + } + return lifetime.GetType().Name; + } + + + private static int GetLifespan(ILifetime lifetime) + { + if (lifetime == null) + { + return 0; + } + if (LifeSpans.TryGetValue(lifetime.GetType(), out var lifespan)) + { + return lifespan; + } + return 0; + } + + + + } + + + public class ValidationTarget + { + public ServiceRegistration DeclaringService { get; } + public ParameterInfo Parameter { get; } + public Type ServiceType { get; } + public string ServiceName { get; } + + + public ValidationTarget(ServiceRegistration declaringRegistration, ParameterInfo parameter) : this(declaringRegistration, parameter, parameter.ParameterType, string.Empty) + { + } + + + public ValidationTarget(ServiceRegistration declaringService, ParameterInfo parameter, Type serviceType, string serviceName) + { + DeclaringService = declaringService; + Parameter = parameter; + ServiceType = serviceType; + ServiceName = serviceName; + + + if (serviceType.GetTypeInfo().IsGenericType && serviceType.GetTypeInfo().ContainsGenericParameters) + { + ServiceType = serviceType.GetGenericTypeDefinition(); + } + + } + + public ValidationTarget WithServiceDescription(Type serviceType, string serviceName) + { + return new ValidationTarget(DeclaringService, Parameter, serviceType, serviceName); + } + + } + + + + + + public class ValidationResult + { + public ValidationResult(string message, ValidationSeverity severity, ValidationTarget validationTarget) + { + Message = message; + Severity = severity; + ValidationTarget = validationTarget; + } + + public string Message { get; } + + public ValidationSeverity Severity { get; } + public ValidationTarget ValidationTarget { get; } + } + + public enum ValidationSeverity + { + NoIssues, + Captive, + NotDisposed, + MissingDependency, + Ambiguous + } + + internal static class TypeExtensions + { + public static bool Implements(this Type type) + { + return type.GetTypeInfo().ImplementedInterfaces.Contains(typeof(TBaseType)); + } + + public static bool IsFunc(this Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Func<>); + } + + public static bool IsLazy(this Type type) + { + var typeInfo = type.GetTypeInfo(); + return typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Lazy<>); + } + } + + internal static class ServiceMapExtensions + { + public static bool ContainsAmbiguousRegistrationFor(this ServiceMap serviceMap, Type serviceType) + { + if (!serviceMap.TryGetValue(serviceType, out var registrations)) + { + return false; + } + return registrations.Count > 1; + } + } +} diff --git a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs index 180ecb7d42..d100713102 100644 --- a/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs +++ b/src/Umbraco.Tests/Composing/PackageActionCollectionTests.cs @@ -8,6 +8,7 @@ using Umbraco.Core.Components; using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core._Legacy.PackageActions; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Composing { @@ -19,7 +20,7 @@ namespace Umbraco.Tests.Composing { var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Add(() => TypeLoader.GetPackageActions()); diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index 07625db9bf..79f608c1b5 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -9,6 +9,7 @@ using umbraco; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -26,7 +27,7 @@ namespace Umbraco.Tests.Composing public void Initialize() { // this ensures it's reset - _typeLoader = new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), new ProfilingLogger(Mock.Of(), Mock.Of())); + _typeLoader = new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of())); foreach (var file in Directory.GetFiles(IOHelper.MapPath("~/App_Data/TEMP/TypesCache"))) File.Delete(file); diff --git a/src/Umbraco.Tests/CoreThings/UdiTests.cs b/src/Umbraco.Tests/CoreThings/UdiTests.cs index 3ee7f33b6e..2b4ace8810 100644 --- a/src/Umbraco.Tests/CoreThings/UdiTests.cs +++ b/src/Umbraco.Tests/CoreThings/UdiTests.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Deploy; using Umbraco.Core.Logging; using Umbraco.Core.Serialization; @@ -25,7 +26,7 @@ namespace Umbraco.Tests.CoreThings var container = new Mock(); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NullCacheProvider.Instance, globalSettings, new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); Current.Factory = container.Object; Udi.ResetUdiTypes(); diff --git a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs index 7571388d1b..7508395c64 100644 --- a/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs +++ b/src/Umbraco.Tests/FrontEnd/UmbracoHelperTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Tests.TestHelpers; using Umbraco.Core.Cache; using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Web; @@ -413,7 +414,7 @@ namespace Umbraco.Tests.FrontEnd .Setup(x => x.GetInstance(typeof(TypeLoader))) .Returns(new TypeLoader( NullCacheProvider.Instance, - globalSettings, + LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()) ) ); diff --git a/src/Umbraco.Tests/IO/FileSystemsTests.cs b/src/Umbraco.Tests/IO/FileSystemsTests.cs index d6ddac88d4..116c53f5e4 100644 --- a/src/Umbraco.Tests/IO/FileSystemsTests.cs +++ b/src/Umbraco.Tests/IO/FileSystemsTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.IO; using Umbraco.Core.IO.MediaPathSchemes; using Umbraco.Core.Logging; using Umbraco.Core.Services; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.IO @@ -31,7 +32,7 @@ namespace Umbraco.Tests.IO _register = RegisterFactory.Create(); - var composition = new Composition(_register, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(_register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.Register(_ => Mock.Of()); composition.Register(_ => Mock.Of()); diff --git a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs index e801721f6b..c55da764e2 100644 --- a/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs +++ b/src/Umbraco.Tests/PropertyEditors/ImageCropperTest.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; using Umbraco.Core.Services; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Web.Models; using Umbraco.Web; @@ -70,7 +71,7 @@ namespace Umbraco.Tests.PropertyEditors try { var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder(); diff --git a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs index e78ebb7eb1..ea5fbcaa06 100644 --- a/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/PropertyEditorValueEditorTests.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; using Umbraco.Core.Strings; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; namespace Umbraco.Tests.PropertyEditors @@ -24,7 +25,7 @@ namespace Umbraco.Tests.PropertyEditors Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); register.Register(_ => new DefaultShortStringHelper(new DefaultShortStringHelperConfig().WithDefault(SettingsForTests.GetDefaultUmbracoSettings()))); diff --git a/src/Umbraco.Tests/Published/ConvertersTests.cs b/src/Umbraco.Tests/Published/ConvertersTests.cs index b0be7b5eb6..eb0f1a9368 100644 --- a/src/Umbraco.Tests/Published/ConvertersTests.cs +++ b/src/Umbraco.Tests/Published/ConvertersTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; @@ -176,7 +177,7 @@ namespace Umbraco.Tests.Published Current.Reset(); var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.WithCollectionBuilder() .Append() diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs new file mode 100644 index 0000000000..4d3a315cd2 --- /dev/null +++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Components; +using Umbraco.Core.Composing; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence; +using Umbraco.Core.Runtime; +using Umbraco.Tests.Composing; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Runtimes +{ + [TestFixture] + public class StandaloneTests + { + [Test] + public void Test() + { + // this is almost what CoreRuntime does, without + // - managing MainDom + // - configuring for unhandled exceptions, assembly resolution, application root path + // - testing for database, and for upgrades (runtime level) + // - assigning the factory to Current.Factory + + // create the very basic and essential things we need + var logger = new ConsoleLogger(); + var profiler = Mock.Of(); + var profilingLogger = new ProfilingLogger(logger, profiler); + var appCaches = CacheHelper.Disabled; + var databaseFactory = Mock.Of(); + var typeLoader = new TypeLoader(appCaches.RuntimeCache, LocalTempStorage.Default, profilingLogger); + var runtimeState = Mock.Of(); + Mock.Get(runtimeState).Setup(x => x.Level).Returns(RuntimeLevel.Run); + + // create the register and the composition + var register = RegisterFactory.Create(); + var composition = new Composition(register, typeLoader, profilingLogger, runtimeState); + composition.RegisterEssentials(logger, profiler, profilingLogger, appCaches, databaseFactory, typeLoader, runtimeState); + + // create the core runtime and have it compose itself + var coreRuntime = new CoreRuntime(); + coreRuntime.Compose(composition); + + // fixme + // at that point, CoreRuntime also does + //composition.RegisterUnique(mainDom) + // we should make it + //composition.RegisterUnique(mainDom) + // because some components want to use it + // (and then, what would a standalone maindom be?) + + // get the components + // all of them? + var componentTypes = typeLoader.GetTypes(); + // filtered? + //var componentTypes = typeLoader.GetTypes() + // .Where(x => !x.FullName.StartsWith("Umbraco.Web")); + // single? + //var componentTypes = new[] { typeof(CoreRuntimeComponent) }; + var components = new Core.Components.Components(composition, componentTypes, profilingLogger); + + // get components to compose themselves + components.Compose(); + + // create the factory + var factory = composition.CreateFactory(); + + // at that point Umbraco is fully composed + // but nothing is initialized (no maindom, nothing - beware!) + // to actually *run* Umbraco standalone, better use a StandaloneRuntime + // that would inherit from CoreRuntime and ensure everything starts + + // get components to initialize themselves + //components.Initialize(factory); + + // and then, validate + var lightInjectContainer = (LightInject.ServiceContainer) factory.Concrete; + var results = lightInjectContainer.Validate().ToList(); + foreach (var resultGroup in results.GroupBy(x => x.Severity)) + foreach (var result in resultGroup) + { + Console.WriteLine(); + Console.WriteLine($"{result.Severity}: {WordWrap(result.Message, 120)}"); + var target = result.ValidationTarget; + Console.Write("\t"); + Console.Write(target.ServiceName); + Console.Write(target.DeclaringService.ServiceType); + if (!target.DeclaringService.ServiceName.IsNullOrWhiteSpace()) + { + Console.Write(" '"); + Console.Write(target.DeclaringService.ServiceName); + Console.Write("'"); + } + + Console.Write(" "); + if (target.DeclaringService.Lifetime == null) + Console.Write("?"); + else + Console.Write(target.DeclaringService.Lifetime.ToString().TrimStart("LightInject.")); + Console.WriteLine(); + Console.Write("\t"); + Console.Write(target.Parameter); + Console.WriteLine(); + } + Assert.AreEqual(0, results.Count); + } + + public static string WordWrap(string text, int width) + { + int pos, next; + var sb = new StringBuilder(); + var nl = Environment.NewLine; + + // Lucidity check + if (width < 1) + return text; + + // Parse each line of text + for (pos = 0; pos < text.Length; pos = next) + { + // Find end of line + var eol = text.IndexOf(nl, pos, StringComparison.Ordinal); + + if (eol == -1) + next = eol = text.Length; + else + next = eol + nl.Length; + + // Copy this line of text, breaking into smaller lines as needed + if (eol > pos) + { + do + { + var len = eol - pos; + + if (len > width) + len = BreakLine(text, pos, width); + + if (pos > 0) + sb.Append("\t\t"); + sb.Append(text, pos, len); + sb.Append(nl); + + // Trim whitespace following break + pos += len; + + while (pos < eol && char.IsWhiteSpace(text[pos])) + pos++; + + } while (eol > pos); + } + else sb.Append(nl); // Empty line + } + + return sb.ToString(); + } + + public static int BreakLine(string text, int pos, int max) + { + // Find last whitespace in line + var i = max - 1; + while (i >= 0 && !char.IsWhiteSpace(text[pos + i])) + i--; + if (i < 0) + return max; // No whitespace found; break at maximum length + // Find start of whitespace + while (i >= 0 && char.IsWhiteSpace(text[pos + i])) + i--; + // Return length of text before whitespace + return i + 1; + } + } +} diff --git a/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs similarity index 92% rename from src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs rename to src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs index b9f8f6576b..97688a7113 100644 --- a/src/Umbraco.Tests/Runtimes/WebRuntimeTests.cs +++ b/src/Umbraco.Tests/Runtimes/WebRuntimeComponentTests.cs @@ -2,14 +2,13 @@ using System.Web.Mvc; using NUnit.Framework; using Umbraco.Core.Profiling; -using Umbraco.Web; using Umbraco.Web.Mvc; using Umbraco.Web.Runtime; namespace Umbraco.Tests.Runtimes { [TestFixture] - public class WebRuntimeTests + public class WebRuntimeComponentTests { [Test] public void WrapViewEngines_HasEngines_WrapsAll() @@ -43,7 +42,6 @@ namespace Umbraco.Tests.Runtimes Assert.That(((ProfilingViewEngine)engines[1]).Inner, Is.InstanceOf()); } - [Test] public void WrapViewEngines_HasProfiledEngine_AddsSameInstance() { @@ -61,10 +59,7 @@ namespace Umbraco.Tests.Runtimes [Test] public void WrapViewEngines_CollectionIsNull_DoesNotThrow() { - IList engines = null; - Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(engines)); - Assert.That(engines, Is.Null); + Assert.DoesNotThrow(() => WebRuntimeComponent.WrapViewEngines(null)); } - } } diff --git a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs index 6a1a7272e7..0433603684 100644 --- a/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeEventDispatcherTests.cs @@ -16,6 +16,7 @@ using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.Mappers; using Umbraco.Core.Services; +using Umbraco.Tests.Components; namespace Umbraco.Tests.Scoping { @@ -34,7 +35,7 @@ namespace Umbraco.Tests.Scoping var register = RegisterFactory.Create(); - var composition = new Composition(register, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(register, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); _testObjects = new TestObjects(register); diff --git a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs index a88438efce..9ba8c6fb83 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseUsingSqlCeSyntax.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Persistence; +using Umbraco.Tests.Components; namespace Umbraco.Tests.TestHelpers { @@ -36,14 +37,14 @@ namespace Umbraco.Tests.TestHelpers var container = RegisterFactory.Create(); - var composition = new Composition(container, new TypeLoader(), Mock.Of(), RuntimeLevel.Run); + var composition = new Composition(container, new TypeLoader(), Mock.Of(), ComponentTests.MockRuntimeState(RuntimeLevel.Run)); composition.RegisterUnique(_ => Mock.Of()); composition.RegisterUnique(_ => Mock.Of()); var logger = new ProfilingLogger(Mock.Of(), Mock.Of()); var pluginManager = new TypeLoader(NullCacheProvider.Instance, - SettingsForTests.GenerateMockGlobalSettings(), + LocalTempStorage.Default, logger, false); composition.RegisterUnique(pluginManager); diff --git a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs index ff9318e63f..3f5240d04c 100644 --- a/src/Umbraco.Tests/Testing/UmbracoTestBase.cs +++ b/src/Umbraco.Tests/Testing/UmbracoTestBase.cs @@ -29,6 +29,7 @@ using Umbraco.Core.Scoping; using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Tests.Components; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Stubs; using Umbraco.Web; @@ -130,7 +131,7 @@ namespace Umbraco.Tests.Testing var register = RegisterFactory.Create(); - Composition = new Composition(register, typeLoader, proflogger, RuntimeLevel.Run); + Composition = new Composition(register, typeLoader, proflogger, ComponentTests.MockRuntimeState(RuntimeLevel.Run)); Composition.RegisterUnique(typeLoader); Composition.RegisterUnique(logger); @@ -267,7 +268,7 @@ namespace Umbraco.Tests.Testing // common to all tests = cannot be overriden private static TypeLoader CreateCommonTypeLoader(IRuntimeCacheProvider runtimeCache, IGlobalSettings globalSettings, IProfilingLogger logger) { - return new TypeLoader(runtimeCache, globalSettings, logger, false) + return new TypeLoader(runtimeCache, globalSettings.LocalTempStorageLocation, logger, false) { AssembliesToScan = new[] { diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index f73847fb3f..0b6b65f103 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -116,6 +116,7 @@ + @@ -133,6 +134,7 @@ + @@ -277,7 +279,7 @@ - + diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 613ae4f1c2..fc3224131f 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -39,7 +39,7 @@ namespace Umbraco.Tests.Web // fixme - bad in a unit test - but Udi has a static ctor that wants it?! var container = new Mock(); container.Setup(x => x.GetInstance(typeof(TypeLoader))).Returns( - new TypeLoader(NullCacheProvider.Instance, SettingsForTests.GenerateMockGlobalSettings(), new ProfilingLogger(Mock.Of(), Mock.Of()))); + new TypeLoader(NullCacheProvider.Instance, LocalTempStorage.Default, new ProfilingLogger(Mock.Of(), Mock.Of()))); container.Setup(x => x.GetInstance(typeof (ServiceContext))).Returns(serviceContext); Current.Factory = container.Object; diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 525a8abf77..a1081f8177 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -55,17 +55,6 @@ namespace Umbraco.Web.Runtime return factory; } - /// - public override void Compose(Composition composition) - { - base.Compose(composition); - - // some components may want to initialize with the UmbracoApplicationBase - // well, they should not - we should not do this - // TODO remove this eventually. - composition.RegisterUnique(_umbracoApplication); - } - #region Getters protected override IProfiler GetProfiler() => new WebProfiler(); diff --git a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs index da07096b75..35e51cca84 100644 --- a/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs +++ b/src/Umbraco.Web/Runtime/WebRuntimeComponent.cs @@ -275,7 +275,7 @@ namespace Umbraco.Web.Runtime { if (viewEngines == null || viewEngines.Count == 0) return; - var originalEngines = viewEngines.Select(e => e).ToArray(); + var originalEngines = viewEngines.ToList(); viewEngines.Clear(); foreach (var engine in originalEngines) { diff --git a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs index f8b2c6ef8b..decfb3c738 100644 --- a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs @@ -25,7 +25,7 @@ namespace Umbraco.Web.Search { //TODO: we should inject the different IValueSetValidator so devs can just register them instead of overriding this class? - public UmbracoIndexesCreator(ProfilingLogger profilingLogger, + public UmbracoIndexesCreator(IProfilingLogger profilingLogger, ILocalizationService languageService, IPublicAccessService publicAccessService, IMemberService memberService) @@ -36,7 +36,7 @@ namespace Umbraco.Web.Search MemberService = memberService ?? throw new System.ArgumentNullException(nameof(memberService)); } - protected ProfilingLogger ProfilingLogger { get; } + protected IProfilingLogger ProfilingLogger { get; } protected ILocalizationService LanguageService { get; } protected IPublicAccessService PublicAccessService { get; } protected IMemberService MemberService { get; } @@ -64,7 +64,7 @@ namespace Umbraco.Web.Search GetFileSystemLuceneDirectory(Constants.UmbracoIndexes.InternalIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, - LanguageService, + LanguageService, GetContentValueSetValidator()); return index; } @@ -91,7 +91,7 @@ namespace Umbraco.Web.Search UmbracoExamineIndex.UmbracoIndexFieldDefinitions, GetFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath), new CultureInvariantWhitespaceAnalyzer(), - ProfilingLogger, + ProfilingLogger, GetMemberValueSetValidator()); return index; } @@ -128,6 +128,6 @@ namespace Umbraco.Web.Search { return new MemberValueSetValidator(); } - + } } diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs index 650229a21b..d8d0d65b55 100644 --- a/src/Umbraco.Web/UmbracoApplicationBase.cs +++ b/src/Umbraco.Web/UmbracoApplicationBase.cs @@ -6,7 +6,6 @@ using System.Web.Hosting; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Logging; -using Umbraco.Core.Logging.Serilog; namespace Umbraco.Web {