Improve components, proper cache refresher ordering
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -14,6 +15,7 @@ namespace Umbraco.Core.Components
|
||||
{
|
||||
private readonly IServiceContainer _container;
|
||||
private readonly ProfilingLogger _proflog;
|
||||
private readonly ILogger _logger;
|
||||
private IUmbracoComponent[] _components;
|
||||
private bool _booted;
|
||||
|
||||
@@ -28,6 +30,7 @@ namespace Umbraco.Core.Components
|
||||
if (container == null) throw new ArgumentNullException(nameof(container));
|
||||
_container = container;
|
||||
_proflog = container.GetInstance<ProfilingLogger>();
|
||||
_logger = container.GetInstance<ILogger>();
|
||||
}
|
||||
|
||||
private class EnableInfo
|
||||
@@ -62,7 +65,7 @@ namespace Umbraco.Core.Components
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> PrepareComponentTypes2(IEnumerable<Type> componentTypes, RuntimeLevel level)
|
||||
private IEnumerable<Type> PrepareComponentTypes2(IEnumerable<Type> componentTypes, RuntimeLevel level)
|
||||
{
|
||||
// create a list, remove those that cannot be enabled due to runtime level
|
||||
var componentTypeList = componentTypes
|
||||
@@ -77,6 +80,84 @@ namespace Umbraco.Core.Components
|
||||
if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false)
|
||||
componentTypeList.Add(typeof(UmbracoCoreComponent));
|
||||
|
||||
// enable or disable components
|
||||
EnableDisableComponents(componentTypeList);
|
||||
|
||||
// sort the components according to their dependencies
|
||||
var requirements = new Dictionary<Type, List<Type>>();
|
||||
foreach (var type in componentTypeList) requirements[type] = null;
|
||||
foreach (var type in componentTypeList)
|
||||
{
|
||||
GatherRequirementsFromRequireAttribute(type, componentTypeList, requirements);
|
||||
GatherRequirementsFromRequiredAttribute(type, componentTypeList, requirements);
|
||||
}
|
||||
|
||||
// only for debugging, this is verbose
|
||||
//_logger.Debug<BootLoader>(GetComponentsReport(requirements));
|
||||
|
||||
// sort components
|
||||
var graph = new TopoGraph<Type, KeyValuePair<Type, List<Type>>>(kvp => kvp.Key, kvp => kvp.Value);
|
||||
graph.AddItems(requirements);
|
||||
List<Type> sortedComponentTypes;
|
||||
try
|
||||
{
|
||||
sortedComponentTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// in case of an error, force-dump everything to log
|
||||
_logger.Info<BootLoader>(GetComponentsReport(requirements));
|
||||
_logger.Error<BootLoader>("Failed to sort components.", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
// 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<BootLoader>(text);
|
||||
|
||||
return sortedComponentTypes;
|
||||
}
|
||||
|
||||
private static string GetComponentsReport(Dictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine("Components & Dependencies:");
|
||||
text.AppendLine();
|
||||
|
||||
foreach (var kvp in requirements)
|
||||
{
|
||||
var type = kvp.Key;
|
||||
|
||||
text.AppendLine(type.FullName);
|
||||
foreach (var attribute in type.GetCustomAttributes<RequireComponentAttribute>())
|
||||
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
|
||||
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
|
||||
: ""));
|
||||
foreach (var attribute in type.GetCustomAttributes<RequiredComponentAttribute>())
|
||||
text.AppendLine(" -< " + attribute.RequiringType);
|
||||
foreach (var i in type.GetInterfaces())
|
||||
{
|
||||
text.AppendLine(" : " + i.FullName);
|
||||
foreach (var attribute in i.GetCustomAttributes<RequireComponentAttribute>())
|
||||
text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
|
||||
? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
|
||||
: ""));
|
||||
foreach (var attribute in i.GetCustomAttributes<RequiredComponentAttribute>())
|
||||
text.AppendLine(" -< " + attribute.RequiringType);
|
||||
}
|
||||
if (kvp.Value != null)
|
||||
foreach (var t in kvp.Value)
|
||||
text.AppendLine(" = " + t);
|
||||
text.AppendLine();
|
||||
}
|
||||
text.AppendLine("/");
|
||||
text.AppendLine();
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
private static void EnableDisableComponents(ICollection<Type> types)
|
||||
{
|
||||
var enabled = new Dictionary<Type, EnableInfo>();
|
||||
|
||||
// process the enable/disable attributes
|
||||
@@ -87,7 +168,7 @@ namespace Umbraco.Core.Components
|
||||
// what happens in case of conflicting remote declarations is unspecified. more
|
||||
// precisely, the last declaration to be processed wins, but the order of the
|
||||
// declarations depends on the type finder and is unspecified.
|
||||
foreach (var componentType in componentTypeList)
|
||||
foreach (var componentType in types)
|
||||
{
|
||||
foreach (var attr in componentType.GetCustomAttributes<EnableComponentAttribute>())
|
||||
{
|
||||
@@ -116,63 +197,80 @@ namespace Umbraco.Core.Components
|
||||
|
||||
// remove components that end up being disabled
|
||||
foreach (var kvp in enabled.Where(x => x.Value.Enabled == false))
|
||||
componentTypeList.Remove(kvp.Key);
|
||||
types.Remove(kvp.Key);
|
||||
}
|
||||
|
||||
// sort the components according to their dependencies
|
||||
var items = new List<TopologicalSorter.DependencyField<Type>>();
|
||||
var temp = new List<Type>(); // reduce allocs
|
||||
foreach (var type in componentTypeList)
|
||||
private static void GatherRequirementsFromRequireAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
// get 'require' attributes
|
||||
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
|
||||
var requireAttributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<RequireComponentAttribute>()) // those marking interfaces
|
||||
.Concat(type.GetCustomAttributes<RequireComponentAttribute>()); // those marking the component
|
||||
|
||||
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
|
||||
foreach (var attr in requireAttributes)
|
||||
{
|
||||
temp.Clear();
|
||||
if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
|
||||
|
||||
//// for tests
|
||||
//Console.WriteLine("Components & Dependencies:");
|
||||
//Console.WriteLine(type.FullName);
|
||||
//foreach (var attribute in type.GetCustomAttributes<RequireComponentAttribute>())
|
||||
// Console.WriteLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue ? (attribute.Weak.Value ? " (weak)" : " (strong)") : ""));
|
||||
//foreach (var i in type.GetInterfaces())
|
||||
//{
|
||||
// Console.WriteLine(" " + i.FullName);
|
||||
// foreach (var attribute in i.GetCustomAttributes<RequireComponentAttribute>())
|
||||
// Console.WriteLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue ? (attribute.Weak.Value ? " (weak)" : " (strong)") : ""));
|
||||
//}
|
||||
//Console.WriteLine("/");
|
||||
//Console.WriteLine();
|
||||
|
||||
// get attributes
|
||||
// these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
|
||||
var attributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<RequireComponentAttribute>())
|
||||
.Concat(type.GetCustomAttributes<RequireComponentAttribute>());
|
||||
|
||||
// what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
|
||||
foreach (var attr in attributes)
|
||||
// requiring an interface = require any enabled component implementing that interface
|
||||
// unless strong, and then require at least one enabled component implementing that interface
|
||||
if (attr.RequiredType.IsInterface)
|
||||
{
|
||||
// requiring an interface = require any enabled component implementing that interface
|
||||
// unless strong, and then require at least one enabled component implementing that interface
|
||||
if (attr.RequiredType.IsInterface)
|
||||
var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
|
||||
if (implems.Count > 0)
|
||||
{
|
||||
var implems = componentTypeList.Where(x => attr.RequiredType.IsAssignableFrom(x)).ToList();
|
||||
if (implems.Count > 0)
|
||||
temp.AddRange(implems);
|
||||
else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
if (requirements[type] == null) requirements[type] = new List<Type>();
|
||||
requirements[type].AddRange(implems);
|
||||
}
|
||||
// requiring a class = require that the component is enabled
|
||||
// unless weak, and then requires it if it is enabled
|
||||
else
|
||||
else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
// requiring a class = require that the component is enabled
|
||||
// unless weak, and then requires it if it is enabled
|
||||
else
|
||||
{
|
||||
if (types.Contains(attr.RequiredType))
|
||||
{
|
||||
if (componentTypeList.Contains(attr.RequiredType))
|
||||
temp.Add(attr.RequiredType);
|
||||
else if (attr.Weak != true) // if not explicitely set to weak, is strong
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
if (requirements[type] == null) requirements[type] = new List<Type>();
|
||||
requirements[type].Add(attr.RequiredType);
|
||||
}
|
||||
else if (attr.Weak != true) // if not explicitely set to weak, is strong
|
||||
throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void GatherRequirementsFromRequiredAttribute(Type type, ICollection<Type> types, IDictionary<Type, List<Type>> requirements)
|
||||
{
|
||||
// get 'required' attributes
|
||||
// fixme explain
|
||||
var requiredAttributes = type
|
||||
.GetInterfaces().SelectMany(x => x.GetCustomAttributes<RequiredComponentAttribute>())
|
||||
.Concat(type.GetCustomAttributes<RequiredComponentAttribute>());
|
||||
|
||||
foreach (var attr in requiredAttributes)
|
||||
{
|
||||
if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
|
||||
|
||||
if (attr.RequiringType.IsInterface)
|
||||
{
|
||||
var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
|
||||
foreach (var implem in implems)
|
||||
{
|
||||
if (requirements[implem] == null) requirements[implem] = new List<Type>();
|
||||
requirements[implem].Add(type);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (types.Contains(attr.RequiringType))
|
||||
{
|
||||
if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List<Type>();
|
||||
requirements[attr.RequiringType].Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
var dependsOn = temp.Distinct().Select(x => x.FullName).ToArray();
|
||||
items.Add(new TopologicalSorter.DependencyField<Type>(type.FullName, dependsOn, new Lazy<Type>(() => type)));
|
||||
}
|
||||
return TopologicalSorter.GetSortedItems(items);
|
||||
}
|
||||
|
||||
private void InstanciateComponents(IEnumerable<Type> types)
|
||||
|
||||
41
src/Umbraco.Core/Components/RequiredComponentAttribute.cs
Normal file
41
src/Umbraco.Core/Components/RequiredComponentAttribute.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a component is required by another component.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// fixme
|
||||
/// <para>This attribute is *not* inherited. This means that a component class inheriting from
|
||||
/// another component class does *not* inherit its requirements. However, the bootloader checks
|
||||
/// the *interfaces* of every component for their requirements, so requirements declared on
|
||||
/// interfaces are inherited by every component class implementing the interface.</para>
|
||||
/// <para>When targetting a class, indicates a dependency on the component which must be enabled,
|
||||
/// unless the requirement has explicitely been declared as weak (and then, only if the component
|
||||
/// is enabled).</para>
|
||||
/// <para>When targetting an interface, indicates a dependency on enabled components implementing
|
||||
/// the interface. It could be no component at all, unless the requirement has explicitely been
|
||||
/// declared as strong (and at least one component must be enabled).</para>
|
||||
/// </remarks>
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
|
||||
public class RequiredComponentAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RequiredComponentAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="requiringType">The type of the required component.</param>
|
||||
public RequiredComponentAttribute(Type requiringType)
|
||||
{
|
||||
if (typeof(IUmbracoComponent).IsAssignableFrom(requiringType) == false)
|
||||
throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}.");
|
||||
RequiringType = requiringType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required type.
|
||||
/// </summary>
|
||||
public Type RequiringType { get; }
|
||||
}
|
||||
}
|
||||
116
src/Umbraco.Core/TopoGraph.cs
Normal file
116
src/Umbraco.Core/TopoGraph.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public class TopoGraph
|
||||
{
|
||||
internal const string CycleDependencyError = "Cyclic dependency.";
|
||||
internal const string MissingDependencyError = "Missing dependency.";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic DAG that can be topologically sorted.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the keys.</typeparam>
|
||||
/// <typeparam name="TItem">The type of the items.</typeparam>
|
||||
public class TopoGraph<TKey, TItem> : TopoGraph
|
||||
{
|
||||
private readonly Func<TItem, TKey> _getKey;
|
||||
private readonly Func<TItem, IEnumerable<TKey>> _getDependencies;
|
||||
private readonly Dictionary<TKey, TItem> _items = new Dictionary<TKey, TItem>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TopoGraph{TKey, TItem}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="getKey">A method that returns the key of an item.</param>
|
||||
/// <param name="getDependencies">A method that returns the dependency keys of an item.</param>
|
||||
public TopoGraph(Func<TItem, TKey> getKey, Func<TItem, IEnumerable<TKey>> getDependencies)
|
||||
{
|
||||
_getKey = getKey;
|
||||
_getDependencies = getDependencies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the graph.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
public void AddItem(TItem item)
|
||||
{
|
||||
var key = _getKey(item);
|
||||
_items[key] = item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds items to the graph.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void AddItems(IEnumerable<TItem> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
AddItem(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sorted items.
|
||||
/// </summary>
|
||||
/// <param name="throwOnCycle">A value indicating whether to throw on cycles, or just ignore the branch.</param>
|
||||
/// <param name="throwOnMissing">A value indicating whether to throw on missing dependency, or just ignore the dependency.</param>
|
||||
/// <param name="reverse">A value indicating whether to reverse the order.</param>
|
||||
/// <returns>The (topologically) sorted items.</returns>
|
||||
public IEnumerable<TItem> GetSortedItems(bool throwOnCycle = true, bool throwOnMissing = true, bool reverse = false)
|
||||
{
|
||||
var sorted = new TItem[_items.Count];
|
||||
var visited = new HashSet<TItem>();
|
||||
var index = reverse ? _items.Count - 1 : 0;
|
||||
var incr = reverse ? -1 : +1;
|
||||
|
||||
foreach (var item in _items.Values)
|
||||
Visit(item, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing);
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static bool Contains(TItem[] items, TItem item, int start, int count)
|
||||
{
|
||||
return Array.IndexOf(items, item, start, count) >= 0;
|
||||
}
|
||||
|
||||
private void Visit(TItem item, ISet<TItem> visited, TItem[] sorted, ref int index, int incr, bool throwOnCycle, bool throwOnMissing)
|
||||
{
|
||||
if (visited.Contains(item))
|
||||
{
|
||||
// visited but not sorted yet = cycle
|
||||
var start = incr > 0 ? 0 : index;
|
||||
var count = incr > 0 ? index : sorted.Length - index;
|
||||
if (throwOnCycle && Contains(sorted, item, start, count) == false)
|
||||
throw new Exception(CycleDependencyError);
|
||||
return;
|
||||
}
|
||||
|
||||
visited.Add(item);
|
||||
|
||||
var keys = _getDependencies(item);
|
||||
var dependencies = keys == null ? null : FindDependencies(keys, throwOnMissing);
|
||||
|
||||
if (dependencies != null)
|
||||
foreach (var dep in dependencies)
|
||||
Visit(dep, visited, sorted, ref index, incr, throwOnCycle, throwOnMissing);
|
||||
|
||||
sorted[index] = item;
|
||||
index += incr;
|
||||
}
|
||||
|
||||
private IEnumerable<TItem> FindDependencies(IEnumerable<TKey> keys, bool throwOnMissing)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
TItem value;
|
||||
if (_items.TryGetValue(key, out value))
|
||||
yield return value;
|
||||
else if (throwOnMissing)
|
||||
throw new Exception(MissingDependencyError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,10 @@ namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Topological Sort algorithm for sorting items based on dependencies.
|
||||
/// Use the static method TopologicalSorter.GetSortedItems for a convenient
|
||||
/// Use the static method TopologicalSorter.GetSortedItems for a convenient
|
||||
/// way of sorting a list of items with dependencies between them.
|
||||
/// </summary>
|
||||
[Obsolete("Use the TopoGraph class instead, perfs are way better.")] // fixme remove!
|
||||
public class TopologicalSorter
|
||||
{
|
||||
private readonly int[] _vertices; // list of vertices
|
||||
@@ -45,7 +46,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
// get a vertex with no successors, or -1
|
||||
var currentVertex = NoSuccessors();
|
||||
if (currentVertex == -1) // must be a cycle
|
||||
if (currentVertex == -1) // must be a cycle
|
||||
throw new Exception("Graph has cycles");
|
||||
|
||||
// insert vertex label in sorted array (start at end)
|
||||
@@ -113,7 +114,7 @@ namespace Umbraco.Core
|
||||
|
||||
#region Static methods
|
||||
|
||||
public static IEnumerable<T> GetSortedItems<T>(List<DependencyField<T>> fields) where T : class
|
||||
public static IEnumerable<T> GetSortedItems<T>(List<DependencyField<T>> fields) where T : class
|
||||
{
|
||||
var sortOrder = GetTopologicalSortOrder(fields);
|
||||
var list = new List<T>();
|
||||
@@ -126,7 +127,7 @@ namespace Umbraco.Core
|
||||
return list;
|
||||
}
|
||||
|
||||
internal static int[] GetTopologicalSortOrder<T>(List<DependencyField<T>> fields) where T : class
|
||||
internal static int[] GetTopologicalSortOrder<T>(List<DependencyField<T>> fields) where T : class
|
||||
{
|
||||
var g = new TopologicalSorter(fields.Count);
|
||||
var indexes = new Dictionary<string, int>();
|
||||
@@ -158,7 +159,7 @@ namespace Umbraco.Core
|
||||
|
||||
#endregion
|
||||
|
||||
public class DependencyField<T> where T : class
|
||||
public class DependencyField<T> where T : class
|
||||
{
|
||||
public DependencyField()
|
||||
{
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
<Compile Include="Components\IUmbracoCoreComponent.cs" />
|
||||
<Compile Include="Components\IUmbracoUserComponent.cs" />
|
||||
<Compile Include="Components\RequireComponentAttribute.cs" />
|
||||
<Compile Include="Components\RequiredComponentAttribute.cs" />
|
||||
<Compile Include="Components\RuntimeLevelAttribute.cs" />
|
||||
<Compile Include="Components\UmbracoComponentBase.cs" />
|
||||
<Compile Include="Components\UmbracoCoreComponent.cs" />
|
||||
@@ -1169,6 +1170,7 @@
|
||||
<Compile Include="Strings\DefaultShortStringHelperConfig.cs" />
|
||||
<Compile Include="Strings\UrlSegmentProviderCollection.cs" />
|
||||
<Compile Include="Strings\UrlSegmentProviderCollectionBuilder.cs" />
|
||||
<Compile Include="TopoGraph.cs" />
|
||||
<Compile Include="Xml\XPath\RenamedRootNavigator.cs" />
|
||||
<Compile Include="_Legacy\PackageActions\IPackageAction.cs" />
|
||||
<Compile Include="_Legacy\PackageActions\PackageActionCollection.cs" />
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Umbraco.Tests.Components
|
||||
var f = new UmbracoDatabaseFactory(s, logger, new TestDatabaseScopeAccessor(), new MapperCollection(Enumerable.Empty<BaseMapper>()));
|
||||
|
||||
var mock = new Mock<IServiceContainer>();
|
||||
mock.Setup(x => x.GetInstance<ILogger>()).Returns(logger);
|
||||
mock.Setup(x => x.GetInstance<ProfilingLogger>()).Returns(new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
|
||||
mock.Setup(x => x.GetInstance<DatabaseContext>()).Returns(new DatabaseContext(f));
|
||||
setup?.Invoke(mock);
|
||||
@@ -37,22 +38,49 @@ namespace Umbraco.Tests.Components
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Boot()
|
||||
public void Boot1()
|
||||
{
|
||||
var container = MockContainer();
|
||||
|
||||
var thing = new BootLoader(container);
|
||||
var loader = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
thing.Boot(new [] { typeof (Component1), typeof (Component2), typeof (Component3), typeof (Component4) }, RuntimeLevel.Unknown);
|
||||
Assert.AreEqual(4, Composed.Count);
|
||||
Assert.AreEqual(typeof(Component1), Composed[0]);
|
||||
Assert.AreEqual(typeof(Component4), Composed[1]);
|
||||
Assert.AreEqual(typeof(Component2), Composed[2]);
|
||||
Assert.AreEqual(typeof(Component3), Composed[3]);
|
||||
// 2 is Core and requires 4
|
||||
// 3 is User
|
||||
// => reorder components accordingly
|
||||
loader.Boot(TypeArray<Component1, Component2, Component3, Component4>(), RuntimeLevel.Unknown);
|
||||
AssertTypeArray(TypeArray<Component1, Component4, Component2, Component3>(), Composed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BrokenDependency()
|
||||
public void Boot2()
|
||||
{
|
||||
var container = MockContainer();
|
||||
|
||||
var loader = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
// 21 is required by 20
|
||||
// => reorder components accordingly
|
||||
loader.Boot(TypeArray<Component20, Component21>(), RuntimeLevel.Unknown);
|
||||
AssertTypeArray(TypeArray<Component21, Component20>(), Composed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Boot3()
|
||||
{
|
||||
var container = MockContainer();
|
||||
|
||||
var loader = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
// i23 requires 22
|
||||
// 24, 25 implement i23
|
||||
// 25 required by i23
|
||||
// => reorder components accordingly
|
||||
loader.Boot(TypeArray<Component22, Component24, Component25>(), RuntimeLevel.Unknown);
|
||||
AssertTypeArray(TypeArray<Component22, Component25, Component24>(), Composed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BrokenRequire()
|
||||
{
|
||||
var container = MockContainer();
|
||||
|
||||
@@ -60,7 +88,10 @@ namespace Umbraco.Tests.Components
|
||||
Composed.Clear();
|
||||
try
|
||||
{
|
||||
thing.Boot(new[] { typeof(Component1), typeof(Component2), typeof(Component3) }, RuntimeLevel.Unknown);
|
||||
// 2 is Core and requires 4
|
||||
// 4 is missing
|
||||
// => throw
|
||||
thing.Boot(TypeArray < Component1, Component2, Component3>(), RuntimeLevel.Unknown);
|
||||
Assert.Fail("Expected exception.");
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -69,6 +100,21 @@ namespace Umbraco.Tests.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BrokenRequired()
|
||||
{
|
||||
var container = MockContainer();
|
||||
|
||||
var thing = new BootLoader(container);
|
||||
Composed.Clear();
|
||||
// 2 is Core and requires 4
|
||||
// 13 is required by 1
|
||||
// 1 is missing
|
||||
// => reorder components accordingly
|
||||
thing.Boot(TypeArray<Component2, Component4, Component13>(), RuntimeLevel.Unknown);
|
||||
AssertTypeArray(TypeArray<Component4, Component2, Component13>(), Composed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Initialize()
|
||||
{
|
||||
@@ -153,6 +199,8 @@ namespace Umbraco.Tests.Components
|
||||
Assert.AreEqual(typeof(Component8), Composed[1]);
|
||||
}
|
||||
|
||||
#region Components
|
||||
|
||||
public class TestComponentBase : UmbracoComponentBase
|
||||
{
|
||||
public override void Compose(Composition composition)
|
||||
@@ -213,8 +261,90 @@ namespace Umbraco.Tests.Components
|
||||
public class Component12 : TestComponentBase, IUmbracoCoreComponent
|
||||
{ }
|
||||
|
||||
[RequiredComponent(typeof(Component1))]
|
||||
public class Component13 : TestComponentBase
|
||||
{ }
|
||||
|
||||
public interface ISomeResource { }
|
||||
|
||||
public class SomeResource : ISomeResource { }
|
||||
|
||||
public class Component20 : TestComponentBase
|
||||
{ }
|
||||
|
||||
[RequiredComponent(typeof(Component20))]
|
||||
public class Component21 : TestComponentBase
|
||||
{ }
|
||||
|
||||
public class Component22 : TestComponentBase
|
||||
{ }
|
||||
|
||||
[RequireComponent(typeof(Component22))]
|
||||
public interface IComponent23 : IUmbracoComponent
|
||||
{ }
|
||||
|
||||
public class Component24 : TestComponentBase, IComponent23
|
||||
{ }
|
||||
|
||||
// should insert itself between 22 and anything i23
|
||||
[RequiredComponent(typeof(IComponent23))]
|
||||
//[RequireComponent(typeof(Component22))] - not needed, implement i23
|
||||
public class Component25 : TestComponentBase, IComponent23
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
|
||||
#region TypeArray
|
||||
|
||||
// fixme - move to Testing
|
||||
|
||||
private static Type[] TypeArray<T1>()
|
||||
{
|
||||
return new[] { typeof(T1) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2, T3>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2), typeof(T3) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2, T3, T4>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2, T3, T4, T5>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2, T3, T4, T5, T6>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2, T3, T4, T5, T6, T7>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7) };
|
||||
}
|
||||
|
||||
private static Type[] TypeArray<T1, T2, T3, T4, T5, T6, T7, T8>()
|
||||
{
|
||||
return new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8) };
|
||||
}
|
||||
|
||||
private static void AssertTypeArray(IReadOnlyList<Type> expected, IReadOnlyList<Type> test)
|
||||
{
|
||||
Assert.AreEqual(expected.Count, test.Count);
|
||||
for (var i = 0; i < expected.Count; i++)
|
||||
Assert.AreEqual(expected[i], test[i]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using Umbraco.Core.Components;
|
||||
using Umbraco.Core.DI;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using UmbracoExamine;
|
||||
@@ -18,6 +17,7 @@ using UmbracoExamine;
|
||||
namespace Umbraco.Tests.Runtimes
|
||||
{
|
||||
[TestFixture]
|
||||
[Ignore("cannot work until we refactor IDatabaseFactory vs UmbracoDatabaseFactory")]
|
||||
public class CoreRuntimeTests
|
||||
{
|
||||
[SetUp]
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Web.Cache
|
||||
/// Installs listeners on service events in order to refresh our caches.
|
||||
/// </summary>
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
[RequiredComponent(typeof(IUmbracoCoreComponent))] // runs before every other IUmbracoCoreComponent!
|
||||
public class CacheRefresherComponent : UmbracoComponentBase, IUmbracoCoreComponent
|
||||
{
|
||||
public void Initialize()
|
||||
|
||||
Reference in New Issue
Block a user