using LobbyRelaySample; using NUnit.Framework; using System; using System.Text.RegularExpressions; using UnityEngine.TestTools; namespace Test { public class MessengerTests { #region Test classes /// Trivial message recipient that will run some action on any message. class Subscriber : IReceiveMessages { Action m_thingToDo; public Subscriber(Action thingToDo) { m_thingToDo = thingToDo; } public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); } } /// Trivial message recipient that will run some action on any message, with args. private class SubscriberArgs : IReceiveMessages { private Action m_thingToDo; public SubscriberArgs(Action thingToDo) { m_thingToDo = thingToDo; } public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(type, msg); } } /// Trivial message recipient that will run some action on any message and then unsubscribe. private class SubscriberUnsub : IReceiveMessages { private Action m_thingToDo; private Messenger m_messenger; public SubscriberUnsub(Action thingToDo, Messenger messenger) { m_thingToDo = thingToDo; m_messenger = messenger; } public void OnReceiveMessage(MessageType type, object msg) { m_thingToDo?.Invoke(); m_messenger.Unsubscribe(this); } } #endregion [SetUp] public void Setup() { LogHandler.Get().mode = LogMode.Verbose; // Some tests rely on log messages appearing. This is reset when entering Play mode, so it's safe to set it arbitrarily here. } [Test] public void BasicBehavior() { Messenger messenger = new Messenger(); int msgCount = 0; SubscriberArgs sub = new SubscriberArgs((type, msg) => { msgCount++; // These are just for simple detection of the intended behavior. if (type == MessageType.RenameRequest) msgCount += 9; if (msg is string) msgCount += int.Parse(msg as string); }); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(0, msgCount, "Should not act on message until Subscribed."); messenger.Subscribe(sub); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "Should act on message once Subscribed"); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(2, msgCount, "Should act on message once Subscribed, again"); messenger.OnReceiveMessage(MessageType.RenameRequest, null); Assert.AreEqual(12, msgCount, "Should provide the correct message type."); messenger.OnReceiveMessage(MessageType.None, "99"); Assert.AreEqual(112, msgCount, "Should provide the msg object."); messenger.Subscribe(sub); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(113, msgCount, "Should not duplicate subscription."); messenger.Unsubscribe(sub); messenger.OnReceiveMessage(MessageType.None, "36"); Assert.AreEqual(113, msgCount, "Should not message subscriber once Unsubscribed."); messenger.Subscribe(sub); messenger.OnReceiveMessage(MessageType.None, "36"); Assert.AreEqual(150, msgCount, "Should receive messages on resubscription."); messenger.Unsubscribe(sub); } [Test] public void BasicBehavior_Multiple() { Messenger messenger = new Messenger(); int msgCount = 0; SubscriberArgs sub1 = new SubscriberArgs(UpdateMsgCount); SubscriberArgs sub2 = new SubscriberArgs(UpdateMsgCount); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(0, msgCount, "Base case"); messenger.Subscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "First subscriber"); messenger.Subscribe(sub2); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(3, msgCount, "Both subscribers should get the message."); messenger.Unsubscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(4, msgCount, "Second subscriber should not be affected by first subscriber's Unsubscribe."); messenger.Unsubscribe(sub2); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(4, msgCount, "No Unsubscribed subscriber should get messages."); void UpdateMsgCount(MessageType type, object msg) { msgCount++; if (type == MessageType.RenameRequest) msgCount += 9; if (msg is string) msgCount += int.Parse(msg as string); } } [Test] public void SubAndUnsubRepeatedly() { Messenger messenger = new Messenger(); int msgCount = 0; Subscriber sub1 = new Subscriber(() => { msgCount++; }); Subscriber sub2 = new Subscriber(() => { msgCount += 10; }); Subscriber sub3 = new Subscriber(() => { msgCount += 100; }); messenger.Subscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "Initial state"); messenger.Unsubscribe(sub1); messenger.Unsubscribe(sub2); messenger.Subscribe(sub2); messenger.Subscribe(sub1); messenger.Subscribe(sub3); messenger.Subscribe(sub3); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(112, msgCount, "Should have all three subscribers registered once."); } [Test] public void SubscribeWithinOnReceiveMessage() { Messenger messenger = new Messenger(); int msgCount = 0; Subscriber sub1 = new Subscriber(UpdateMsgCountWithSubscribe); messenger.Subscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "First subscriber adds another when receiving a message, but the new subscriber shouldn't immediately receive the same message."); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(3, msgCount, "First subscriber adds a third on another message; second subscriber gets this message."); messenger.Unsubscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(5, msgCount, "Original subscriber is gone; two added subscribers persist."); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(7, msgCount, "Confirming there are just the two subscribers."); void UpdateMsgCountWithSubscribe() { msgCount++; messenger.Subscribe(new Subscriber(UpdateMsgCount)); } void UpdateMsgCount() { msgCount++; } } [Test] public void UnsubscribeWithinOnReceiveMessage() { Messenger messenger = new Messenger(); int msgCount = 0; SubscriberUnsub sub1 = new SubscriberUnsub(UpdateMsgCount, messenger); Subscriber sub2 = new Subscriber(UpdateMsgCount); messenger.Subscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "Message received by subscriber."); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "Unsubscribed as part of OnReceiveMessage."); messenger.Subscribe(sub1); messenger.Subscribe(sub2); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(3, msgCount, "Both subscribed subs get the message."); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(4, msgCount, "One sub is still subscribed."); messenger.Subscribe(sub1); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(6, msgCount, "Both subscribed subs get the message (reversed order)."); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(7, msgCount, "One sub is again still subscribed."); void UpdateMsgCount() { msgCount++; } } /// /// If a subscriber is added/removed during an OnReceiveMessage call and it immediately sends its own message, we must ensure that we don't process the change in the subscriber list. /// (During a foreach loop, this is the "Collection was modified" error.) /// [Test] [Timeout(100)] // These recursion tests could recurse infinitely, so time out (in ms) just in case. (We should see a StackOverflowException before then, though.) public void RecurseWithinOnReceiveMessageDontModifyCollection() { Messenger messenger = new Messenger(); int msgCount = 0; SubscriberArgs sub = new SubscriberArgs(Recurse); messenger.Subscribe(sub); LogAssert.Expect(UnityEngine.LogType.Error, new Regex(".*OnReceiveMessage recursion detected.*")); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(6, msgCount, "Expected to break out of recursion and not allow subscriber list modification while recursing."); LogAssert.ignoreFailingMessages = true; // The "recursion detected" error will appear multiple times. messenger.OnReceiveMessage(MessageType.None, null); LogAssert.ignoreFailingMessages = false; Assert.Less(12, msgCount, "Subscribers added during recursion should be given messages afterward."); void Recurse(MessageType type, object msg) { msgCount++; if (type == MessageType.None) // This is just to prevent an exponential explosion of new subscribers. The exact type used is arbitrary. { SubscriberArgs newSub = new SubscriberArgs(Recurse); messenger.Subscribe(newSub); } messenger.OnReceiveMessage(MessageType.RenameRequest, null); } } [Test] [Timeout(100)] public void RecurseWithinOnReceiveMessageBreakout() { Messenger messenger = new Messenger(); int msgCount = 0; Subscriber sub = new Subscriber(Recurse); messenger.Subscribe(sub); messenger.OnReceiveMessage(MessageType.None, null); Assert.Pass("Should have broken out of a recursion loop if too deep."); void Recurse() { msgCount++; Subscriber newSub = new Subscriber(Recurse); messenger.Subscribe(newSub); messenger.OnReceiveMessage(MessageType.None, null); } } /// /// If a message recipient takes a long time to process a message, we want to be made aware. /// [Test] public void WhatIfAMessageIsVerySlow() { Messenger messenger = new Messenger(); int msgCount = 0; string inefficientString = ""; Subscriber sub = new Subscriber(() => { for (int n = 0; n < 12345; n++) inefficientString += n.ToString(); msgCount++; }); messenger.Subscribe(sub); LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(".*took too long.*")); messenger.OnReceiveMessage(MessageType.None, null); Assert.AreEqual(1, msgCount, "Should have acted on the message."); } } }