simplified EventAggregator
This commit is contained in:
parent
b3c6db5997
commit
66972e5bc6
|
@ -0,0 +1,58 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Eventing;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Test.EventingTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ServiceNameFixture : TestBase
|
||||||
|
{
|
||||||
|
private EventAggregator _aggregator;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_aggregator = new EventAggregator(TestLogger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_publish_event_to_handlers()
|
||||||
|
{
|
||||||
|
var intHandler = new Mock<IHandle<int>>();
|
||||||
|
_aggregator = new EventAggregator(TestLogger, new List<IHandle> { intHandler.Object });
|
||||||
|
_aggregator.Publish(12);
|
||||||
|
|
||||||
|
intHandler.Verify(c => c.Handle(12), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_publish_to_more_than_one_handler()
|
||||||
|
{
|
||||||
|
var intHandler1 =new Mock<IHandle<int>>();
|
||||||
|
var intHandler2 = new Mock<IHandle<int>>();
|
||||||
|
_aggregator = new EventAggregator(TestLogger, new List<IHandle> { intHandler1.Object, intHandler2.Object });
|
||||||
|
_aggregator.Publish(12);
|
||||||
|
|
||||||
|
intHandler1.Verify(c => c.Handle(12), Times.Once());
|
||||||
|
intHandler2.Verify(c => c.Handle(12), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_not_publish_to_incompatible_handlers()
|
||||||
|
{
|
||||||
|
var intHandler = new Mock<IHandle<int>>();
|
||||||
|
var stringHandler = new Mock<IHandle<string>>();
|
||||||
|
_aggregator = new EventAggregator(TestLogger, new List<IHandle> { intHandler.Object, stringHandler.Object });
|
||||||
|
|
||||||
|
_aggregator.Publish(12);
|
||||||
|
|
||||||
|
intHandler.Verify(c => c.Handle(12), Times.Once());
|
||||||
|
stringHandler.Verify(c => c.Handle(It.IsAny<string>()), Times.Never());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -86,6 +86,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ConfigFileProviderTest.cs" />
|
<Compile Include="ConfigFileProviderTest.cs" />
|
||||||
|
<Compile Include="EventingTests\EventAggregatorTests.cs" />
|
||||||
<Compile Include="ReflectionExtensions.cs" />
|
<Compile Include="ReflectionExtensions.cs" />
|
||||||
<Compile Include="ReportingService_ReportParseError_Fixture.cs" />
|
<Compile Include="ReportingService_ReportParseError_Fixture.cs" />
|
||||||
<Compile Include="PathExtentionFixture.cs" />
|
<Compile Include="PathExtentionFixture.cs" />
|
||||||
|
|
|
@ -1,173 +1,28 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using NLog;
|
||||||
|
|
||||||
//From http://caliburnmicro.codeplex.com/
|
|
||||||
|
|
||||||
namespace NzbDrone.Common.Eventing
|
namespace NzbDrone.Common.Eventing
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Enables loosely-coupled publication of and subscription to events.
|
|
||||||
/// </summary>
|
|
||||||
public class EventAggregator : IEventAggregator
|
public class EventAggregator : IEventAggregator
|
||||||
{
|
{
|
||||||
readonly List<Handler> handlers = new List<Handler>();
|
private readonly Logger _logger;
|
||||||
|
private readonly IEnumerable<IHandle> _handlers;
|
||||||
|
|
||||||
/// <summary>
|
public EventAggregator(Logger logger, IEnumerable<IHandle> handlers)
|
||||||
/// The default thread marshaller used for publication;
|
|
||||||
/// </summary>
|
|
||||||
public static Action<Action> DefaultPublicationThreadMarshaller = action => action();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Processing of handler results on publication thread.
|
|
||||||
/// </summary>
|
|
||||||
public static Action<object, object> HandlerResultProcessing = (target, result) => { };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref = "EventAggregator" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public EventAggregator()
|
|
||||||
{
|
{
|
||||||
PublicationThreadMarshaller = DefaultPublicationThreadMarshaller;
|
_logger = logger;
|
||||||
|
_handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void Publish<TEvent>(TEvent message)
|
||||||
/// Gets or sets the default publication thread marshaller.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// The default publication thread marshaller.
|
|
||||||
/// </value>
|
|
||||||
public Action<System.Action> PublicationThreadMarshaller { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "instance">The instance to subscribe for event publication.</param>
|
|
||||||
public virtual void Subscribe(object instance)
|
|
||||||
{
|
{
|
||||||
lock (handlers)
|
_logger.Trace("Publishing {0}", message.GetType().Name);
|
||||||
|
|
||||||
|
foreach (var handler in _handlers.OfType<IHandle<TEvent>>())
|
||||||
{
|
{
|
||||||
if (handlers.Any(x => x.Matches(instance)))
|
_logger.Trace("{0} => {1}", message.GetType().Name, handler.GetType().Name);
|
||||||
{
|
handler.Handle(message);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers.Add(new Handler(instance));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unsubscribes the instance from all events.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "instance">The instance to unsubscribe.</param>
|
|
||||||
public virtual void Unsubscribe(object instance)
|
|
||||||
{
|
|
||||||
lock (handlers)
|
|
||||||
{
|
|
||||||
var found = handlers.FirstOrDefault(x => x.Matches(instance));
|
|
||||||
|
|
||||||
if (found != null)
|
|
||||||
{
|
|
||||||
handlers.Remove(found);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Publishes a message.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "message">The message instance.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Does not marshall the the publication to any special thread by default.
|
|
||||||
/// </remarks>
|
|
||||||
public virtual void Publish(object message)
|
|
||||||
{
|
|
||||||
Publish(message, PublicationThreadMarshaller);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Publishes a message.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "message">The message instance.</param>
|
|
||||||
/// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
|
|
||||||
public virtual void Publish(object message, Action<System.Action> marshal)
|
|
||||||
{
|
|
||||||
Handler[] toNotify;
|
|
||||||
lock (handlers)
|
|
||||||
{
|
|
||||||
toNotify = handlers.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
marshal(() =>
|
|
||||||
{
|
|
||||||
var messageType = message.GetType();
|
|
||||||
|
|
||||||
var dead = toNotify
|
|
||||||
.Where(handler => !handler.Handle(messageType, message))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (dead.Any())
|
|
||||||
{
|
|
||||||
lock (handlers)
|
|
||||||
{
|
|
||||||
foreach (var item in dead)
|
|
||||||
{
|
|
||||||
handlers.Remove(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Handler
|
|
||||||
{
|
|
||||||
readonly WeakReference reference;
|
|
||||||
readonly Dictionary<Type, MethodInfo> supportedHandlers = new Dictionary<Type, MethodInfo>();
|
|
||||||
|
|
||||||
public Handler(object handler)
|
|
||||||
{
|
|
||||||
reference = new WeakReference(handler);
|
|
||||||
|
|
||||||
var interfaces = handler.GetType().GetInterfaces()
|
|
||||||
.Where(x => typeof(IHandle).IsAssignableFrom(x) && x.IsGenericType);
|
|
||||||
|
|
||||||
foreach (var @interface in interfaces)
|
|
||||||
{
|
|
||||||
var type = @interface.GetGenericArguments()[0];
|
|
||||||
var method = @interface.GetMethod("Handle");
|
|
||||||
supportedHandlers[type] = method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Matches(object instance)
|
|
||||||
{
|
|
||||||
return reference.Target == instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Handle(Type messageType, object message)
|
|
||||||
{
|
|
||||||
var target = reference.Target;
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pair in supportedHandlers)
|
|
||||||
{
|
|
||||||
if (pair.Key.IsAssignableFrom(messageType))
|
|
||||||
{
|
|
||||||
var result = pair.Value.Invoke(target, new[] { message });
|
|
||||||
if (result != null)
|
|
||||||
{
|
|
||||||
HandlerResultProcessing(target, result);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,44 +4,10 @@ using System.Linq;
|
||||||
namespace NzbDrone.Common.Eventing
|
namespace NzbDrone.Common.Eventing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enables loosely-coupled publication of and subscription to events.
|
/// Enables loosely-coupled publication of events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IEventAggregator
|
public interface IEventAggregator
|
||||||
{
|
{
|
||||||
/// <summary>
|
void Publish<TEvent>(TEvent message);
|
||||||
/// Gets or sets the default publication thread marshaller.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>
|
|
||||||
/// The default publication thread marshaller.
|
|
||||||
/// </value>
|
|
||||||
Action<Action> PublicationThreadMarshaller { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "instance">The instance to subscribe for event publication.</param>
|
|
||||||
void Subscribe(object instance);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unsubscribes the instance from all events.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "instance">The instance to unsubscribe.</param>
|
|
||||||
void Unsubscribe(object instance);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Publishes a message.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "message">The message instance.</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// Uses the default thread marshaller during publication.
|
|
||||||
/// </remarks>
|
|
||||||
void Publish(object message);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Publishes a message.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name = "message">The message instance.</param>
|
|
||||||
/// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
|
|
||||||
void Publish(object message, Action<System.Action> marshal);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NLog;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Instrumentation;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.InstrumentationTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class DatabaseTargetFixture : ObjectDbTest<DatabaseTarget, Log>
|
||||||
|
{
|
||||||
|
string _loggerName;
|
||||||
|
|
||||||
|
private static string _uniqueMessage;
|
||||||
|
Logger _logger;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
Mocker.Resolve<ILogRepository, LogRepository>();
|
||||||
|
Mocker.Resolve<DatabaseTarget>().Register();
|
||||||
|
|
||||||
|
_logger = LogManager.GetCurrentClassLogger();
|
||||||
|
_loggerName = _logger.Name.Replace("NzbDrone.","");
|
||||||
|
|
||||||
|
_uniqueMessage = "Unique message: " + Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void write_log()
|
||||||
|
{
|
||||||
|
_logger.Info(_uniqueMessage);
|
||||||
|
|
||||||
|
StoredModel.Message.Should().Be(_uniqueMessage);
|
||||||
|
VerifyLog(StoredModel, LogLevel.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void write_long_log()
|
||||||
|
{
|
||||||
|
var message = String.Empty;
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
message += Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Info(message);
|
||||||
|
|
||||||
|
StoredModel.Message.Should().HaveLength(message.Length);
|
||||||
|
StoredModel.Message.Should().Be(message);
|
||||||
|
VerifyLog(StoredModel, LogLevel.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void write_log_exception()
|
||||||
|
{
|
||||||
|
var ex = new InvalidOperationException("Fake Exception");
|
||||||
|
|
||||||
|
_logger.ErrorException(_uniqueMessage, ex);
|
||||||
|
|
||||||
|
|
||||||
|
VerifyLog(StoredModel, LogLevel.Error);
|
||||||
|
StoredModel.Message.Should().Be(_uniqueMessage + ": " + ex.Message);
|
||||||
|
StoredModel.ExceptionType.Should().Be(ex.GetType().ToString());
|
||||||
|
StoredModel.Exception.Should().Be(ex.ToString());
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void exception_log_with_no_message_should_use_exceptions_message()
|
||||||
|
{
|
||||||
|
|
||||||
|
var ex = new InvalidOperationException("Fake Exception");
|
||||||
|
_uniqueMessage = String.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
_logger.ErrorException(_uniqueMessage, ex);
|
||||||
|
|
||||||
|
StoredModel.Message.Should().Be(ex.Message);
|
||||||
|
|
||||||
|
VerifyLog(StoredModel, LogLevel.Error);
|
||||||
|
|
||||||
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void null_string_as_arg_should_not_fail()
|
||||||
|
{
|
||||||
|
var epFile = new EpisodeFile();
|
||||||
|
_logger.Trace("File {0} no longer exists on disk. removing from database.", epFile.Path);
|
||||||
|
|
||||||
|
epFile.Path.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyLog(Log logItem, LogLevel level)
|
||||||
|
{
|
||||||
|
logItem.Time.Should().BeWithin(TimeSpan.FromSeconds(2));
|
||||||
|
logItem.Logger.Should().Be(_loggerName);
|
||||||
|
logItem.Level.Should().Be(level.Name);
|
||||||
|
logItem.Method.Should().Be(new StackTrace().GetFrame(1).GetMethod().Name);
|
||||||
|
_logger.Name.Should().EndWith(logItem.Logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,9 @@ namespace NzbDrone.Test.Common
|
||||||
{
|
{
|
||||||
public abstract class LoggingTest
|
public abstract class LoggingTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected Logger TestLogger = LogManager.GetLogger("TestLogger");
|
||||||
|
|
||||||
protected static void InitLogging()
|
protected static void InitLogging()
|
||||||
{
|
{
|
||||||
if (LogManager.Configuration == null || LogManager.Configuration is XmlLoggingConfiguration)
|
if (LogManager.Configuration == null || LogManager.Configuration is XmlLoggingConfiguration)
|
||||||
|
|
Loading…
Reference in New Issue