Skip to content

Commit 82bbda0

Browse files
chore: enable unified hybrid tests (#4122)
* update - ci Adding an additional unified hybrid spawning test pass. * fix Fixed: - Issue with NetworkObject throwing an exception during hybrid integration test. - Issue with setting the active world prior to spawning a hybrid prefab during integration tests. - Issue with UnifiedNetcodeUpdateSystem not overriding the OnCreate method along with checks for a valid NetworkManager and/or transport within OnUpdate. - Issues (minor) with the original UnifiedNetworkTransformTest. - Adjustments to the NetcodeIntegrationTest that assures the correct active world is assigned when spawning. * update Adding the modified NetcodeConfig specific to NGO. Adding the unified manifest file for running unified tests. * update Unified specific settings * fix Formatting of the unified test filter. * update One more time... just running the UnifiedNetworkTransformTest for this phase. A more integrated one will be coming in the next PR for this effort. * Apply suggestions from code review Co-authored-by: Emma <emma.mcmillan@unity3d.com> * Update .yamato/_triggers.yml Co-authored-by: Emma <emma.mcmillan@unity3d.com> * Update TODO comment for Netcode singleton issue * Update unified-tests.yml to simplify commands Removed command to delete packages-lock.json from unified tests. --------- Co-authored-by: Emma <emma.mcmillan@unity3d.com>
1 parent 6ec9bac commit 82bbda0

13 files changed

Lines changed: 361 additions & 36 deletions

File tree

.yamato/_run-all.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,21 @@ run_all_project_tests_console_standalone_default:
368368
{% endfor -%}
369369

370370

371+
# Runs all unified (NGO + N4E) tests
372+
# These run on their own pinned editor (unified_editors) rather than the validation_editors, because
373+
# they need an editor that bundles a com.unity.netcode with the unified API. See unified-tests.yml.
374+
run_all_unified_tests:
375+
name: Run All Unified Tests
376+
dependencies:
377+
{% for project in projects.default -%}
378+
{% for platform in unified_test_platforms -%}
379+
{% for editor in unified_editors.default -%}
380+
- .yamato/unified-tests.yml#unified_test_{{ project.name }}_{{ platform.name }}_{{ editor }}
381+
{% endfor -%}
382+
{% endfor -%}
383+
{% endfor -%}
384+
385+
371386
# Runs all CMB service tests
372387
run_all_project_tests_cmb_service:
373388
name: Run All CMB Service Tests

.yamato/_triggers.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ pr_code_changes_checks:
9090
# Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations
9191
- .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }}
9292
- .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }}
93+
- .yamato/_run-all.yml#run_all_unified_tests
9394
# Run code coverage test (PRs use the pinned "safe" trunk)
9495
- .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_{{ pinnedTrunk }}
9596
triggers:
@@ -120,6 +121,19 @@ pr_code_changes_checks:
120121

121122

122123

124+
# Unified (NGO + N4E) validation, on demand.
125+
# This job allows the Unified tests to be kicked off by commenting "/ci unified".
126+
# This is useful for PRs where pr_code_changes_checks doesn't trigger.
127+
unified_pr_checks:
128+
name: Unified (NGO + N4E) checks [on demand]
129+
dependencies:
130+
- .yamato/_run-all.yml#run_all_unified_tests
131+
triggers:
132+
expression: |-
133+
pull_request.comment eq "unified"
134+
cancel_old_ci: true
135+
136+
123137
# Run all tests on nightly basis.
124138
# Same subset as pull_request_trigger with addition of mobile/desktop/console tests and webgl builds
125139
# Those tests are all running on trunk and the default editor (since it's daily and running all of them would add a lot of overhead)
@@ -156,6 +170,8 @@ develop_nightly:
156170
# Run Runtime tests against cmb service on trunk and default editors
157171
- .yamato/_run-all.yml#run_all_project_tests_cmb_service_trunk
158172
- .yamato/_run-all.yml#run_all_project_tests_cmb_service_default
173+
# Run the unified (NGO + N4E) tests on their own pinned editor (see .yamato/unified-tests.yml)
174+
- .yamato/_run-all.yml#run_all_unified_tests
159175
# Build player for webgl platform on trunk and default editors
160176
- .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_ubuntu_trunk
161177
- .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_{{ validation_editors.default }}

