Skip to content

test: all runtime tests passing on consoles #1647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4b40c04
fix
NoelStephensUnity Jan 27, 2022
859d88c
style
NoelStephensUnity Jan 27, 2022
ae1c750
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Jan 27, 2022
c022e08
fix - partial
NoelStephensUnity Jan 28, 2022
9628212
style
NoelStephensUnity Jan 28, 2022
0e4435b
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Jan 28, 2022
5b6146a
fix
NoelStephensUnity Jan 28, 2022
26d2335
style
NoelStephensUnity Jan 28, 2022
041e10d
fix
NoelStephensUnity Jan 28, 2022
7766f27
fix
NoelStephensUnity Jan 28, 2022
554bf0d
style
NoelStephensUnity Jan 28, 2022
402210e
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Jan 28, 2022
43e9382
update
NoelStephensUnity Jan 28, 2022
8244627
Merge branch 'fix/console-tests-failing' of https://github.com/Unity-…
NoelStephensUnity Jan 28, 2022
d509fca
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Jan 28, 2022
90b9ed9
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Jan 29, 2022
96b62b4
update
NoelStephensUnity Jan 31, 2022
71ae813
refactor
NoelStephensUnity Jan 31, 2022
3a90687
refactor
NoelStephensUnity Jan 31, 2022
82d243f
revert
NoelStephensUnity Jan 31, 2022
72d3d49
refactor
NoelStephensUnity Jan 31, 2022
91d8f31
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Feb 1, 2022
d5efbe6
refactor
NoelStephensUnity Feb 1, 2022
d8e5b1f
refactor
NoelStephensUnity Feb 1, 2022
33e6601
refactor
NoelStephensUnity Feb 1, 2022
02e4681
style
NoelStephensUnity Feb 1, 2022
a66d4f3
style
NoelStephensUnity Feb 1, 2022
feb9e47
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Feb 1, 2022
a967264
update
NoelStephensUnity Feb 1, 2022
2f18881
fix
NoelStephensUnity Feb 1, 2022
0b955fe
fix
NoelStephensUnity Feb 1, 2022
1ee1297
fix
NoelStephensUnity Feb 1, 2022
c47ea65
fix
NoelStephensUnity Feb 2, 2022
2fa12fa
fix
NoelStephensUnity Feb 2, 2022
bee18d3
Merge branch 'develop' into fix/test-roject-runtimetests-standalone-t…
NoelStephensUnity Feb 2, 2022
2480daf
Merge branch 'develop' into fix/test-roject-runtimetests-standalone-t…
NoelStephensUnity Feb 2, 2022
73340d6
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Feb 2, 2022
67566d6
refactor
NoelStephensUnity Feb 2, 2022
76af4d4
test
NoelStephensUnity Feb 2, 2022
312a5f5
test
NoelStephensUnity Feb 2, 2022
5319473
revert
NoelStephensUnity Feb 2, 2022
a2f6fca
test
NoelStephensUnity Feb 2, 2022
35b47a5
test refactor
NoelStephensUnity Feb 2, 2022
994d459
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Feb 2, 2022
71a4d4e
Merge branch 'develop' into fix/console-tests-failing
NoelStephensUnity Feb 2, 2022
091d82f
test refactor
NoelStephensUnity Feb 3, 2022
f8fd60c
test update and style
NoelStephensUnity Feb 3, 2022
38ec0fe
update
NoelStephensUnity Feb 3, 2022
0f9d8ca
update
NoelStephensUnity Feb 3, 2022
3088309
fix
NoelStephensUnity Feb 3, 2022
ba3e09d
style
NoelStephensUnity Feb 3, 2022
3e70580
update
NoelStephensUnity Feb 3, 2022
861ef54
fix and update
NoelStephensUnity Feb 3, 2022
5915c3e
test fix
NoelStephensUnity Feb 3, 2022
82606ea
test update
NoelStephensUnity Feb 3, 2022
36c52d7
style
NoelStephensUnity Feb 3, 2022
85fded2
style
NoelStephensUnity Feb 3, 2022
8ad9b42
fix
NoelStephensUnity Feb 3, 2022
ce84aba
test update
NoelStephensUnity Feb 3, 2022
1c2d493
style
NoelStephensUnity Feb 3, 2022
455a756
Revert "test"
NoelStephensUnity Feb 3, 2022
120073d
Merge branch 'fix/console-tests-failing' into test/all-runtime-tests-…
NoelStephensUnity Feb 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
#endif
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]

