Skip to content

Commit b7993ab

Browse files
feat: hosting and disconnect smoke test for IP flow (#487)
1 parent eef5e8d commit b7993ab

10 files changed

+238
-4
lines changed

Assets/BossRoom/Scripts/Client/Game/State/ClientMainMenuState.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,14 @@ void OnAuthSignIn()
8383

8484
void OnSignInFailed()
8585
{
86-
m_LobbyButton.interactable = false;
87-
m_SignInSpinner.SetActive(false);
86+
if (m_LobbyButton)
87+
{
88+
m_LobbyButton.interactable = false;
89+
}
90+
if (m_SignInSpinner)
91+
{
92+
m_SignInSpinner.SetActive(false);
93+
}
8894
}
8995
}
9096

Assets/BossRoom/Scripts/Client/UI/IPUIMediator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ public class IPUIMediator : MonoBehaviour
2525
[SerializeField] IPHostingUI m_IPHostingUI;
2626

2727
[SerializeField] UITinter m_JoinTabButtonHighlightTinter;
28-
28+
2929
[SerializeField] UITinter m_JoinTabButtonTabBlockerTinter;
3030

3131
[SerializeField] UITinter m_HostTabButtonHighlightTinter;
32-
32+
3333
[SerializeField] UITinter m_HostTabButtonTabBlockerTinter;
3434

3535
[SerializeField] GameObject m_SignInSpinner;
@@ -38,6 +38,8 @@ public class IPUIMediator : MonoBehaviour
3838
GameNetPortal m_GameNetPortal;
3939
ClientGameNetPortal m_ClientNetPortal;
4040

41+
public IPHostingUI IPHostingUI => m_IPHostingUI;
42+
4143
[Inject]
4244
void InjectDependenciesAndInitialize(
4345
NameGenerationData nameGenerationData,

Assets/BossRoom/Scripts/Shared/ApplicationController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ private void OnDestroy()
8989
{
9090
m_LobbyServiceFacade?.EndTracking();
9191
DIScope.RootScope.Dispose();
92+
DIScope.RootScope = null;
9293
}
9394

9495
/// <summary>

Assets/BossRoom/Scripts/Shared/Infrastructure/DIScope.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static DIScope RootScope
6060

6161
return m_rootScope;
6262
}
63+
set => m_rootScope = value;
6364
}
6465

6566
readonly DisposableGroup m_DisposableGroup = new DisposableGroup();

