Improve components, proper cache refresher ordering

This commit is contained in:
Stephan
2016-12-15 13:15:49 +01:00
parent 37ca66f42e
commit 1276415ea4
8 changed files with 454 additions and 65 deletions

View File

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

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

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

View File

@@ -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()
{

View File

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

View File

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

View File

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

View File

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