107 changes: 104 additions & 3 deletions com.unity.netcode.gameobjects/Tests/Runtime/BaseMultiInstanceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,70 @@ public abstract class BaseMultiInstanceTest

protected bool m_BypassStartAndWaitForClients = false;

static protected TimeOutHelper s_GloabalTimeOutHelper = new TimeOutHelper();

protected const uint k_DefaultTickRate = 30;
protected WaitForSeconds m_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);

/// <summary>
/// An update to the original MultiInstanceHelpers.WaitForCondition that:
/// -operates at the current tick rate
/// -provides a value of type T to be passed into the checkForCondition function
/// -allows for a unique TimeOutHelper handler (if none then uses the default)
/// -adjusts its yield period to the settings of the m_ServerNetworkManager.NetworkConfig.TickRate
/// Notes: This method provides more stability when running integration tests that could
/// be impacted by:
/// -how the integration test is being executed (i.e. in editor or in a stand alone build)
/// -potential platform performance issues (i.e. VM is throttled or maxed)
/// </summary>
/// <typeparam name="T">type of the value to compare</typeparam>
/// <param name="checkForCondition">condition checking function that is passed valueToCompare to compare against</param>
/// <param name="valueToCompare">the value to compare against</param>
/// <param name="timeOutHelper">optional custom time out helper-handler</param>
/// <returns></returns>
protected IEnumerator WaitForConditionOrTimeOut<T>(Func<T, bool> checkForCondition, T valueToCompare, TimeOutHelper timeOutHelper = null)
{
if (checkForCondition == null)
{
throw new ArgumentNullException($"checkForCondition cannot be null!");
}

if (valueToCompare == null)
{
throw new ArgumentNullException($"The value to be compared cannot be null!");
}

// If none is provided we use the default global time out helper
if (timeOutHelper == null)
{
timeOutHelper = s_GloabalTimeOutHelper;
}

// Start checking for a timeout
timeOutHelper.Start();
while (!timeOutHelper.HasTimedOut())
{
// Update and check to see if the condition has been met
if (checkForCondition.Invoke(valueToCompare))
{
break;
}

// Otherwise wait for 1 tick interval
yield return m_DefaultWaitForTick;
}
// Stop checking for a timeout
timeOutHelper.Stop();
}

[UnitySetUp]
public virtual IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, _ => { });
if (m_ServerNetworkManager != null)
{
m_DefaultWaitForTick = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate);
}
}

[UnityTearDown]
Expand Down Expand Up @@ -54,9 +114,12 @@ public virtual IEnumerator Teardown()
Object.DestroyImmediate(networkObject);
}

// wait for next frame so everything is destroyed, so following tests can execute from clean environment
int nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
// wait for 1 tick interval so everything is destroyed and any following tests
// can execute from clean environment
yield return m_DefaultWaitForTick;

// reset the m_ServerWaitForTick for the next test to initialize
m_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
}

/// <summary>
Expand Down Expand Up @@ -170,4 +233,42 @@ public IEnumerator StartSomeClientsAndServerWithPlayers(bool useHost, int nbClie
}
}
}

