using System.Text.RegularExpressions; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; namespace Mirror.Tests { public class SyncVarTests : MirrorTest { [SetUp] public override void SetUp() { base.SetUp(); // SyncVar hooks are only called while client is active for now. // so we need an active client. NetworkServer.Listen(1); ConnectClientBlockingAuthenticatedAndReady(out _); } [TearDown] public override void TearDown() { base.TearDown(); } // SyncField should recommend SyncFielGameObject instead [Test] public void SyncFieldGameObject_Recommendation() { // should show even if value is null since T is LogAssert.Expect(LogType.Warning, new Regex($"Use explicit {nameof(SyncVarGameObject)}.*")); SyncVar _ = new SyncVar(null); } // SyncField should recommend SyncFielNetworkIdentity instead [Test] public void SyncFieldNetworkIdentity_Recommendation() { // should show even if value is null since T is LogAssert.Expect(LogType.Warning, new Regex($"Use explicit {nameof(SyncVarNetworkIdentity)}.*")); SyncVar _ = new SyncVar(null); } // SyncField should recommend SyncFielNetworkBehaviour instead [Test] public void SyncFieldNetworkBehaviour_Recommendation() { // should show even if value is null since T is LogAssert.Expect(LogType.Warning, new Regex($"Use explicit SyncVarNetworkBehaviour.*")); SyncVar _ = new SyncVar(null); } [Test] public void SetValue_SetsValue() { // .Value is a property which does several things. // make sure it .set actually sets the value SyncVar field = 42; // avoid 'not initialized' exception field.OnDirty = () => {}; field.Value = 1337; Assert.That(field.Value, Is.EqualTo(1337)); } [Test] public void SetValue_CallsOnDirty() { SyncVar field = 42; int dirtyCalled = 0; field.OnDirty = () => ++dirtyCalled; // setting SyncField.Value should call dirty field.Value = 1337; Assert.That(dirtyCalled, Is.EqualTo(1)); } [Test] public void SetValue_CallsOnDirty_OnlyIfValueChanged() { SyncVar field = 42; int dirtyCalled = 0; field.OnDirty = () => ++dirtyCalled; // setting same value should not call OnDirty again field.Value = 42; Assert.That(dirtyCalled, Is.EqualTo(0)); } [Test] public void ImplicitTo() { SyncVar field = new SyncVar(42); // T = field implicit conversion should get .Value int value = field; Assert.That(value, Is.EqualTo(42)); } [Test] public void ImplicitFrom_SetsValue() { // field = T implicit conversion should set .Value SyncVar field = 42; Assert.That(field.Value, Is.EqualTo(42)); } [Test] public void Hook_IsCalled() { int called = 0; void OnChanged(int oldValue, int newValue) { ++called; Assert.That(oldValue, Is.EqualTo(42)); Assert.That(newValue, Is.EqualTo(1337)); } SyncVar field = 42; field.Callback += OnChanged; // avoid 'not initialized' exception field.OnDirty = () => {}; field.Value = 1337; Assert.That(called, Is.EqualTo(1)); } [Test] public void Hook_OnlyCalledIfValueChanged() { int called = 0; void OnChanged(int oldValue, int newValue) { ++called; Assert.That(oldValue, Is.EqualTo(42)); Assert.That(newValue, Is.EqualTo(1337)); } SyncVar field = 42; field.Callback += OnChanged; // assign same value again. hook shouldn't be called again. field.Value = 42; Assert.That(called, Is.EqualTo(0)); } [Test] public void Hook_Set_DoesntDeadlock() { // Value.set calls the hook. // calling Value.set inside the hook would deadlock. // this needs to be prevented. SyncVar field = null; int called = 0; void OnChanged(int oldValue, int newValue) { // setting a different value calls setter -> hook again field.Value = 0; ++called; } field = 42; field.Callback += OnChanged; // avoid 'not initialized' exception field.OnDirty = () => {}; // setting a different value will call the hook field.Value = 1337; // in the end, hook should've been called exactly once Assert.That(called, Is.EqualTo(1)); } [Test] public void DeserializeAll_CallsHook() { // create field with hook int called = 0; void OnChanged(int oldValue, int newValue) { ++called; Assert.That(oldValue, Is.EqualTo(42)); Assert.That(newValue, Is.EqualTo(1337)); } SyncVar field = 42; field.Callback += OnChanged; // avoid 'not initialized' exception field.OnDirty = () => {}; // create reader with data NetworkWriter writer = new NetworkWriter(); writer.WriteInt(1337); NetworkReader reader = new NetworkReader(writer.ToArraySegment()); // deserialize field.OnDeserializeAll(reader); Assert.That(called, Is.EqualTo(1)); } [Test] public void DeserializeDelta_CallsHook() { // create field with hook int called = 0; void OnChanged(int oldValue, int newValue) { ++called; Assert.That(oldValue, Is.EqualTo(42)); Assert.That(newValue, Is.EqualTo(1337)); } SyncVar fieldWithHook = 42; fieldWithHook.Callback += OnChanged; // avoid 'not initialized' exception fieldWithHook.OnDirty = () => {}; // create reader with data NetworkWriter writer = new NetworkWriter(); writer.WriteInt(1337); NetworkReader reader = new NetworkReader(writer.ToArraySegment()); // deserialize fieldWithHook.OnDeserializeDelta(reader); Assert.That(called, Is.EqualTo(1)); } [Test] public void EqualsT() { // .Equals should compare .Value SyncVar field = 42; Assert.That(field.Equals(42), Is.True); } [Test] public void EqualsNull() { // .Equals(null) should always be false. so that == null works. SyncVar field = 42; Assert.That(field.Equals(null), Is.False); } [Test] public void EqualsEqualsT() { // == should compare .Value SyncVar field = 42; Assert.That(field == 42, Is.True); } [Test] public void ToString_CallsValueToString() { SyncVar field = 42; Assert.That(field.ToString(), Is.EqualTo("42")); } } }