.yamato/project.metafile

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,55 @@ validation_editors:
190190
pinnedTrunk: 5fe7931aab8c4fff9274e15ef0800125c68b8d6a
191191

192192

193+
# UNIFIED (NGO + N4E) CONFIGURATION---------------------------------------------------------------------
194+
# The unified test job validates NGO running against Netcode for Entities (N4E) through the unified API.
195+
# It is deliberately kept separate from every other job in this repo because it needs an editor that
196+
# bundles com.unity.netcode with the unified API, and that editor is NOT one of the validation_editors.
197+
#
198+
# WHY THIS IS PINNED TO 6000.7.0a5 (and not 6000.7.0a2):
199+
# UnifiedNetcodeTransport is compiled behind "#if UNIFIED_NETCODE && OUT_OF_BAND_RPC" and needs the
200+
# N4E out-of-band RPC API (IOutOfBandRpcCommand / OutgoingOutOfBandRpcDataStreamBuffer).
201+
# Both 6000.7.0a2 and 6000.7.0a5 bundle com.unity.netcode 6.7.0, but that API only landed in the a5
202+
# snapshot. On a2 the unified transport does not compile, so the unified tests cannot run there.
203+
# Bump this pin (and unified_netcode_version below) together whenever the required N4E API moves.
204+
#
205+
# An explicit alpha version is used instead of a trunk revision hash because published alphas are
206+
# already immutable, so there is nothing to pin against.
207+
unified_editors:
208+
default:
209+
- 6000.7.0a5
210+
211+
# Version of com.unity.netcode (N4E) bundled with unified_editors.default.
212+
# It resolves as a "builtin" package out of the editor install, so it must match the editor exactly.
213+
unified_netcode_version: 6.7.0
214+
215+
# The unified job runs in Editor context only, so a single fast platform is enough.
216+
unified_test_platforms:
217+
- name: ubuntu
218+
type: Unity::VM
219+
image: package-ci/ubuntu-22.04:v4.87.0
220+
flavor: b1.large
221+
222+
# Restricts the unified job to unified-only tests so nothing else runs on the alpha editor.
223+
#
224+
# This is a REGULAR EXPRESSION, not a glob. UTR passes it to the editor as -testFilter, which ends up
225+
# in UnityEngine.TestRunner's FullNameFilter -> NUnit ValueMatchFilter -> new Regex(pattern).IsMatch().
226+
# A glob-style "*Unified*" throws "Quantifier {x,y} following nothing" and fails the whole run.
227+
# The other jobs' "Unity.Netcode.RuntimeTests.*" works because it is also a valid regex - it just
228+
# happens to read like a glob.
229+
#
230+
# The match is against the NUnit *full* test name, which includes both fixture and method arguments.
231+
#
232+
# DO NOT widen this to ".*Unified.*". "Unified" appearing in a test name does NOT mean that test was
233+
# deliberately converted to run against hybrid prefabs. HostOrServer gained UnifiedServer/UnifiedHost
234+
# members under UNIFIED_NETCODE, and NUnit expands a bare [Values] on an enum parameter (and
235+
# enum-typed fixture constructors) to every member. So with N4E installed, much of the existing suite
236+
# silently grows unified cases - e.g. NetworkVariableTests(Default).AllNetworkVariableTypes(UnifiedHost),
237+
# which nobody wrote. ".*Unified.*" selected 138 tests, 84 of which failed, against the 1 test this
238+
# job exists to validate. Deciding which of those should pass, and how they opt in, is Goal-2.
239+
unified_test_filter: ".*UnifiedNetworkTransformTest.*"
240+
241+
193242
# Scripting backends used by Standalone RunTimeTests---------------------------------------------------
194243

195244
scripting_backends:

.yamato/unified-tests.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{% metadata_file .yamato/project.metafile %} # All configuration that is used to create different configurations (used in for loops) is taken from this file.
2+
---
3+
4+
# DESCRIPTION--------------------------------------------------------------------------
5+
# This job validates NGO running against Netcode for Entities (N4E) through the unified API.
6+
# A "hybrid prefab" is an NGO prefab (NetworkObject at the root) that also carries a GhostObject.
7+
# When one or more hybrid prefabs are in the NetworkManager's prefab list, NGO hands transform
8+
# synchronization to N4E's snapshot system and tunnels its own batched messages over
9+
# UnifiedNetcodeTransport (N4E's out-of-band RPC) instead of using a NetworkTransport such as UTP.
10+
# UnifiedNetworkTransformTest is the validation test that this path works end to end.
11+
12+
# WHY THIS JOB IS SEPARATE FROM EVERY OTHER JOB------------------------------------------
13+
# 1. It needs an editor that bundles a com.unity.netcode with the unified API. That editor
14+
# (unified_editors.default in project.metafile) is not one of the validation_editors, and NGO
15+
# still has to keep building and testing against editors that have no unified API at all.
16+
# 2. It needs com.unity.netcode in the testproject so that the UNIFIED_NETCODE define is set
17+
# (see the versionDefines in Unity.Netcode.Runtime.asmdef). The committed
18+
# testproject/Packages/manifest.json deliberately does NOT reference it, so this job swaps in
19+
# testproject/Packages/manifest-unified.json instead.
20+
# 3. It is wired into pr_code_changes_checks and develop_nightly the same way the CMB service tests
21+
# are, so it runs automatically on PRs targeting develop / develop-3.x.x / release. Be aware that
22+
# this makes an unsupported alpha editor part of the PR gate: when N4E lands breaking changes in
23+
# trunk this job goes red and the pin has to be bumped to unblock PRs.
24+
# _triggers.yml also has unified_pr_checks, so it can be kicked off with "/ci unified" on PRs
25+
# that pr_code_changes_checks does not cover.
26+
27+
# CONFIGURATION STRUCTURE--------------------------------------------------------------
28+
# Jobs are generated using nested loops:
29+
# 1. For all unified test platforms (currently Ubuntu only, see project.metafile)
30+
# 2. For all unified editors (currently a single pinned alpha, see project.metafile)
31+
32+
# TECHNICAL CONSIDERATIONS---------------------------------------------------------------
33+
# This job runs in Editor context only (no player builds required), like project-tests.yml.
34+
# Only playmode is run: every unified test is an integration test and there are no unified EditMode tests.
35+
# The run is restricted to unified tests via unified_test_filter so that nothing else is exercised
36+
# on the alpha editor. Non-unified tests are covered by the regular jobs on the supported editors.
37+
# packages-lock.json is removed because the committed lock was resolved against a much older editor
38+
# and its builtin package versions do not exist in the unified editor.
39+
40+
# QUALITY CONSIDERATIONS--------------------------------------------------------------------
41+
# TODO: the manifest swap means testproject/Packages/manifest-unified.json has to be kept in sync
42+
# with manifest.json by hand. Revisit once N4E is a hard dependency of NGO and one manifest
43+
# can cover both cases.
44+
# TODO: unified_test_filter is a test-name pattern. Replacing it with an NUnit category (for
45+
# example [Category("Unified")]) would be less fragile once more fixtures gain unified variants.
46+
47+
#------------------------------------------------------------------------------------
48+
49+
{% for project in projects.default -%}
50+
{% for platform in unified_test_platforms -%}
51+
{% for editor in unified_editors.default -%}
52+
unified_test_{{ project.name }}_{{ platform.name }}_{{ editor }}:
53+
name : Unified Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}]
54+
agent:
55+
type: {{ platform.type }}
56+
image: {{ platform.image }}
57+
flavor: {{ platform.flavor }}
58+
{% if platform.model %}
59+
model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile)
60+
{% endif %}
61+
commands:
62+
# Swap in the manifest that pulls in com.unity.netcode (N4E). This is what causes UNIFIED_NETCODE
63+
# to be defined and therefore what makes the unified tests compile at all.
64+
- cp {{ project.path }}/Packages/manifest-unified.json {{ project.path }}/Packages/manifest.json
65+
- unity-downloader-cli --fast --wait -u {{ editor }} -c Editor # Installing basic editor for tests execution
66+
- UnifiedTestRunner --testproject={{ project.path }} --suite=playmode --artifacts-path=test-results --editor-location=.Editor --testfilter="{{ unified_test_filter }}" --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --timeout={{ test_timeout }}
67+
artifacts:
68+
logs:
69+
paths:
70+
- "test-results/**/*"
71+
dependencies:
72+
- .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors
73+
{% endfor -%}
74+
{% endfor -%}
75+
{% endfor -%}

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3968,6 +3968,17 @@ private void Start()
39683968