/// <summary>
/// Can be used independently or assigned to <see cref="BaseMultiInstanceTest.WaitForConditionOrTimeOut"></see> in the
/// event the default timeout period needs to be adjusted
/// </summary>
public class TimeOutHelper
{
private const float k_DefaultTimeOutWaitPeriod = 2.0f;

private float m_MaximumTimeBeforeTimeOut;
private float m_TimeOutPeriod;

private bool m_IsStarted;
public bool TimedOut { get; internal set; }

public void Start()
{
m_MaximumTimeBeforeTimeOut = Time.realtimeSinceStartup + m_TimeOutPeriod;
m_IsStarted = true;
TimedOut = false;
}

public void Stop()
{
TimedOut = HasTimedOut();
m_IsStarted = false;
}

public bool HasTimedOut()
{
return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut;
}

public TimeOutHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod)
{
m_TimeOutPeriod = timeOutPeriod;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
Expand Down Expand Up @@ -153,9 +154,7 @@ public class NetworkBehaviourUpdaterTests : BaseMultiInstanceTest
{
// Go ahead and create maximum number of clients (not all tests will use them)
protected override int NbClients => 2;
private const float k_TimeOutWaitPeriod = 2.0f;
public const int NetVarValueToSet = 1;
private static float s_TimeOutPeriod;
private static List<GameObject> s_ClientSpawnedNetworkObjects = new List<GameObject>();
private List<NetworkManager> m_ActiveClientsForCurrentTest;

Expand All @@ -168,31 +167,9 @@ public static void ClientSideNotifyObjectSpawned(GameObject objectSpaned)
if (!s_ClientSpawnedNetworkObjects.Contains(objectSpaned))
{
s_ClientSpawnedNetworkObjects.Add(objectSpaned);
// As long as we are getting notified the clients are spawning objects
// then bump up the timeout period
AdvanceTimeOutPeriod();
}
}

/// <summary>
/// This will simply advance the timeout period
/// Note: When ClientSideNotifyObjectSpawned is invoked this will get
/// called to handle any potential latencies due to poor performance or
/// the like.
/// </summary>
private static void AdvanceTimeOutPeriod()
{
s_TimeOutPeriod = Time.realtimeSinceStartup + k_TimeOutWaitPeriod;
}

/// <summary>
/// Checks if the timeout period has elapsed
/// </summary>
private static bool HasTimedOut()
{
return s_TimeOutPeriod <= Time.realtimeSinceStartup;
}

public override IEnumerator Setup()
{
// This makes sure the server and client are not started during the setup
Expand Down Expand Up @@ -315,22 +292,10 @@ public IEnumerator BehaviourUpdaterAllTests([Values] bool useHost,
// wait until all objects are spawned on the clients
if (numberOfObjectsToSpawnOnClients > 0)
{
var allClientsSpawnedObjects = false;
// Reset the time out to be k_TimeOutWaitPeriod + the current time
AdvanceTimeOutPeriod();

// Waits for all clients to spawn the NetworkObjects
while (!allClientsSpawnedObjects && !HasTimedOut())
{
allClientsSpawnedObjects = numberOfObjectsToSpawnOnClients == s_ClientSpawnedNetworkObjects.Count;
yield return new WaitForSeconds(tickInterval);
}

Assert.True(!HasTimedOut(), $"Timed out waiting for clients to report spawning objects! " +
yield return WaitForConditionOrTimeOut((c) => c == s_ClientSpawnedNetworkObjects.Count, numberOfObjectsToSpawnOnClients);
Assert.IsFalse(s_GloabalTimeOutHelper.TimedOut, $"Timed out waiting for clients to report spawning objects! " +
$"Total reported client-side spawned objects {s_ClientSpawnedNetworkObjects.Count}");

// This really should never fail as it should timeout first
Assert.True(allClientsSpawnedObjects, "Not all clients spawned their objects!");
}

// Once all clients have spawned the NetworkObjects, set the network variables for
Expand Down Expand Up @@ -371,30 +336,10 @@ public IEnumerator BehaviourUpdaterAllTests([Values] bool useHost,
}
}

var allClientsCompleted = false;
AdvanceTimeOutPeriod();

// Wait until all clients have had their NetworkVariables updated
while (!allClientsCompleted && !HasTimedOut())
{
var completedCount = 0;
foreach (var clientNetVarContainer in clientSideNetVarContainers)
{
if (clientNetVarContainer.HaveAllValuesChanged(NetVarValueToSet))
{
completedCount++;
}
}

allClientsCompleted = completedCount == clientSideNetVarContainers.Count;

yield return new WaitForSeconds(tickInterval);
}

Assert.True(!HasTimedOut(), $"Timed out waiting for client side NetVarContainers to report all NetworkVariables have been updated!");

// This really should never fail as it should timeout first
Assert.True(allClientsCompleted, "Not all client side NetworkVariables reported they were updated with target value!");
yield return WaitForConditionOrTimeOut((c) =>
Copy link
Collaborator

@ShadauxCat ShadauxCat Feb 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something to consider here: What this test is really trying to do is wait for messages to be sent and received and then assert that the values have changed. Waiting for the values to change like this is kind of mixing the precondition with the assertion - if the assertion fails we wait forever.

In general, I would like to see us moving in the direction in our test code of waiting for distinct preconditions that are not part of what's being tested, and then asserting on the expected result. So in this case, using WaitForMessageOfType<SnapshotDataMessage> or WaitForMessageOfType<NetworkVariableDeltaMessage> (not sure if snapshots are on or not for this test) and then asserting that all the values have changed.

Reading this test as it's written now, if I were to interpret the written code as a "given-when-then" statement, I would word it as "Given an object with network variables on multiple clients, when all clients have changed the values of their variables, waiting for them to change doesn't time out".

I know this is a pretty pedantic comment, but I think moving toward smaller, more discreet tests that test only one thing and are very clear about what they're testing by what's inside the one assert, will ultimately make our test code better, cleaner, and easier to maintain.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other places in this PR where this comment also applies, but I'm going to only mention it the once. It might be something to address later rather than now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This comment, btw, is directed at myself as well. I've been guilty of this in the past, too. I've been trying to learn more about good test design and this is something I've learned has value over the last few months.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that is why I switched it over to the +new+ WaitForConditionOrTimeOut. It will wait for a condition to be reached and if the condition is never met it will stop waiting and the new assert below checks to see if it timed out or not. If not, it moves on, but if so then it asserts with a meaningful/unique message that makes troubleshooting a failed integration test easier to find.

clientSideNetVarContainers.Where(d =>
d.HaveAllValuesChanged(c)).Count() == clientSideNetVarContainers.Count, NetVarValueToSet);
Assert.IsFalse(s_GloabalTimeOutHelper.TimedOut, $"Timed out waiting for client side NetVarContainers to report all NetworkVariables have been updated!");
}

Object.DestroyImmediate(prefabToSpawn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public class NetworkObjectOnSpawnTests : BaseMultiInstanceTest

protected override int NbClients => 2;


/// <summary>
/// Tests that instantiating a <see cref="NetworkObject"/> and destroying without spawning it
/// does not run <see cref="NetworkBehaviour.OnNetworkSpawn"/> or <see cref="NetworkBehaviour.OnNetworkSpawn"/>.
Expand Down Expand Up @@ -57,7 +56,6 @@ public override IEnumerator Setup()
[UnityTearDown]
public override IEnumerator Teardown()
{

if (m_TestNetworkObjectPrefab != null)
{
Object.Destroy(m_TestNetworkObjectPrefab);
Expand All @@ -68,7 +66,6 @@ public override IEnumerator Teardown()
Object.Destroy(m_TestNetworkObjectInstance);
}
yield return base.Teardown();

}

/// <summary>
Expand Down Expand Up @@ -99,69 +96,79 @@ public IEnumerator TestOnNetworkSpawnCallbacks()
// check spawned on server
Assert.AreEqual(1, serverInstance.OnNetworkSpawnCalledCount);

// safety check despawned
// safety check server despawned
Assert.AreEqual(0, serverInstance.OnNetworkDespawnCalledCount);

// check spawned on client
foreach (var clientInstance in clientInstances)
// Conditional check for clients spawning or despawning
bool checkSpawnCondition = false;
bool HasConditionBeenMet(int count)
{
Assert.AreEqual(1, clientInstance.OnNetworkSpawnCalledCount);

// safety check despawned
Assert.AreEqual(0, clientInstance.OnNetworkDespawnCalledCount);
var clientsCompleted = 0;
// check spawned on client
foreach (var clientInstance in clientInstances)
{
if (checkSpawnCondition)
{
if (clientInstance.OnNetworkSpawnCalledCount == count)
{
clientsCompleted++;
}
}
else
{
if (clientInstance.OnNetworkDespawnCalledCount == count)
{
clientsCompleted++;
}
}
}
return clientsCompleted >= NbClients;
}

// despawn on server. However, since we'll be using this object later in the test, don't delete it (false)
// safety check that all clients have not been despawned yet
Assert.True(HasConditionBeenMet(0), "Failed condition that all clients not despawned yet!");

// now verify that all clients have been spawned
checkSpawnCondition = true;
yield return WaitForConditionOrTimeOut(HasConditionBeenMet, 1);
Assert.False(s_GloabalTimeOutHelper.TimedOut, "Timed out while waiting for client side spawns!");

// despawn on server. However, since we'll be using this object later in the test, don't delete it
serverInstance.GetComponent<NetworkObject>().Despawn(false);

// check despawned on server
Assert.AreEqual(1, serverInstance.OnNetworkDespawnCalledCount);

// wait long enough for player object to be despawned
int nextFrameNumber = Time.frameCount + 4;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);

// check despawned on clients
foreach (var clientInstance in clientInstances)
{
Assert.AreEqual(1, clientInstance.OnNetworkDespawnCalledCount);
}
// verify that all client-side instances are despawned
checkSpawnCondition = false;
yield return WaitForConditionOrTimeOut(HasConditionBeenMet, 1);

//----------- step 2 check spawn again and destroy
Assert.False(s_GloabalTimeOutHelper.TimedOut, "Timed out while waiting for client side despawns!");

//----------- step 2 check spawn and destroy again
serverInstance.GetComponent<NetworkObject>().Spawn();

// wait long enough for player object to be spawned
nextFrameNumber = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);

// wait a tick
yield return m_DefaultWaitForTick;
// check spawned again on server this is 2 because we are reusing the object which was already spawned once.
Assert.AreEqual(2, serverInstance.OnNetworkSpawnCalledCount);

// check spawned on client
foreach (var clientInstance in clientInstances)
{
Assert.AreEqual(1, clientInstance.OnNetworkSpawnCalledCount);
}
checkSpawnCondition = true;
yield return WaitForConditionOrTimeOut(HasConditionBeenMet, 1);

Assert.False(s_GloabalTimeOutHelper.TimedOut, "Timed out while waiting for client side spawns! (2nd pass)");

// destroy the server object
Object.Destroy(serverInstance.gameObject);

// wait one frame for destroy to kick in
yield return null;
yield return m_DefaultWaitForTick;

// check whether despawned was called again on server instance
Assert.AreEqual(2, serverInstance.OnNetworkDespawnCalledCount);

// wait long enough for player object to be despawned on client
nextFrameNumber = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
checkSpawnCondition = false;
yield return WaitForConditionOrTimeOut(HasConditionBeenMet, 1);

// check despawned on clients
foreach (var clientInstance in clientInstances)
{
Assert.AreEqual(1, clientInstance.OnNetworkDespawnCalledCount);
}
Assert.False(s_GloabalTimeOutHelper.TimedOut, "Timed out while waiting for client side despawns! (2nd pass)");
}

private class TrackOnSpawnFunctions : NetworkBehaviour
Expand Down
Loading