Assets/Tests.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Tests/Runtime.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
using System;
2+
using System.Collections;
3+
using NUnit.Framework;
4+
using Unity.Multiplayer.Samples.BossRoom.Client;
5+
using Unity.Multiplayer.Samples.BossRoom.Visual;
6+
using Unity.Multiplayer.Samples.Utilities;
7+
using Unity.Netcode;
8+
using UnityEngine;
9+
using UnityEngine.SceneManagement;
10+
using UnityEngine.TestTools;
11+
12+
namespace Unity.Multiplayer.Samples.BossRoom.Tests.Runtime
13+
{
14+
public class HostAndDisconnectTest
15+
{
16+
const string k_BootstrapSceneName = "Startup";
17+
18+
const string k_MainMenuSceneName = "MainMenu";
19+
20+
const string k_CharSelectSceneName = "CharSelect";
21+
22+
const string k_BossRoomSceneName = "BossRoom";
23+
24+
static int[] s_PlayerIndices = new int[] { 0, 1, 2, 3, 4, 5, 6, 7 };
25+
26+
NetworkManager m_NetworkManager;
27+
28+
IEnumerator WaitUntilMainMenuSceneIsLoaded()
29+
{
30+
// load Bootstrap scene
31+
SceneManager.LoadSceneAsync(k_BootstrapSceneName);
32+
33+
// validate the loading of project's Bootstrap scene
34+
yield return new TestUtilities.WaitForSceneLoad(k_BootstrapSceneName);
35+
36+
// Bootstrap scene is loaded, containing NetworkManager instance; cache it
37+
m_NetworkManager = NetworkManager.Singleton;
38+
39+
Assert.That(m_NetworkManager != null);
40+
41+
// MainMenu is loaded as soon as Startup scene is launched, validate it is loaded
42+
yield return new TestUtilities.WaitForSceneLoad(k_MainMenuSceneName);
43+
44+
yield return new WaitForEndOfFrame();
45+
}
46+
47+
IEnumerator WaitUntilCharacterIsSelectedAndReady(int playerIndex)
48+
{
49+
yield return new TestUtilities.WaitForSceneLoad(k_CharSelectSceneName);
50+
51+
yield return new WaitForEndOfFrame();
52+
53+
// select a Character
54+
var seatObjectName = $"PlayerSeat ({playerIndex})";
55+
var playerSeat = GameObject.Find(seatObjectName);
56+
Assert.That(playerSeat != null, $"{seatObjectName} not found!");
57+
58+
var uiCharSelectPlayerSeat = playerSeat.GetComponent<UICharSelectPlayerSeat>();
59+
Assert.That(uiCharSelectPlayerSeat != null,
60+
$"{nameof(UICharSelectPlayerSeat)} component not found on {playerSeat}!");
61+
uiCharSelectPlayerSeat.OnClicked();
62+
63+
// selecting a class will enable the "Ready" button, next frame it is selectable
64+
yield return new WaitForEndOfFrame();
65+
66+
// hit ready
67+
ClientCharSelectState.Instance.OnPlayerClickedReady();
68+
}
69+
70+
/// <summary>
71+
/// For now, just tests that the host has entered the BossRoom scene. Can become more complex in the future
72+
/// (eg. testing networked abilities)
73+
/// </summary>
74+
/// <returns></returns>
75+
IEnumerator WaitUntilBossRoomSceneIsLoaded()
76+
{
77+
yield return TestUtilities.AssertIsNetworkSceneLoaded(k_BossRoomSceneName, m_NetworkManager.SceneManager);
78+
}
79+
80+
IEnumerator WaitUntilDisconnectedAndMainMenuSceneIsLoaded()
81+
{
82+
// once loaded into BossRoom scene, disconnect
83+
var uiSettingsCanvas = GameObject.FindObjectOfType<UISettingsCanvas>();
84+
Assert.That(uiSettingsCanvas != null, $"{nameof(UISettingsCanvas)} component not found!");
85+
uiSettingsCanvas.OnClickQuitButton();
86+
87+
yield return new WaitForFixedUpdate();
88+
89+
var uiQuitPanel = GameObject.FindObjectOfType<UIQuitPanel>(true);
90+
Assert.That(uiQuitPanel != null, $"{nameof(UIQuitPanel)} component not found!");
91+
uiQuitPanel.Quit();
92+
93+
// Netcode TODO: OnNetworkDespawn() errors pop up here
94+
// Line below should not be necessary, logged here: https://jira.unity3d.com/browse/MTT-3376
95+
yield return new WaitForSeconds(1f);
96+
97+
// wait until shutdown is complete
98+
yield return new WaitUntil(() => !m_NetworkManager.ShutdownInProgress);
99+
100+
Assert.That(!NetworkManager.Singleton.IsListening, "NetworkManager not fully shut down!");
101+
102+
// MainMenu is loaded as soon as a shutdown is encountered; validate it is loaded
103+
yield return new TestUtilities.WaitForSceneLoad(k_MainMenuSceneName);
104+
}
105+
106+
/// <summary>
107+
/// Smoke test to validating hosting inside Boss Room. The test will load the project's bootstrap scene,
108+
/// Startup, and commence the game IP flow as a host, pick and confirm a parametrized character, and jump into
109+
/// the BossRoom scene, where the test will disconnect the host.
110+
/// </summary>
111+
[UnityTest]
112+
public IEnumerator IP_HostAndDisconnect_Valid([ValueSource(nameof(s_PlayerIndices))] int playerIndex)
113+
{
114+
yield return WaitUntilMainMenuSceneIsLoaded();
115+
116+
var clientMainMenuState = GameObject.FindObjectOfType<ClientMainMenuState>();
117+
118+
Assert.That(clientMainMenuState != null, $"{nameof(clientMainMenuState)} component not found!");
119+
120+
var scope = clientMainMenuState.Scope;
121+
122+
var ipUIMediator = scope.Resolve<IPUIMediator>();
123+
Assert.That(ipUIMediator != null, $"{nameof(IPUIMediator)} component not found!");
124+
125+
var ipHostingUI = ipUIMediator.IPHostingUI;
126+
Assert.That(ipHostingUI != null, $"{nameof(IPHostingUI)} component not found!");
127+
128+
// select "DIRECT IP" button
129+
clientMainMenuState.OnDirectIPClicked();
130+
131+
yield return new WaitForEndOfFrame();
132+
133+
// select the "HOST" button
134+
ipHostingUI.OnCreateClick();
135+
136+
// confirming hosting will initialize the hosting process; next frame the results will be ready
137+
yield return new WaitForEndOfFrame();
138+
139+
// verify hosting is successful
140+
Assert.That(m_NetworkManager.IsListening && m_NetworkManager.IsHost);
141+
142+
// CharSelect is loaded as soon as hosting is successful, validate it is loaded
143+
yield return WaitUntilCharacterIsSelectedAndReady(playerIndex);
144+
145+
// selecting ready as host with no other party members will load BossRoom scene; validate it is loaded
146+
yield return WaitUntilBossRoomSceneIsLoaded();
147+
148+
// Netcode TODO: the line below prevents a NullReferenceException on NetworkSceneManager.OnSceneLoaded
149+
// Line below should not be necessary, logged here: https://jira.unity3d.com/browse/MTT-3376
150+
yield return new WaitForSeconds(2f);
151+
152+
yield return WaitUntilDisconnectedAndMainMenuSceneIsLoaded();
153+
}
154+
155+
[UnityTearDown]
156+
public IEnumerator DestroySceneGameObjects()
157+
{
158+
foreach (var sceneGameObject in GameObject.FindObjectsOfType<GameObject>())
159+
{
160+
GameObject.DestroyImmediate(sceneGameObject);
161+
}
162+
yield break;
163+
}
164+
}
165+
}

Assets/Tests/Runtime/HostAndDisconnectTest.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "Unity.Multiplayer.Samples.BossRoom.Tests.Runtime",
3+
"rootNamespace": "",
4+
"references": [
5+
"UnityEngine.TestRunner",
6+
"UnityEditor.TestRunner",
7+
"Unity.Multiplayer.Samples.Utilities.Testing",
8+
"Unity.Multiplayer.Samples.BossRoom.Client",
9+
"Unity.Multiplayer.Samples.BossRoom.Shared",
10+
"Unity.Netcode.Runtime"
11+
],
12+
"includePlatforms": [],
13+
"excludePlatforms": [],
14+
"allowUnsafeCode": false,
15+
"overrideReferences": true,
16+
"precompiledReferences": [
17+
"nunit.framework.dll"
18+
],
19+
"autoReferenced": false,
20+
"defineConstraints": [
21+
"UNITY_INCLUDE_TESTS"
22+
],
23+
"versionDefines": [],
24+
"noEngineReferences": false
25+
}

Assets/Tests/Runtime/com.unity.multiplayer.samples.bossroom.tests.runtime.asmdef.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)