test: add MTE network and multiplayer integration tests - #5325
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a testing guide and three ModuleTestingEnvironment integration tests that exercise client creation, client-side subsystem updates, and multi-client chat propagation under a listen-server network environment. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/Engine-Testing-Patterns.md`:
- Around line 79-85: The docs incorrectly state that ChatMessageEvent is a
broadcast event; update the wording to reflect that the ChatMessageEvent class
is annotated with `@OwnerEvent` (not `@BroadcastEvent`) so tests shouldn’t assume
broadcast semantics — locate references to ChatMessageEvent in the docs (e.g.,
the example using clientEntity.send(new ChatMessageEvent(...))) and change the
description to say it is owner-only/owner-annotated (`@OwnerEvent`) and not
replicated as a broadcast, and suggest using an engine-registered
`@BroadcastEvent` if true broadcast replication is required.
- Around line 98-107: The docs currently show registering a probe twice which
causes duplicate event delivery; update the example to only register the probe
via ComponentSystemManager.register (e.g., remove the
EventSystem.registerEventHandler call), and note that
ComponentSystemManager.register(probe) already registers
BaseComponentSystem-derived systems as event handlers so calling
EventSystem.registerEventHandler(probe) (as shown for MyProbe) is unnecessary
and should be omitted.
In
`@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java`:
- Around line 40-79: The test currently picks an arbitrary sender (senderClient)
from hostNetwork.getPlayers(), so client1Ctx is unused; change the sender
selection to specifically use client1Ctx: obtain the clientInfo for the local
client created in client1Ctx (via its ClientComponent.clientInfo) and then find
the matching Client in hostNetwork.getPlayers() whose
client.getEntity().getComponent(ClientComponent.class).clientInfo equals that
client1 clientInfo, assign that matching Client to senderClient, and use it when
building senderInfo and sending ChatMessageEvent so the test actually verifies
client1→client2 chat propagation.
- Around line 64-69: The ChatProbe is being registered twice which can cause
duplicate event delivery; remove the redundant direct EventSystem registration
by deleting the call to
client2Ctx.get(EventSystem.class).registerEventHandler(probe) and rely solely on
client2Csm.register(probe) (where ChatProbe is the class with `@ReceiveEvent`
handlers) so the probe is only registered once.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0953e51e-430e-4ff6-ab84-82b40ed75685
📒 Files selected for processing (4)
docs/Engine-Testing-Patterns.mdengine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientNetworkStateTest.javaengine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.javaengine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java
There was a problem hiding this comment.
♻️ Duplicate comments (2)
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java (2)
65-70:⚠️ Potential issue | 🟡 MinorRemove the duplicate event-handler registration.
client2Csm.register(probe)should be the single registration path for the component system; registering the same handler directly withEventSystemcan double-deliverChatMessageEvents.🧹 Proposed fix
ChatProbe probe = new ChatProbe(); ComponentSystemManager client2Csm = client2Ctx.get(ComponentSystemManager.class); client2Csm.register(probe); - // Also register with EventSystem for post-init registration - client2Ctx.get(EventSystem.class).registerEventHandler(probe);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java` around lines 65 - 70, The ChatProbe is being registered twice which causes duplicate event delivery; remove the direct EventSystem registration and rely solely on the component system registration. Specifically, keep the call client2Csm.register(probe) (ComponentSystemManager.register) and delete the line that calls client2Ctx.get(EventSystem.class).registerEventHandler(probe) so ChatProbe is only registered once.
72-80:⚠️ Potential issue | 🟠 MajorBind the sender to client 1 before sending the message.
Line 74 picks an arbitrary connected host client, so this may validate host-to-client delivery from either client rather than the intended client1-to-client2 path.
🎯 Proposed direction
- Client senderClient = hostNetwork.getPlayers().iterator().next(); + EntityRef client1Info = findLocalClientInfo(client1Ctx); + Client senderClient = null; + for (Client client : hostNetwork.getPlayers()) { + EntityRef candidateInfo = client.getEntity().getComponent(ClientComponent.class).clientInfo; + if (candidateInfo.equals(client1Info)) { + senderClient = client; + break; + } + } + assertNotNull(senderClient, "Client 1 should be registered on the host"); EntityRef senderInfo = senderClient.getEntity().getComponent(ClientComponent.class).clientInfo;Add a small helper in the test class:
private static EntityRef findLocalClientInfo(Context clientContext) { NetworkSystem network = clientContext.get(NetworkSystem.class); for (Client client : network.getPlayers()) { ClientComponent component = client.getEntity().getComponent(ClientComponent.class); if (component != null && component.local) { return component.clientInfo; } } throw new AssertionError("Local client info should exist"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java` around lines 72 - 80, The test currently picks an arbitrary sender from hostNetwork.getPlayers(), which can pick the wrong client; add and use a helper to find the local client's ClientComponent.clientInfo and bind the sender to client1 explicitly. Implement a private helper like findLocalClientInfo(Context clientContext) that iterates network.getPlayers(), checks ClientComponent.component.local, and returns component.clientInfo (throw an assertion if missing), then in TwoClientChatTest replace the arbitrary senderClient/senderInfo selection with a call that obtains the client1 context and uses findLocalClientInfo(client1Context) as the senderInfo when constructing ChatMessageEvent and sending to other clients.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In
`@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java`:
- Around line 65-70: The ChatProbe is being registered twice which causes
duplicate event delivery; remove the direct EventSystem registration and rely
solely on the component system registration. Specifically, keep the call
client2Csm.register(probe) (ComponentSystemManager.register) and delete the line
that calls client2Ctx.get(EventSystem.class).registerEventHandler(probe) so
ChatProbe is only registered once.
- Around line 72-80: The test currently picks an arbitrary sender from
hostNetwork.getPlayers(), which can pick the wrong client; add and use a helper
to find the local client's ClientComponent.clientInfo and bind the sender to
client1 explicitly. Implement a private helper like findLocalClientInfo(Context
clientContext) that iterates network.getPlayers(), checks
ClientComponent.component.local, and returns component.clientInfo (throw an
assertion if missing), then in TwoClientChatTest replace the arbitrary
senderClient/senderInfo selection with a call that obtains the client1 context
and uses findLocalClientInfo(client1Context) as the senderInfo when constructing
ChatMessageEvent and sending to other clients.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f0f5b89a-67a5-46b6-9fac-e9a1d66b87a2
📒 Files selected for processing (3)
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientNetworkStateTest.javaengine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.javaengine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
- engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.java
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java (1)
25-30:⚠️ Potential issue | 🟡 MinorBind the sender, or narrow the test name/scope.
Line 70 still picks an arbitrary host-side client, so the sender can be the same client as the probed receiver. Either derive the sender from the first created client context, or rename the test/Javadoc to describe host-to-client delivery rather than “between clients.”
Also applies to: 40-40, 70-75
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java` around lines 25 - 30, The test currently chooses an arbitrary host-side client as the sender, which allows the sender and probed receiver to be the same; update TwoClientChatTest so the host-side sender is explicitly derived from the first created client context (instead of the arbitrary pick used when creating the ChatMessageEvent) — locate the code that selects the host-side client entity for sending the ChatMessageEvent and bind it to the first client context created in the test setup (or alternatively change the test/Javadoc/test name to indicate this is host-to-client delivery rather than “between clients”); ensure references to ChatMessageEvent and the LISTEN_SERVER verification use the newly bound sender.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java`:
- Around line 78-80: The test sets a too-short real-time safety cap which can
trigger MainLoop.runWhile() safety timeout before the intended game-time wait;
update the test around helper.setSafetyTimeoutMs(...) and helper.runUntil(...)
so the safety timeout is not shorter than the normal propagation wait (e.g.,
remove the 10000ms override or increase it to a larger value such as 60000ms),
ensuring probe.received has time to become true without hitting
MainLoop.runWhile()'s safety timeout.
---
Duplicate comments:
In
`@engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java`:
- Around line 25-30: The test currently chooses an arbitrary host-side client as
the sender, which allows the sender and probed receiver to be the same; update
TwoClientChatTest so the host-side sender is explicitly derived from the first
created client context (instead of the arbitrary pick used when creating the
ChatMessageEvent) — locate the code that selects the host-side client entity for
sending the ChatMessageEvent and bind it to the first client context created in
the test setup (or alternatively change the test/Javadoc/test name to indicate
this is host-to-client delivery rather than “between clients”); ensure
references to ChatMessageEvent and the LISTEN_SERVER verification use the newly
bound sender.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7201417b-5195-42ac-844b-5cd689e34d8c
📒 Files selected for processing (2)
docs/Engine-Testing-Patterns.mdengine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java
✅ Files skipped from review due to trivial changes (1)
- docs/Engine-Testing-Patterns.md
There was a problem hiding this comment.
Pull request overview
Adds new ModuleTestingEnvironment (MTE) multiplayer/network integration tests and accompanying documentation to help validate and guide engine-level network behavior under headless listen-server scenarios.
Changes:
- Add 3 MTE integration tests covering client network state, client update ticks, and two-client chat event propagation.
- Add new documentation page describing MTE testing patterns and common pitfalls for network event replication.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java | Adds a two-client listen-server integration test for chat event replication using ChatMessageEvent. |
| engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.java | Adds a client-side integration test ensuring UpdateSubscriberSystem receives ticks in the MTE loop. |
| engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientNetworkStateTest.java | Adds client integration tests validating NetworkSystem mode and presence of NetworkSubsystem. |
| docs/Engine-Testing-Patterns.md | Documents engine/MTE testing patterns, network event registration gotchas, and isolation guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1692754 to
75574a7
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
engine-tests/src/main/java/org/terasology/engine/integrationenvironment/ModuleTestingEnvironment.java:64
- Same as the overload above: this overload can also throw an
UncheckedTimeoutExceptionif the safety timeout is exceeded while waiting. Documenting that here makes the failure modes clearer.
* @param gameTimeTimeoutMs how long to wait, in game time
* @param description what is being waited for, phrased to read after "timed out waiting for"
* @throws AssertionError if the condition does not hold within {@code gameTimeTimeoutMs} of game time
*/
void awaitUntil(long gameTimeTimeoutMs, String description, Supplier<Boolean> condition);
| * @param description what is being waited for, phrased to read after "timed out waiting for" | ||
| * @throws AssertionError if the condition does not hold within DEFAULT_GAME_TIME_TIMEOUT of game time |
| * @param description what is being waited for, phrased to read after "timed out waiting for" | ||
| * @throws AssertionError if the condition does not hold within DEFAULT_GAME_TIME_TIMEOUT of game time | ||
| */ | ||
| void awaitUntil(String description, Supplier<Boolean> condition); |
75574a7 to
5f9d680
Compare
Salvaged and cleaned up from PR MovingBlocks#5304 (Antigravity DI exploration). All debug logging removed, imports cleaned, tests restructured. - ClientNetworkStateTest: verifies client NetworkSystem mode and NetworkSubsystem presence - ClientSystemUpdateTest: registers a system on a client and verifies it receives update ticks through the MTE game loop - TwoClientChatTest: two-client chat propagation via ChatMessageEvent broadcast through the LISTEN_SERVER network stack Also adds Engine-Testing-Patterns.md documenting MTE patterns, the network event registration problem (inner-class events don't replicate), context isolation, and Gradle execution tips. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the PathManager/CoreRegistry singleton save/restore pattern, the @tempdir cleanup ordering gotcha, and why missing tearDown causes non-deterministic cross-class test failures in CI. Prompted by a real CI failure: PathManagerTest mutated PathManager without restoring, causing ServerConnectListManagerTest to fail with NoSuchFileException on Jenkins but not locally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All tests that call helper.createClient() are subject to connection timeouts in resource-constrained CI environments. This matches the existing pattern in ExampleTest where all client-creating methods are tagged @tag("flaky"). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… registration - ChatMessageEvent is @OwnerEvent, not @BroadcastEvent — fix docs and test Javadoc - Remove redundant EventSystem.registerEventHandler() call in test and docs example — ComponentSystemManager.register() already handles it - Remove unused client1Ctx variable and unused EventSystem import - Fix test message to reflect what's actually tested (host-to-client, not client1-to-client2) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ng diagnostics - Match client 1 to its host-side representation via ClientComponent entity ID so the sender identity is explicit, not arbitrary - Bump safety timeout from 10s to 45s — test takes ~27s locally, 10s was too low for CI - Add SLF4J timing logs at each milestone (client connect, registration, send, receive) to help diagnose flaky CI failures - Fix Javadoc to describe @OwnerEvent semantics correctly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tics TwoClientChatTest: remove fragile cross-engine entity ID matching (IDs aren't stable across engines), dead permission-grant block, and the lowered safety timeout — use the 60s default instead. ClientNetworkStateTest: use assertInstanceOf instead of bare cast. All three tests: add JUnit 5 TestReporter.publishEntry() for structured timing data (client_connect_ms, total_ms, etc.) that appears in JUnit XML reports for Jenkins API access. Engine-Testing-Patterns.md: add TestReporter diagnostics section, fix NetworkMode qualifier in table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…erns Two sections written alongside this branch's earlier work but never committed, recovered from a stash. **Test Context Pattern** documents the arrangement that keeps `@BeforeAll`-built environments from leaking across test methods: hold a stable `baseContext`, build each test's context as a child of *that* rather than of the previous test's context, point `CoreRegistry` at the per-test context, and register loaded instances rather than lazy constructors. Includes the chaining anti-pattern it replaces, which is worth showing because the broken version looks reasonable. This is the same pattern MovingBlocks#5323 applies to `TerasologyTestingEnvironment`. Documenting it here rather than there because this branch already owns the file, and the pattern generalises past the one class that motivated it. **DI Patterns** is a living reference harvested during the Gestalt DI migration: constructor injection over field injection, `@In` as legacy *except* in MTE test classes where the harness requires it, never injecting `Context` itself, `ServiceRegistry` during registration and `ImmutableContextImpl` afterwards, and the duplicate-bean-key trap when explicitly mapping an interface that Gestalt already auto-maps. Points at reference implementations rather than restating them, so it ages by going stale rather than by going wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`runUntil` reports a timeout by returning `true`. That is easy to read backwards — `if (runUntil(cond))` looks like it means the condition was met — and easier still to drop, because nothing forces you to look at it. A dropped timeout is silent: the test carries on and fails at a later assertion with nothing indicating the wait was the problem. Only the *safety* timeout throws; the game-time timeout does not. `MainLoop` already carries a TODO calling this "a weird interface due for a revision". Every wait in the tests added on this branch had exactly that shape: ```java helper.runUntil(() -> probe.received); // return value discarded assertThat(probe.received).isTrue(); // where it actually fails ``` Adds `awaitUntil(description, condition)` and a timeout-taking overload, which throw an `AssertionError` naming what was awaited, the game time and real time elapsed, and the limit that was hit. Converted the waits in `TwoClientChatTest`, `ClientSystemUpdateTest` and `ExampleTest` to use it. `ExampleTest` is not new on this branch, but it is the canonical MTE demo — the testing docs point newcomers at it — so leaving the ignorable pattern there guarantees it keeps being copied. `runUntil` and `runWhile` are unchanged and stay public; a caller that genuinely wants to branch on the outcome still can. This is additive, so nothing outside the engine breaks: modules implementing `ModuleTestingEnvironment` would need the new methods, but the interface is implemented in-tree by `ModuleTestingHelper` and consumed elsewhere, not implemented. Two tests cover the new method — that it returns quietly when the condition already holds, and that a timeout names what it was waiting for. The second uses a short game-time timeout deliberately, to exercise the game-time path rather than the safety timeout, since that is the one that previously failed silently. Doc section added to `Engine-Testing-Patterns.md` covering both helpers, why to prefer `awaitUntil`, and the distinction between the game-time and safety timeouts, which is not obvious from the method names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ng out
`createClient` is the most common source of intermittent MTE failures, and it was reporting them in the least useful way available:
```java
if (!connectToHost(client, mainLoop)) {
throw new RuntimeException(String.format("Could not connect client %s to local host - timeout.", client));
}
```
Two problems. `connectToHost` ended in `return !mainLoop.runUntil(...)` — the same inverted-boolean shape `awaitUntil` was added to remove, where a timeout is a return value rather than a failure. And the `JoinStatus` returned by `NetworkSystem.join` was assigned, handed to `StateLoading`, and then dropped, so everything that could explain the failure was thrown away before the exception was built.
The result was that a join the host actively **refused** and a handshake that was merely **slow** produced byte-identical messages, with no indication of where it stalled. That is bearable locally, where you can re-run and watch. In CI it means a flaky failure is undiagnosable after the fact — which is exactly where it happens.
`connectToHost` now waits via `awaitUntil` and, on timeout, throws with the client's state at the point of abandonment plus the `JoinStatus`: its status, current activity, progress, and error message when present.
```
Could not connect client TerasologyEngine@1d3aefd to local host. Client state
when we gave up: StateLoading; join status IN_PROGRESS during 'Awaiting
Connection...' (0%)
```
That distinction is actionable rather than decorative: `FAILED` with an error means the host refused and waiting longer would never have helped, while `IN_PROGRESS` points at timing or a stalled loading step and names which one. The `awaitUntil` `AssertionError` is kept as the cause, so the elapsed game and real time are still in the trace.
`connectToHost` returns void now instead of a boolean nobody could use correctly; it has one caller. The TODO about making this asynchronous is left alone — that is a real redesign, and this is about making the current behaviour legible.
Prompted by a flaky failure of `ExampleTest.testSendEvent` on MovingBlocks#5323's Jenkins run, whose entire report was "Could not connect client ... - timeout."
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5f9d680 to
e69b070
Compare
| * @param mainLoop | ||
| * | ||
| * return true if the connection to the host was successful and the client is in state <i>in-game</i>. | ||
| */ | ||
| boolean connectToHost(TerasologyEngine client, MainLoop mainLoop) { | ||
| void connectToHost(TerasologyEngine client, MainLoop mainLoop) { |
…tine filesystem side effects Leftovers from the reviews of #5323 and #5325 that were not worth another cycle on those PRs. **`connectToHost` javadoc (Copilot, #5325).** The doc still promised a boolean after the method became void. It was already stale before that - a bare `@param mainLoop` with no text, and a `return` line that was never a `@return` tag, so javadoc was not rendering it anyway. Rewritten to describe what the method does and what it throws, including that the message distinguishes a refused join from a slow one. **PMD `GuardLogStatement` in `TwoClientChatTest`.** Two `logger.info` calls computed `System.currentTimeMillis() - startTime` inside the argument list, which is the work the rule exists to stop. Hoisted both into locals, matching what the rest of the method already does for `client1Ms`, `registeredMs` and `totalMs`. That also fixes a small inconsistency: `messages_sent_ms` was published from one `System.currentTimeMillis()` call and logged from a second, so the reported metric and the log line could disagree by a millisecond. They now come from the same value. **Quarantined the one test that writes outside a `@TempDir`.** `useDefaultHomePathWindows` creates a real `Terasology` folder in the developer's own Saved Games directory, because the Windows home lookup goes through the OS rather than `user.home` and cannot be redirected the way the Mac and Linux cases are. Copilot flagged it on #5323 and it was already noted as wanting a tag group. Tagged `filesystem-side-effects` and excluded from `test` and `unitTest`, with a new opt-in `filesystemSideEffectTest` task for running it deliberately. Worth noting the effect is the opposite of what a tag usually buys: the test is `@EnabledOnOs(WINDOWS)`, so it is skipped wherever the CI agent is not Windows and in practice only ever ran on developer machines - the people it inconveniences. Opt-in is the right default here rather than a CI-only group. Verified both directions on Windows, where it actually matters: `unitTest` no longer lists the test at all (the tag filter drops it before `@EnabledOnOs` is evaluated), and `filesystemSideEffectTest` runs exactly that one test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y MTE tests Two small leftovers that were being tracked separately and are cheaper together than as their own PRs. **Deprecated `addEntityManagementRelatedClasses(Context, ServiceRegistry)`.** The overload ignores the context it is handed and delegates to the single-argument form, so passing a context implies a relationship that does not exist and makes it harder to reason about which context the registrations apply to. Copilot raised this on #5323 against the one call site that PR touched; the same call shape was left in three other places. All three now call the non-deprecated overload. The deprecated method itself stays. It is public on an engine class and modules may call it, so removing it is a separate decision from stopping our own use of it. With these three gone it has no remaining in-tree callers, which is the state it should be in before anyone proposes deleting it. **Timing diagnostics on `ExampleTest`.** `TestReporter.publishEntry` was added to the integration tests introduced in #5325 so CI timing is queryable through the Jenkins API rather than only readable in logs. The pre-existing `@Tag("flaky")` tests never got the same treatment, which is backwards - those are the ones whose timing anyone actually wants when they fail. `testClientCreation`, `testClientConnection` and `testSendEvent` now publish how long client connection took. All three are flaky-tagged, and all three flake in the same place: waiting for a client to reach in-game. `testSendEvent` in particular failed this way on #5323's Jenkins run with no timing recorded, which is what prompted this. Deliberately not touching `DelayManagerTest`, the other test named alongside `ExampleTest` for this retrofit: it lives in the separate ModuleTestingEnvironment module repository and rides that repo's own change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
docs/Engine-Testing-Patterns.mddocumenting MTE testing patterns, including the network event registration problem (inner-class@BroadcastEvent/@OwnerEvent/@ServerEventevents don't replicate) and context isolationTest plan
ClientNetworkStateTest— verifies client NetworkSystem mode (CLIENT) and NetworkSubsystem presenceClientSystemUpdateTest— registers UpdateSubscriberSystem on client, verifies it receives ticksTwoClientChatTest— two clients connect, chat message broadcast via ChatMessageEvent propagates between themRelated
NetworkEventSystemWrappedfix from the DI merge (b353187)