using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Moq; using NUnit.Framework; using NzbDrone.Common; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.Messaging.Commands { [TestFixture] public class CommandExecutorFixture : TestBase { private CommandQueue _commandQueue; private Mock> _executorA; private Mock> _executorB; [SetUp] public void Setup() { _executorA = new Mock>(); _executorB = new Mock>(); Mocker.GetMock() .Setup(c => c.Build(typeof(IExecute))) .Returns(_executorA.Object); Mocker.GetMock() .Setup(c => c.Build(typeof(IExecute))) .Returns(_executorB.Object); } [TearDown] public void TearDown() { Subject.Handle(new ApplicationShutdownRequested()); // Give the threads a bit of time to shut down. Thread.Sleep(10); } private void GivenCommandQueue() { _commandQueue = new CommandQueue(); Mocker.GetMock() .Setup(s => s.Queue(It.IsAny())) .Returns(_commandQueue.GetConsumingEnumerable); } private void QueueAndWaitForExecution(CommandModel commandModel, bool waitPublish = false) { var waitEventComplete = new ManualResetEventSlim(); var waitEventPublish = new ManualResetEventSlim(); Mocker.GetMock() .Setup(s => s.Complete(It.Is(c => c == commandModel), It.IsAny())) .Callback(() => waitEventComplete.Set()); Mocker.GetMock() .Setup(s => s.Fail(It.Is(c => c == commandModel), It.IsAny(), It.IsAny())) .Callback(() => waitEventComplete.Set()); Mocker.GetMock() .Setup(s => s.PublishEvent(It.IsAny())) .Callback(() => waitEventPublish.Set()); _commandQueue.Add(commandModel); if (!waitEventComplete.Wait(2000)) { Assert.Fail("Command did not Complete/Fail within 2 sec"); } if (waitPublish && !waitEventPublish.Wait(500)) { Assert.Fail("Command did not Publish within 500 msec"); } } [Test] public void should_start_executor_threads() { Subject.Handle(new ApplicationStartedEvent()); Mocker.GetMock() .Verify(v => v.Queue(It.IsAny()), Times.AtLeastOnce()); } [Test] public void should_execute_on_executor() { GivenCommandQueue(); var commandA = new CommandA(); var commandModel = new CommandModel { Body = commandA }; Subject.Handle(new ApplicationStartedEvent()); QueueAndWaitForExecution(commandModel); _executorA.Verify(c => c.Execute(commandA), Times.Once()); } [Test] public void should_not_execute_on_incompatible_executor() { GivenCommandQueue(); var commandA = new CommandA(); var commandModel = new CommandModel { Body = commandA }; Subject.Handle(new ApplicationStartedEvent()); QueueAndWaitForExecution(commandModel); _executorA.Verify(c => c.Execute(commandA), Times.Once()); _executorB.Verify(c => c.Execute(It.IsAny()), Times.Never()); } [Test] public void broken_executor_should_publish_executed_event() { GivenCommandQueue(); var commandA = new CommandA(); var commandModel = new CommandModel { Body = commandA }; _executorA.Setup(s => s.Execute(It.IsAny())) .Throws(new NotImplementedException()); Subject.Handle(new ApplicationStartedEvent()); QueueAndWaitForExecution(commandModel); VerifyEventPublished(); ExceptionVerification.WaitForErrors(1, 500); } [Test] public void should_publish_executed_event_on_success() { GivenCommandQueue(); var commandA = new CommandA(); var commandModel = new CommandModel { Body = commandA }; Subject.Handle(new ApplicationStartedEvent()); QueueAndWaitForExecution(commandModel); VerifyEventPublished(); } [Test] public void should_use_completion_message() { GivenCommandQueue(); var commandA = new CommandA(); var commandModel = new CommandModel { Body = commandA }; Subject.Handle(new ApplicationStartedEvent()); QueueAndWaitForExecution(commandModel); Mocker.GetMock() .Verify(s => s.Complete(It.Is(c => c == commandModel), commandA.CompletionMessage), Times.Once()); } [Test] public void should_use_last_progress_message_if_completion_message_is_null() { GivenCommandQueue(); var commandB = new CommandB(); var commandModel = new CommandModel { Body = commandB, Message = "Do work" }; Subject.Handle(new ApplicationStartedEvent()); QueueAndWaitForExecution(commandModel); Mocker.GetMock() .Verify(s => s.Complete(It.Is(c => c == commandModel), commandModel.Message), Times.Once()); } } public class CommandA : Command { public CommandA(int id = 0) { } } public class CommandB : Command { public override string CompletionMessage => null; } }