Skip to content

test: add MTE network and multiplayer integration tests#5325

Open
agent-refr wants to merge 7 commits into
MovingBlocks:developfrom
SiliconSaga:test/salvage-mte-network-tests
Open

test: add MTE network and multiplayer integration tests#5325
agent-refr wants to merge 7 commits into
MovingBlocks:developfrom
SiliconSaga:test/salvage-mte-network-tests

Conversation

@agent-refr
Copy link
Copy Markdown
Collaborator

AI-assisted change proposal. Filed by agent driven by @Cervator via GDD.

Summary

  • Add 3 MTE integration tests salvaged from PR Antigravity take on the Gestalt DI PR - fixes multiplayer! #5304 (Antigravity DI exploration): client network state verification, client system update ticks, and two-client chat propagation
  • Add docs/Engine-Testing-Patterns.md documenting MTE testing patterns, including the network event registration problem (inner-class @BroadcastEvent/@OwnerEvent/@ServerEvent events don't replicate) and context isolation

Test plan

  • ClientNetworkStateTest — verifies client NetworkSystem mode (CLIENT) and NetworkSubsystem presence
  • ClientSystemUpdateTest — registers UpdateSubscriberSystem on client, verifies it receives ticks
  • TwoClientChatTest — two clients connect, chat message broadcast via ChatMessageEvent propagates between them
  • All 3 tests pass locally on Windows (JDK 17, Gradle 8.10.2)

Related

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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 19, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Testing Documentation
docs/Engine-Testing-Patterns.md
New documentation describing Terasology engine test tiers, MTE field injection (@In), single- and multiplayer @IntegrationEnvironment usage, network event registration/replication rules, singleton save/restore patterns, verification patterns for network behavior, and Gradle commands for running tests.
Integration Tests — Network & Client State
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientNetworkStateTest.java
New @IntegrationEnvironment(networkMode = LISTEN_SERVER) test that creates a client via ModuleTestingHelper and asserts the client Context has a NetworkSystem in CLIENT mode and that the GameEngine includes a NetworkSubsystem.
Integration Tests — Client System Updates
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.java
New @IntegrationEnvironment(networkMode = LISTEN_SERVER) test registering a client-side TickCountingSystem with the client ComponentSystemManager, advancing the environment until the system records a target number of update ticks, and asserting update delivery.
Integration Tests — Multi-client Chat
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java
New @IntegrationEnvironment(networkMode = LISTEN_SERVER) test that creates two clients, waits for host registration, grants chat permission to clients, installs a ChatProbe on client 2, sends a ChatMessageEvent from the host, waits for replication, and asserts the probe observed the expected message.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~45 minutes

Poem

🐰 I nibble notes and hop through tests,
Clients boot up, then chat and rest.
Ticks count down in gentle stride,
Packets scamper from host to side.
A tiny cheer for each green test.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the primary change: adding MTE network and multiplayer integration tests. It clearly reflects the main additions in the PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the salvaged tests, documentation additions, test plan verification, and related context from PR #5304.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9621e21 and 2b136f8.

📒 Files selected for processing (4)
  • docs/Engine-Testing-Patterns.md
  • engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientNetworkStateTest.java
  • engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.java
  • engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java

Comment thread docs/Engine-Testing-Patterns.md
Comment thread docs/Engine-Testing-Patterns.md
Cervator and others added 3 commits April 19, 2026 16:40
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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java (2)

65-70: ⚠️ Potential issue | 🟡 Minor

Remove the duplicate event-handler registration.

client2Csm.register(probe) should be the single registration path for the component system; registering the same handler directly with EventSystem can double-deliver ChatMessageEvents.

🧹 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 | 🟠 Major

Bind 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

📥 Commits

Reviewing files that changed from the base of the PR and between e5e096e and 3ed1b86.

📒 Files selected for processing (3)
  • engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientNetworkStateTest.java
  • engine-tests/src/test/java/org/terasology/engine/integrationenvironment/ClientSystemUpdateTest.java
  • engine-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

… 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>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java (1)

25-30: ⚠️ Potential issue | 🟡 Minor

Bind 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3ed1b86 and 5841573.

📒 Files selected for processing (2)
  • docs/Engine-Testing-Patterns.md
  • engine-tests/src/test/java/org/terasology/engine/integrationenvironment/TwoClientChatTest.java
✅ Files skipped from review due to trivial changes (1)
  • docs/Engine-Testing-Patterns.md

…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>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread docs/Engine-Testing-Patterns.md Outdated
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants