using System; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Shared.GameObjects { [TestFixture, Parallelizable, TestOf(typeof(EntityEventBus))] internal sealed partial class EntityEventBusTests { private static (EntityEventBus Bus, ISimulation Sim, EntityUid Uid, MetaDataComponent Comp) EntFactory() { var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); var entMan = sim.Resolve(); var uid = entMan.Spawn(); var comp = entMan.MetaQuery.Comp(uid); var bus = entMan.EventBusInternal; bus.ClearSubscriptions(); return (bus, sim, uid, comp); } private static EntityEventBus BusFactory() { return EntFactory().Bus; } /// /// Trying to subscribe a null handler causes a to be thrown. /// [Test] public void SubscribeEvent_NullHandler_NullArgumentException() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); // Act void Code() => bus.SubscribeEvent(EventSource.Local, subscriber, (EntityEventHandler) null!); //Assert Assert.Throws(Code); } /// /// Trying to subscribe with a null subscriber causes a to be thrown. /// [Test] public void SubscribeEvent_NullSubscriber_NullArgumentException() { // Arrange var bus = BusFactory(); // Act void Code() => bus.SubscribeEvent(EventSource.Local, null!, ev => {}); //Assert: this should do nothing Assert.Throws(Code); } /// /// Duplicate Event subscriptions are not allowed. /// [Test] public void SubscribeEvent_DuplicateSubscription_Invalid() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delegateCallCount = 0; void Handler(TestEventArgs ev) => delegateCallCount++; // 2 subscriptions 1 handler bus.SubscribeEvent(EventSource.Local, subscriber, Handler); Assert.Throws(() => bus.SubscribeEvent(EventSource.Local, subscriber, Handler)); } // TODO if ever duplicate events are allowed, re-enable these tests. /* /// /// Unlike C# events, the set of event handler delegates is unique. /// Subscribing the same delegate multiple times will only call the handler once. /// [Test] public void SubscribeEvent_DuplicateSubscription_RaisedOnce() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delegateCallCount = 0; void Handler(TestEventArgs ev) => delegateCallCount++; // 2 subscriptions 1 handler bus.SubscribeEvent(EventSource.Local, subscriber, Handler); bus.SubscribeEvent(EventSource.Local, subscriber, Handler); // Act bus.RaiseEvent(EventSource.Local, new TestEventArgs()); //Assert Assert.That(delegateCallCount, Is.EqualTo(1)); } /// /// Subscribing two different delegates to a single event type causes both events /// to be raised in an indeterminate order. /// [Test] public void SubscribeEvent_MultipleDelegates_BothRaised() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delFooCount = 0; int delBarCount = 0; bus.SubscribeEvent(EventSource.Local, subscriber, ev => delFooCount++); bus.SubscribeEvent(EventSource.Local, subscriber, ev => delBarCount++); // Act bus.RaiseEvent(EventSource.Local, new TestEventArgs()); // Assert Assert.That(delFooCount, Is.EqualTo(1)); Assert.That(delBarCount, Is.EqualTo(1)); } */ /// /// A subscriber's handlers are properly called only when the specified event type is raised. /// [Test] public void SubscribeEvent_MultipleSubscriptions_IndividuallyCalled() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delFooCount = 0; int delBarCount = 0; bus.SubscribeEvent(EventSource.Local, subscriber, ev => delFooCount++); bus.SubscribeEvent(EventSource.Local, subscriber, ev => delBarCount++); bus.LockSubscriptions(); // Act & Assert bus.RaiseEvent(EventSource.Local, new TestEventArgs()); Assert.That(delFooCount, Is.EqualTo(1)); Assert.That(delBarCount, Is.EqualTo(0)); delFooCount = delBarCount = 0; bus.RaiseEvent(EventSource.Local, new TestEventTwoArgs()); Assert.That(delFooCount, Is.EqualTo(0)); Assert.That(delBarCount, Is.EqualTo(1)); } /// /// Trying to subscribe with makes no sense and causes /// a to be thrown. /// [Test] public void SubscribeEvent_SourceNone_ArgOutOfRange() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); void TestEventHandler(TestEventArgs args) { } // Act void Code() => bus.SubscribeEvent(EventSource.None, subscriber, (EntityEventHandler)TestEventHandler); //Assert Assert.Throws(Code); } /// /// Unsubscribing a handler twice does nothing. /// [Test] public void UnsubscribeEvent_DoubleUnsubscribe_Nop() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); void Handler(TestEventArgs ev) { } bus.SubscribeEvent(EventSource.Local, subscriber, Handler); bus.UnsubscribeEvent(EventSource.Local, subscriber); bus.LockSubscriptions(); // Act bus.UnsubscribeEvent(EventSource.Local, subscriber); // Assert: Does not throw } /// /// Unsubscribing a handler that was never subscribed in the first place does nothing. /// [Test] public void UnsubscribeEvent_NoSubscription_Nop() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); // Act bus.UnsubscribeEvent(EventSource.Local, subscriber); // Assert: Does not throw } /// /// Trying to unsubscribe with a null subscriber causes a to be thrown. /// [Test] public void UnsubscribeEvent_NullSubscriber_NullArgumentException() { // Arrange var bus = BusFactory(); // Act void Code() => bus.UnsubscribeEvent(EventSource.Local, null!); // Assert Assert.Throws(Code); } /// /// An event cannot be subscribed to with , so trying to unsubscribe /// with an causes a to be thrown. /// [Test] public void UnsubscribeEvent_SourceNone_ArgOutOfRange() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); // Act void Code() => bus.UnsubscribeEvent(EventSource.None, subscriber); // Assert Assert.Throws(Code); } /// /// Raising an event with no handlers subscribed to it does nothing. /// [Test] public void RaiseEvent_NoSubscriptions_Nop() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delCalledCount = 0; bus.SubscribeEvent(EventSource.Local, subscriber, ev => delCalledCount++); bus.LockSubscriptions(); // Act bus.RaiseEvent(EventSource.Local, new TestEventArgs()); // Assert Assert.That(delCalledCount, Is.EqualTo(0)); } /// /// Raising an event when a handler has been unsubscribed no longer calls the handler. /// [Test] public void RaiseEvent_Unsubscribed_Nop() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delCallCount = 0; void Handler(TestEventArgs ev) => delCallCount++; bus.SubscribeEvent(EventSource.Local, subscriber, Handler); bus.UnsubscribeEvent(EventSource.Local, subscriber); bus.LockSubscriptions(); // Act bus.RaiseEvent(EventSource.Local, new TestEventArgs()); // Assert Assert.That(delCallCount, Is.EqualTo(0)); } /// /// Trying to raise an event with makes no sense and causes /// a to be thrown. /// [Test] public void RaiseEvent_SourceNone_ArgOutOfRange() { // Arrange var bus = BusFactory(); // Act void Code() => bus.RaiseEvent(EventSource.None, new TestEventArgs()); // Assert Assert.Throws(Code); } /// /// Trying to unsubscribe all of a null subscriber's events causes a to be thrown. /// [Test] public void UnsubscribeEvents_NullSubscriber_NullArgumentException() { // Arrange var bus = BusFactory(); // Act void Code() => bus.UnsubscribeEvents(null!); // Assert Assert.Throws(Code); } /// /// Unsubscribing a subscriber with no subscriptions does nothing. /// [Test] public void UnsubscribeEvents_NoSubscriptions_Nop() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); // Act bus.UnsubscribeEvents(subscriber); // Assert: no exception } /// /// The subscriber's handlers are not raised after they are unsubscribed. /// [Test] public void UnsubscribeEvents_UnsubscribedHandler_Nop() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delCallCount = 0; void Handler(TestEventArgs ev) => delCallCount++; bus.SubscribeEvent(EventSource.Local, subscriber, Handler); bus.UnsubscribeEvents(subscriber); bus.LockSubscriptions(); // Act bus.RaiseEvent(EventSource.Local, new TestEventArgs()); // Assert Assert.That(delCallCount, Is.EqualTo(0)); } /// /// Trying to queue a null event causes a to be thrown. /// [Test] public void QueueEvent_NullEvent_ArgumentNullException() { // Arrange var bus = BusFactory(); // Act void Code() => bus.QueueEvent(EventSource.Local, null!); // Assert Assert.Throws(Code); } /// /// Queuing an event does not immediately raise the event unless the queue is processed. /// [Test] public void QueueEvent_EventQueued_DoesNotImmediatelyRaise() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delCallCount = 0; void Handler(TestEventArgs ev) => delCallCount++; bus.SubscribeEvent(EventSource.Local, subscriber, Handler); // Act bus.QueueEvent(EventSource.Local, new TestEventArgs()); // Assert Assert.That(delCallCount, Is.EqualTo(0)); } /// /// Trying to queue an event with makes no sense and causes /// a to be thrown. /// [Test] public void QueueEvent_SourceNone_ArgOutOfRange() { // Arrange var bus = BusFactory(); // Act void Code() => bus.QueueEvent(EventSource.None, new TestEventArgs()); // Assert Assert.Throws(Code); } /// /// Queued events are raised when the queue is processed. /// [Test] public void ProcessQueue_EventQueued_HandlerRaised() { // Arrange var bus = BusFactory(); var subscriber = new TestEventSubscriber(); int delCallCount = 0; void Handler(TestEventArgs ev) => delCallCount++; bus.SubscribeEvent(EventSource.Local, subscriber, Handler); bus.LockSubscriptions(); bus.QueueEvent(EventSource.Local, new TestEventArgs()); // Act bus.ProcessEventQueue(); // Assert Assert.That(delCallCount, Is.EqualTo(1)); } [Test] public void RaiseEvent_Ordered() { // Arrange var bus = BusFactory(); // Expected order is A -> C -> B var a = false; var b = false; var c = false; void HandlerA(TestEventArgs ev) { Assert.That(b, Is.False, "A should run before B"); Assert.That(c, Is.False, "A should run before C"); a = true; } void HandlerB(TestEventArgs ev) { Assert.That(c, Is.True, "B should run after C"); b = true; } void HandlerC(TestEventArgs ev) => c = true; bus.SubscribeEvent(EventSource.Local, new SubA(), HandlerA, typeof(SubA), before: new []{typeof(SubB), typeof(SubC)}); bus.SubscribeEvent(EventSource.Local, new SubB(), HandlerB, typeof(SubB), after: new []{typeof(SubC)}); bus.SubscribeEvent(EventSource.Local, new SubC(), HandlerC, typeof(SubC)); bus.LockSubscriptions(); // Act bus.RaiseEvent(EventSource.Local, new TestEventArgs()); // Assert Assert.That(a, Is.True, "A did not fire"); Assert.That(b, Is.True, "B did not fire"); Assert.That(c, Is.True, "C did not fire"); } internal sealed class SubA : IEntityEventSubscriber { } internal sealed class SubB : IEntityEventSubscriber { } internal sealed class SubC : IEntityEventSubscriber { } } internal sealed class TestEventSubscriber : IEntityEventSubscriber { } internal sealed class TestEventArgs : EntityEventArgs { } internal sealed class TestEventTwoArgs : EntityEventArgs { } }