You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
532 lines
21 KiB
C#
532 lines
21 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using NUnit.Framework;
|
|
using Unity.BossRoom.ConnectionManagement;
|
|
using Unity.BossRoom.Infrastructure;
|
|
using Unity.BossRoom.UnityServices;
|
|
using Unity.BossRoom.UnityServices.Lobbies;
|
|
using Unity.BossRoom.Utils;
|
|
using Unity.Multiplayer.Samples.Utilities;
|
|
using Unity.Netcode;
|
|
using Unity.Netcode.TestHelpers.Runtime;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.TestTools;
|
|
using VContainer;
|
|
using VContainer.Unity;
|
|
|
|
namespace Unity.BossRoom.Tests.Runtime
|
|
{
|
|
public class ConnectionManagementTests : NetcodeIntegrationTest
|
|
{
|
|
const string k_FailedToConnectToServerErrorMessage = "Failed to connect to server.";
|
|
|
|
class ConnectionManagementTestsLifeTimeScope : LifetimeScope
|
|
{
|
|
public NetworkManager NetworkManager;
|
|
public ConnectionManager ConnectionManager;
|
|
public UpdateRunner UpdateRunner;
|
|
|
|
protected override void Awake() { }
|
|
|
|
protected override void Configure(IContainerBuilder builder)
|
|
{
|
|
builder.RegisterComponent(NetworkManager);
|
|
builder.RegisterComponent(ConnectionManager);
|
|
builder.RegisterComponent(UpdateRunner);
|
|
builder.RegisterComponent(new NetworkedMessageChannel<ConnectionEventMessage>()).AsImplementedInterfaces();
|
|
builder.RegisterInstance(new BufferedMessageChannel<ConnectStatus>()).AsImplementedInterfaces();
|
|
builder.RegisterInstance(new MessageChannel<UnityServiceErrorMessage>()).AsImplementedInterfaces();
|
|
builder.RegisterInstance(new MessageChannel<ReconnectMessage>()).AsImplementedInterfaces();
|
|
builder.RegisterInstance(new BufferedMessageChannel<LobbyListFetchedMessage>()).AsImplementedInterfaces();
|
|
builder.Register<LocalLobby>(Lifetime.Singleton);
|
|
builder.Register<LocalLobbyUser>(Lifetime.Singleton);
|
|
builder.Register<ProfileManager>(Lifetime.Singleton);
|
|
builder.RegisterEntryPoint<LobbyServiceFacade>(Lifetime.Singleton).AsSelf();
|
|
}
|
|
}
|
|
|
|
class SceneLoaderWrapperStub : SceneLoaderWrapper
|
|
{
|
|
public override void Awake()
|
|
{
|
|
Instance = this;
|
|
}
|
|
|
|
public override void Start() { }
|
|
|
|
public override void LoadScene(string sceneName, bool useNetworkSceneManager, LoadSceneMode loadSceneMode = LoadSceneMode.Single) { }
|
|
}
|
|
|
|
protected override int NumberOfClients => 2;
|
|
|
|
ConnectionManagementTestsLifeTimeScope[] m_ClientScopes;
|
|
ConnectionManagementTestsLifeTimeScope m_ServerScope;
|
|
|
|
ConnectionManager[] m_ClientConnectionManagers;
|
|
ConnectionManager m_ServerConnectionManager;
|
|
|
|
protected override bool CanStartServerAndClients()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static void InitializeInstance(string name, NetworkManager networkManager, out ConnectionManager connectionManager, out ConnectionManagementTestsLifeTimeScope scope)
|
|
{
|
|
var connectionManagerGO = new GameObject($"ConnectionManager - {name}");
|
|
connectionManager = connectionManagerGO.AddComponent<ConnectionManager>();
|
|
|
|
networkManager.NetworkConfig.ConnectionApproval = true;
|
|
networkManager.NetworkConfig.EnableSceneManagement = false;
|
|
|
|
var updateRunnerGO = new GameObject($"UpdateRunner - {name}");
|
|
var updateRunner = updateRunnerGO.AddComponent<UpdateRunner>();
|
|
|
|
var lifeTimeScopeGO = new GameObject($"LifeTimeScope - {name}");
|
|
scope = lifeTimeScopeGO.AddComponent<ConnectionManagementTestsLifeTimeScope>();
|
|
scope.NetworkManager = networkManager;
|
|
scope.ConnectionManager = connectionManager;
|
|
scope.UpdateRunner = updateRunner;
|
|
scope.Build();
|
|
}
|
|
|
|
void CreatePlayerPrefab()
|
|
{
|
|
// Create playerPrefab
|
|
m_PlayerPrefab = new GameObject("Player");
|
|
NetworkObject networkObject = m_PlayerPrefab.AddComponent<NetworkObject>();
|
|
|
|
// Make it a prefab
|
|
NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject);
|
|
|
|
// Set the player prefab for the server and clients
|
|
m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
|
|
|
|
foreach (var client in m_ClientNetworkManagers)
|
|
{
|
|
client.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
|
|
}
|
|
}
|
|
|
|
protected override void OnServerAndClientsCreated()
|
|
{
|
|
var sceneLoaderWrapperGO = new GameObject("SceneLoader");
|
|
sceneLoaderWrapperGO.AddComponent<SceneLoaderWrapperStub>();
|
|
|
|
m_ClientScopes = new ConnectionManagementTestsLifeTimeScope[NumberOfClients];
|
|
m_ClientConnectionManagers = new ConnectionManager[NumberOfClients];
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var clientId = i;
|
|
InitializeInstance($"Client{clientId}", m_ClientNetworkManagers[clientId], out m_ClientConnectionManagers[clientId], out m_ClientScopes[clientId]);
|
|
}
|
|
|
|
InitializeInstance("Server", m_ServerNetworkManager, out m_ServerConnectionManager, out m_ServerScope);
|
|
|
|
CreatePlayerPrefab();
|
|
|
|
base.OnServerAndClientsCreated();
|
|
}
|
|
|
|
protected override IEnumerator OnTearDown()
|
|
{
|
|
m_ServerConnectionManager.RequestShutdown();
|
|
|
|
yield return new WaitWhile(() => m_ServerNetworkManager.IsListening);
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var clientId = i;
|
|
m_ClientConnectionManagers[clientId].RequestShutdown();
|
|
yield return new WaitWhile(() => m_ClientNetworkManagers[clientId].IsListening);
|
|
}
|
|
|
|
m_ServerScope.Dispose();
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
m_ClientScopes[i].Dispose();
|
|
}
|
|
|
|
foreach (var sceneGameObject in GameObject.FindObjectsOfType<GameObject>())
|
|
{
|
|
GameObject.DestroyImmediate(sceneGameObject);
|
|
}
|
|
|
|
yield return base.OnTearDown();
|
|
}
|
|
|
|
void StartHost()
|
|
{
|
|
m_ServerConnectionManager.StartHostIp("server", "127.0.0.1", 9998);
|
|
}
|
|
|
|
IEnumerator ConnectClients()
|
|
{
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
m_ClientConnectionManagers[i].StartClientIp($"client{i}", "127.0.0.1", 9998);
|
|
}
|
|
|
|
yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers);
|
|
}
|
|
|
|
void SetUniqueProfilesForEachClient()
|
|
{
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var profileManager = m_ClientScopes[i].Container.Resolve<ProfileManager>();
|
|
profileManager.Profile = $"Client{i}";
|
|
}
|
|
}
|
|
|
|
void AssertHostIsListening()
|
|
{
|
|
Assert.IsTrue(m_ServerNetworkManager.IsHost && m_ServerNetworkManager.IsListening, "Host not listening.");
|
|
}
|
|
|
|
void AssertAllClientsAreConnected()
|
|
{
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
Assert.IsTrue(m_ClientNetworkManagers[i].IsConnectedClient, $"Client{i} is not connected.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void StartHost_Success()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator StartHostAndConnectClients_Success()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
yield return ConnectClients();
|
|
AssertAllClientsAreConnected();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator UserRequestedHostShutdownAfterClientsConnected_ClientsDisconnectedWithReason()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
yield return ConnectClients();
|
|
AssertAllClientsAreConnected();
|
|
|
|
var nbHostEndedSessionMsgsReceived = 0;
|
|
var subscriptions = new DisposableGroup();
|
|
|
|
for (int i = 0; i < NumberOfClients; i++)
|
|
{
|
|
m_ClientScopes[i].Container.Resolve<ISubscriber<ConnectStatus>>().Subscribe(message =>
|
|
{
|
|
// ignoring the first success message that is in the buffer
|
|
if (message != ConnectStatus.Success)
|
|
{
|
|
Assert.AreEqual(ConnectStatus.HostEndedSession, message, "Received unexpected ConnectStatus message.");
|
|
nbHostEndedSessionMsgsReceived++;
|
|
}
|
|
});
|
|
}
|
|
|
|
m_ServerConnectionManager.RequestShutdown();
|
|
|
|
yield return new WaitWhile(() => m_ServerNetworkManager.IsListening);
|
|
|
|
Assert.IsFalse(m_ServerNetworkManager.IsHost && m_ServerNetworkManager.IsListening, "Host has not properly shut down.");
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var clientId = i;
|
|
yield return new WaitWhile(() => m_ClientNetworkManagers[clientId].IsListening);
|
|
Assert.IsFalse(m_ClientNetworkManagers[clientId].IsConnectedClient);
|
|
}
|
|
|
|
Assert.AreEqual(NumberOfClients, nbHostEndedSessionMsgsReceived, "Not all clients received a HostEndedSession message.");
|
|
subscriptions.Dispose();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator AttemptingToConnectWithSamePlayerId_ClientsDisconnectedWithReason()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
var nbLoggedInAgainMsgsReceived = 0;
|
|
var subscriptions = new DisposableGroup();
|
|
|
|
// setting the same profile for all clients so that they have the same player ID
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var profileManager = m_ClientScopes[i].Container.Resolve<ProfileManager>();
|
|
profileManager.Profile = $"Client";
|
|
if (i > 0)
|
|
{
|
|
subscriptions.Add(m_ClientScopes[i].Container.Resolve<ISubscriber<ConnectStatus>>().Subscribe(message =>
|
|
{
|
|
Assert.AreEqual(ConnectStatus.LoggedInAgain, message, "Received unexpected ConnectStatus message.");
|
|
nbLoggedInAgainMsgsReceived++;
|
|
}));
|
|
}
|
|
}
|
|
|
|
yield return ConnectClients();
|
|
|
|
// The first client should be able to connect
|
|
Assert.IsTrue(m_ClientNetworkManagers[0].IsConnectedClient, "The first client is not connected.");
|
|
|
|
// Every other client should get their connection denied
|
|
for (var i = 1; i < NumberOfClients; i++)
|
|
{
|
|
Assert.IsFalse(m_ClientNetworkManagers[i].IsConnectedClient, "A client with the same player ID has connected.");
|
|
}
|
|
|
|
Assert.AreEqual(NumberOfClients - 1, nbLoggedInAgainMsgsReceived, "Not all clients received a LoggedInAgain message.");
|
|
subscriptions.Dispose();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ClientConnectingWithoutHost_ConnectionFailed()
|
|
{
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
LogAssert.Expect(LogType.Error, k_FailedToConnectToServerErrorMessage);
|
|
}
|
|
|
|
yield return ConnectClients();
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
Assert.IsFalse(m_ClientNetworkManagers[i].IsConnectedClient, $"Client{i} is connected while no server is running.");
|
|
}
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator UnexpectedClientDisconnect_ClientReconnectingSuccessfully()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
yield return ConnectClients();
|
|
AssertAllClientsAreConnected();
|
|
|
|
// wait for client synchronization to be over server-side before shutting down client
|
|
yield return null;
|
|
yield return null;
|
|
|
|
var nbReconnectingMsgsReceived = 0;
|
|
var subscriptions = new DisposableGroup();
|
|
|
|
subscriptions.Add(m_ClientScopes[0].Container.Resolve<ISubscriber<ConnectStatus>>().Subscribe(message =>
|
|
{
|
|
// ignoring the first success message that is in the buffer
|
|
if (message != ConnectStatus.Success)
|
|
{
|
|
Assert.AreEqual(ConnectStatus.Reconnecting, message, "Received unexpected ConnectStatus message.");
|
|
nbReconnectingMsgsReceived++;
|
|
}
|
|
}));
|
|
|
|
// Disconnecting the client at the transport level
|
|
m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport.DisconnectLocalClient();
|
|
|
|
// Waiting until shutdown is complete
|
|
yield return new WaitWhile(() => m_ClientNetworkManagers[0].ShutdownInProgress);
|
|
Assert.IsFalse(m_ClientNetworkManagers[0].IsConnectedClient, "Client0 has not shut down properly.");
|
|
|
|
// Waiting for client to automatically reconnect
|
|
yield return WaitForClientsConnectedOrTimeOut();
|
|
Assert.IsTrue(m_ClientNetworkManagers[0].IsConnectedClient, "Client0 failed to reconnect.");
|
|
|
|
Assert.AreEqual(1, nbReconnectingMsgsReceived, "No Reconnecting message received.");
|
|
subscriptions.Dispose();
|
|
}
|
|
|
|
#if !NETCODEFORGAMEOBJECTS_1_5_2_OR_1_6_0
|
|
[UnityTest]
|
|
public IEnumerator UnexpectedServerShutdown_ClientsFailToReconnect()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
yield return ConnectClients();
|
|
AssertAllClientsAreConnected();
|
|
|
|
var nbReconnectingMsgsReceived = 0;
|
|
var nbGenericDisconnectMsgReceived = 0;
|
|
var subscriptions = new DisposableGroup();
|
|
|
|
for (int i = 0; i < NumberOfClients; i++)
|
|
{
|
|
subscriptions.Add(m_ClientScopes[i].Container.Resolve<ISubscriber<ConnectStatus>>().Subscribe(message =>
|
|
{
|
|
// ignoring the first success message that is in the buffer
|
|
if (message != ConnectStatus.Success)
|
|
{
|
|
var possibleMessages = new List<ConnectStatus>();
|
|
possibleMessages.Add(ConnectStatus.Reconnecting);
|
|
possibleMessages.Add(ConnectStatus.GenericDisconnect);
|
|
Assert.Contains(message, possibleMessages, "Received unexpected ConnectStatus message.");
|
|
if (message == ConnectStatus.Reconnecting)
|
|
{
|
|
nbReconnectingMsgsReceived++;
|
|
}
|
|
else if (message == ConnectStatus.GenericDisconnect)
|
|
{
|
|
nbGenericDisconnectMsgReceived++;
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
// Shutting down the server
|
|
m_ServerNetworkManager.Shutdown();
|
|
yield return new WaitWhile(() => m_ServerNetworkManager.ShutdownInProgress);
|
|
Assert.IsFalse(m_ServerNetworkManager.IsListening, "Server has not shut down properly.");
|
|
|
|
// Waiting until shutdown is complete for the clients as well
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var clientId = i;
|
|
yield return new WaitWhile(() => m_ClientNetworkManagers[clientId].ShutdownInProgress);
|
|
Assert.IsFalse(m_ClientNetworkManagers[clientId].IsConnectedClient, $"Client{clientId} has not shut down properly after losing connection.");
|
|
}
|
|
|
|
var maxNbReconnectionAttempts = 0;
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var nbReconnectionAttempts = m_ClientConnectionManagers[i].NbReconnectAttempts;
|
|
maxNbReconnectionAttempts = Math.Max(maxNbReconnectionAttempts, nbReconnectionAttempts);
|
|
for (var j = 0; j < nbReconnectionAttempts; j++)
|
|
{
|
|
// Expecting this error for each reconnection attempt for each client
|
|
LogAssert.Expect(LogType.Error, k_FailedToConnectToServerErrorMessage);
|
|
}
|
|
}
|
|
|
|
// Waiting for clients to fail to automatically reconnect. We wait once for each reconnection attempt.
|
|
for (var i = 0; i < maxNbReconnectionAttempts; i++)
|
|
{
|
|
yield return WaitForClientsConnectedOrTimeOut();
|
|
for (var j = 0; j < NumberOfClients; j++)
|
|
{
|
|
Assert.IsFalse(m_ClientNetworkManagers[j].IsConnectedClient, $"Client{j} is connected while no server is running.");
|
|
}
|
|
|
|
}
|
|
|
|
Assert.AreEqual(NumberOfClients, nbReconnectingMsgsReceived, "Not all clients received a Reconnecting message.");
|
|
Assert.AreEqual(NumberOfClients, nbGenericDisconnectMsgReceived, "Not all clients received a GenericDisconnect message.");
|
|
subscriptions.Dispose();
|
|
}
|
|
#endif
|
|
|
|
[UnityTest]
|
|
public IEnumerator ClientAndHostChangingRolesBetweenSessions_Success()
|
|
{
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
yield return ConnectClients();
|
|
AssertAllClientsAreConnected();
|
|
|
|
// Requesting shutdown on the server
|
|
m_ServerConnectionManager.RequestShutdown();
|
|
yield return new WaitWhile(() => m_ServerNetworkManager.IsListening);
|
|
Assert.IsFalse(m_ServerNetworkManager.IsHost);
|
|
|
|
// Waiting until shutdown is complete for the clients
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
var clientId = i;
|
|
yield return new WaitWhile(() => m_ClientNetworkManagers[clientId].ShutdownInProgress);
|
|
Assert.IsFalse(m_ClientNetworkManagers[clientId].IsConnectedClient, $"Client{clientId} has not shut down properly after losing connection.");
|
|
}
|
|
|
|
// Switching references for Client0 and Server temporarily
|
|
(m_ServerNetworkManager, m_ClientNetworkManagers[0]) = (m_ClientNetworkManagers[0], m_ServerNetworkManager);
|
|
(m_ServerConnectionManager, m_ClientConnectionManagers[0]) = (m_ClientConnectionManagers[0], m_ServerConnectionManager);
|
|
|
|
// recreate player prefab here since the GameObject has been destroyed
|
|
CreatePlayerPrefab();
|
|
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
yield return ConnectClients();
|
|
AssertAllClientsAreConnected();
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ClientCancellingWhileConnectingToListeningServer_ConnectionCancelled()
|
|
{
|
|
StartHost();
|
|
AssertHostIsListening();
|
|
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
m_ClientConnectionManagers[i].StartClientIp($"client{i}", "127.0.0.1", 9998);
|
|
}
|
|
|
|
m_ClientConnectionManagers[0].RequestShutdown();
|
|
|
|
yield return WaitForClientsConnectedOrTimeOut();
|
|
|
|
Assert.IsFalse(m_ClientNetworkManagers[0].IsConnectedClient, "Client0 has not successfully cancelled its connection.");
|
|
|
|
for (var i = 1; i < NumberOfClients; i++)
|
|
{
|
|
Assert.IsTrue(m_ClientNetworkManagers[i].IsConnectedClient, $"Client{i} is not connected.");
|
|
}
|
|
}
|
|
|
|
[UnityTest]
|
|
public IEnumerator ClientCancellingWhileConnectingToNonExistingServer_NoConnectionError()
|
|
{
|
|
SetUniqueProfilesForEachClient();
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
m_ClientConnectionManagers[i].StartClientIp($"client{i}", "127.0.0.1", 9998);
|
|
}
|
|
|
|
m_ClientConnectionManagers[0].RequestShutdown();
|
|
|
|
for (var i = 1; i < NumberOfClients; i++)
|
|
{
|
|
// expecting that error for every client except the one that cancelled its connection
|
|
LogAssert.Expect(LogType.Error, k_FailedToConnectToServerErrorMessage);
|
|
}
|
|
|
|
yield return WaitForClientsConnectedOrTimeOut();
|
|
|
|
for (var i = 0; i < NumberOfClients; i++)
|
|
{
|
|
Assert.IsFalse(m_ClientNetworkManagers[i].IsConnectedClient, $"Client{i} is connected while no server is running.");
|
|
}
|
|
}
|
|
}
|
|
}
|