Skip to content

Commit

Permalink
Join a dedicated server in the automated client test. (#4057)
Browse files Browse the repository at this point in the history
* Join a dedicated server in the automated client test.

* Add missing header
  • Loading branch information
modmuss50 authored Aug 29, 2024
1 parent 6c0945c commit f14f8b0
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static net.fabricmc.fabric.test.base.client.FabricClientTestHelper.clickScreenButton;
import static net.fabricmc.fabric.test.base.client.FabricClientTestHelper.closeScreen;
import static net.fabricmc.fabric.test.base.client.FabricClientTestHelper.connectToServer;
import static net.fabricmc.fabric.test.base.client.FabricClientTestHelper.enableDebugHud;
import static net.fabricmc.fabric.test.base.client.FabricClientTestHelper.openGameMenu;
import static net.fabricmc.fabric.test.base.client.FabricClientTestHelper.openInventory;
Expand All @@ -34,11 +35,15 @@
import java.nio.file.Files;
import java.nio.file.Path;

import com.mojang.authlib.GameProfile;
import org.spongepowered.asm.mixin.MixinEnvironment;

import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.AccessibilityOnboardingScreen;
import net.minecraft.client.gui.screen.ConfirmScreen;
import net.minecraft.client.gui.screen.ReconfiguringScreen;
import net.minecraft.client.gui.screen.TitleScreen;
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
import net.minecraft.client.gui.screen.world.CreateWorldScreen;
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
import net.minecraft.client.option.Perspective;
Expand Down Expand Up @@ -114,6 +119,7 @@ private void runTest() {
// See if the player render events are working.
setPerspective(Perspective.THIRD_PERSON_BACK);
takeScreenshot("in_game_overworld_third_person");
setPerspective(Perspective.FIRST_PERSON);
}

{
Expand All @@ -126,6 +132,34 @@ private void runTest() {
openGameMenu();
takeScreenshot("game_menu");
clickScreenButton("menu.returnToMenu");
waitForScreen(TitleScreen.class);
}

try (var server = new TestDedicatedServer()) {
connectToServer(server);
waitForWorldTicks(5);

final GameProfile profile = submitAndWait(MinecraftClient::getGameProfile);
server.runCommand("op " + profile.getName());
server.runCommand("gamemode creative " + profile.getName());

waitForWorldTicks(20);
takeScreenshot("server_in_game");

{ // Test that we can enter and exit configuration
server.runCommand("debugconfig config " + profile.getName());
waitForScreen(ReconfiguringScreen.class);
takeScreenshot("server_config");
server.runCommand("debugconfig unconfig " + profile.getId());
waitForWorldTicks(1);
}

openGameMenu();
takeScreenshot("server_game_menu");
clickScreenButton("menu.disconnect");

waitForScreen(MultiplayerScreen.class);
clickScreenButton("gui.back");
}

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
import net.minecraft.client.gui.screen.world.LevelLoadingScreen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.gui.widget.CyclingButtonWidget;
import net.minecraft.client.gui.widget.PressableWidget;
import net.minecraft.client.gui.widget.Widget;
import net.minecraft.client.network.ServerAddress;
import net.minecraft.client.network.ServerInfo;
import net.minecraft.client.option.Perspective;
import net.minecraft.client.util.ScreenshotRecorder;
import net.minecraft.text.Text;
Expand Down Expand Up @@ -155,6 +158,14 @@ public static void setPerspective(Perspective perspective) {
});
}

public static void connectToServer(TestDedicatedServer server) {
submitAndWait(client -> {
final var serverInfo = new ServerInfo("localhost", server.getConnectionAddress(), ServerInfo.ServerType.OTHER);
ConnectScreen.connect(client.currentScreen, client, ServerAddress.parse(server.getConnectionAddress()), serverInfo, false, null);
return null;
});
}

private static void waitFor(String what, Predicate<MinecraftClient> predicate) {
waitFor(what, predicate, Duration.ofSeconds(10));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.test.base.client;

import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

import net.minecraft.server.Main;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;

public class TestDedicatedServer implements Closeable {
public static final AtomicReference<MinecraftDedicatedServer> DEDICATED_SERVER_REF = new AtomicReference<>();
private static final Duration START_TIMEOUT = Duration.ofMinutes(5);

final ExecutorService executor = Executors.newSingleThreadExecutor();
MinecraftDedicatedServer server;

public TestDedicatedServer() {
assert DEDICATED_SERVER_REF.get() == null : "A dedicated server is already running";
executor.execute(this::run);
waitUntilReady();
Objects.requireNonNull(server);
}

public String getConnectionAddress() {
return "localhost:" + server.getServerPort();
}

public void runCommand(String command) {
submitAndWait(server -> {
server.enqueueCommand(command, server.getCommandSource());
return null;
});
}

private void run() {
setupServer();
Main.main(new String[]{});
}

private <T> CompletableFuture<T> submit(Function<MinecraftDedicatedServer, T> function) {
return server.submit(() -> function.apply(server));
}

private <T> T submitAndWait(Function<MinecraftDedicatedServer, T> function) {
return submit(function).join();
}

private void setupServer() {
try {
Files.writeString(Paths.get("eula.txt"), "eula=true");
Files.writeString(Paths.get("server.properties"), "online-mode=false");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private void waitUntilReady() {
long startTime = System.currentTimeMillis();

while (DEDICATED_SERVER_REF.get() == null) {
if (System.currentTimeMillis() - startTime > START_TIMEOUT.toMillis()) {
throw new RuntimeException("Timeout while waiting for the server to start");
}

try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

server = DEDICATED_SERVER_REF.get();
DEDICATED_SERVER_REF.set(null);
}

@Override
public void close() {
server.stop(true);
executor.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.fabric.test.base.client.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.server.dedicated.MinecraftDedicatedServer;

import net.fabricmc.fabric.test.base.client.TestDedicatedServer;

@Mixin(MinecraftDedicatedServer.class)
public abstract class MinecraftDedicatedServerMixin {
@Inject(method = "setupServer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/MinecraftDedicatedServer;loadWorld()V"))
private void captureServerInstance(CallbackInfoReturnable<Boolean> cir) {
// Capture the server instance once the server is ready to be connected to
TestDedicatedServer.DEDICATED_SERVER_REF.set((MinecraftDedicatedServer) (Object) this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"compatibilityLevel": "JAVA_21",
"client": [
"CyclingButtonWidgetAccessor",
"MinecraftDedicatedServerMixin",
"ScreenAccessor"
],
"injectors": {
Expand Down

0 comments on commit f14f8b0

Please sign in to comment.