39693969
private void InitGhost()
39703970
{
3971+
// Note: If hybrid prefabs are created prior to any NetworkManager instances,
3972+
// then the next line throws and exception. This avoids that issue.
3973+
// We might come up with some global way to verify if we are running integration
3974+
// tests and add additional logic within to determine if we should log an error
3975+
// or not.
3976+
if (NetworkManager == null)
3977+
{
3978+
return;
3979+
}
3980+
3981+
39713982
if (!NetworkManager.IsListening)
39723983
{
39733984
if (NetworkManager.LogLevel == LogLevel.Developer)

com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,11 @@ public void OnUpdate(ref SystemState state)
124124
[UpdateBefore(typeof(RpcSystem))]
125125
internal partial class UnifiedNetcodeUpdateSystem : SystemBase
126126
{
127-
public void OnCreate(ref SystemState state)
127+
protected override void OnCreate()
128128
{
129-
state.RequireForUpdate<RpcCollection>();
130-
state.RequireForUpdate<NetworkId>();
129+
RequireForUpdate<RpcCollection>();
130+
RequireForUpdate<NetworkId>();
131+
base.OnCreate();
131132
}
132133

133134
public UnifiedNetcodeTransport Transport;
@@ -155,6 +156,11 @@ public void SendRpc(TransportRpc rpc, Entity connectionEntity)
155156

156157
protected override void OnUpdate()
157158
{
159+
if (NetworkManager == null || Transport == null)
160+
{
161+
return;
162+
}
163+
158164
NetworkManager.MessageManager.ProcessSendQueues();
159165

160166
using var commandBuffer = new EntityCommandBuffer(Allocator.Temp);

com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#if UNIFIED_NETCODE
22
using System.Collections;
3+
using System.Text;
34
using NUnit.Framework;
45
using Unity.Netcode.Components;
56
using Unity.Netcode.TestHelpers.Runtime;
@@ -52,38 +53,45 @@ protected override IEnumerator OnSetup()
5253
return base.OnSetup();
5354
}
5455

56+
private bool ValidatePosition(StringBuilder errorLog)
57+
{
58+
var authorityPosition = m_AuthorityInstanceTransform.position;
59+
foreach (var client in m_ClientNetworkManagers)
60+
{
61+
var instancePosition = client.SpawnManager.SpawnedObjects[m_Instance.NetworkObjectId].transform.position;
62+
if (!Approximately(authorityPosition, instancePosition))
63+
{
64+
errorLog.AppendLine($"[Client-{client.LocalClientId}] Clone instance position {GetVector3Values(instancePosition)} " +
65+
$"does not equal the authority's {GetVector3Values(authorityPosition)}");
66+
}
67+
}
68+
return errorLog.Length == 0;
69+
}
70+
71+
private Transform m_AuthorityInstanceTransform;
72+
5573
[UnityTest]
5674
public IEnumerator BasicMovementTest()
5775
{
5876
var authority = GetAuthorityNetworkManager();
5977
m_Instance = SpawnObject(m_Prefab, m_ServerNetworkManager).GetComponent<NetworkObject>();
60-
61-
// Wait 5 seconds so we will dump any deferred messages if it failed on clients
62-
// when checking to see if it spawned or not on the clients next.
63-
// Enable this to debug deferred
64-
//yield return new WaitForSeconds(5);
78+
m_AuthorityInstanceTransform = m_Instance.transform;
6579

6680
yield return WaitForSpawnedOnAllOrTimeOut(m_Instance);
6781
AssertOnTimeout($"Failed to spawn {m_Instance.name} on all clients!");
6882

6983
VerboseDebug("All clients spawned instance!");
7084

85+
yield return WaitForConditionOrTimeOut(ValidatePosition);
86+
AssertOnTimeout($"A client failed to synchronize the position on the initial spawn!");
87+
7188
var originalPos = authority.LocalClient.PlayerObject.transform.position;
7289
var newPos = originalPos + new Vector3(1, 1, 1);
7390

7491
m_Instance.transform.position = newPos;
7592

76-
foreach (var client in m_ClientNetworkManagers)
77-
{
78-
Assert.IsTrue(Approximately(originalPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position));
79-
}
80-
81-
yield return new WaitForSeconds(1);
82-
83-
foreach (var client in m_ClientNetworkManagers)
84-
{
85-
Assert.IsTrue(Approximately(newPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position));
86-
}
93+
yield return WaitForConditionOrTimeOut(ValidatePosition);
94+
AssertOnTimeout($"A client failed to synchronize the changed position: {GetVector3Values(newPos)}!");
8795
VerboseDebug("Test Passed!");
8896
}
8997
}

com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,13 +2436,9 @@ protected GameObject CreateHybridPrefab(string baseName, bool moveToDDOL = true)
24362436
// - Then set it back to active.
24372437
gameObject.SetActive(false);
24382438
var adapter = gameObject.AddComponent<GhostObject>();
2439-
// Mark the reference as post processing to avoid registering this instance automatically.
2440-
GhostPrefabReference.s_IsPostProcessing = true;
2441-
adapter.prefabReference = ScriptableObject.CreateInstance<GhostPrefabReference>();
2442-
adapter.prefabReference.name = "GhostPrefabReference";
2443-
adapter.prefabReference.Prefab = gameObject;
24442439

2445-
GhostPrefabReference.s_IsPostProcessing = false;
2440+
// Initialize it as a prefab
2441+
adapter.InitializeAsPrefab();
24462442

24472443
// TODO: This might be part of the CreateHybridPrefab parameters
24482444
// For now, just use normal interpolation until we get integration
@@ -2610,6 +2606,20 @@ protected void SpawnObjectInstance(NetworkObject networkObjectToSpawn, NetworkMa
26102606
private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager owner, bool destroyWithScene = false, bool isPlayerObject = false)
26112607
{
26122608
Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash > 0, $"{nameof(GameObject)} {prefabNetworkObject.name} has a {nameof(NetworkObject.GlobalObjectIdHash)} value of 0! Make sure to make it a valid prefab before trying to spawn!");
2609+
#if UNIFIED_NETCODE
2610+
// This has to happen *before* Instantiate, not after. The hybrid prefab is active, so the clone's
2611+
// GhostObject.Awake runs synchronously inside Object.Instantiate below. The clone is not a prefab
2612+
// (its prefabReference.Prefab points at the prefab, not at itself), so Awake acquires an entity
2613+
// reference, which resolves the world to spawn into from the Netcode.Instance.m_ActiveWorld singleton.
2614+
// N4E's rate managers reassign that singleton on every world update, so by the time a test body runs
2615+
// it points at whichever world updated last - typically a client world - and the spawn is rejected with
2616+
// "You can only spawn a ghost on a server or during prediction on a client."
2617+
// TODO-UNIFIED: NetCode.Netcode.Instance is a singleton and might cause issues assigning this.
2618+
if (prefabNetworkObject.HasGhost)
2619+
{
2620+
NetCode.Netcode.Instance.m_ActiveWorld = m_ServerNetworkManager.NetcodeWorld;
2621+
}
2622+
#endif
26132623
var newInstance = Object.Instantiate(prefabNetworkObject.gameObject);
26142624
var networkObjectToSpawn = newInstance.GetComponent<NetworkObject>();
26152625
SpawnObjectInstance(networkObjectToSpawn, owner, destroyWithScene, isPlayerObject);

0 commit comments

Comments
 (0)