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.");
}
}
}