mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-12 06:54:27 -04:00
Add various lifetime-bound try-with-resources APIs to client gametests (#4318)
* Add various lifetime-bound try-with-resources APIs to client gametests
This commit is contained in:
parent
efa825c9d7
commit
99ff640a5d
35 changed files with 1575 additions and 252 deletions
.github/workflows
fabric-client-gametest-api-v1/src
client
java/net/fabricmc/fabric
api/client/gametest/v1
ClientGameTestContext.javaTestClientWorldContext.javaTestDedicatedServerContext.javaTestInput.javaTestServerConnection.javaTestServerContext.javaTestSingleplayerContext.javaTestWorldBuilder.javaTestWorldSave.javapackage-info.java
impl/client/gametest
ClientGameTestContextImpl.javaClientGameTestImpl.javaDedicatedServerImplUtil.javaFabricClientGameTestRunner.javaTestClientWorldContextImpl.javaTestDedicatedServer.javaTestDedicatedServerContextImpl.javaTestInputImpl.javaTestServerConnectionImpl.javaTestServerContextImpl.javaTestSingleplayerContextImpl.javaTestWorldBuilderImpl.javaTestWorldSaveImpl.java
mixin/client/gametest
resources
testmodClient/java/net/fabricmc/fabric/test/client/gametest
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
|
@ -43,6 +43,7 @@ jobs:
|
|||
with:
|
||||
distribution: 'microsoft'
|
||||
java-version: '21'
|
||||
- run: mkdir run && echo "eula=true" >> run/eula.txt
|
||||
- name: Run Client Gametests
|
||||
uses: modmuss50/xvfb-action@v1
|
||||
with:
|
||||
|
|
|
@ -30,8 +30,9 @@ import net.minecraft.client.MinecraftClient;
|
|||
import net.minecraft.client.gui.screen.Screen;
|
||||
|
||||
/**
|
||||
* Context for a client gametest containing various helpful functions and functions to access the game. Functions in
|
||||
* this class can only be called on the client gametest thread.
|
||||
* Context for a client gametest containing various helpful functions and functions to access the game.
|
||||
*
|
||||
* <p>Functions in this class can only be called on the client gametest thread.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface ClientGameTestContext {
|
||||
|
@ -61,8 +62,9 @@ public interface ClientGameTestContext {
|
|||
* Waits for a predicate to be true. Fails if the predicate is not satisfied after {@link #DEFAULT_TIMEOUT} ticks.
|
||||
*
|
||||
* @param predicate The predicate to check
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
void waitFor(Predicate<MinecraftClient> predicate);
|
||||
int waitFor(Predicate<MinecraftClient> predicate);
|
||||
|
||||
/**
|
||||
* Waits for a predicate to be true. Fails if the predicate is not satisfied after {@code timeout} ticks. If
|
||||
|
@ -70,16 +72,18 @@ public interface ClientGameTestContext {
|
|||
*
|
||||
* @param predicate The predicate to check
|
||||
* @param timeout The number of ticks before timing out
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
void waitFor(Predicate<MinecraftClient> predicate, int timeout);
|
||||
int waitFor(Predicate<MinecraftClient> predicate, int timeout);
|
||||
|
||||
/**
|
||||
* Waits for the given screen class to be shown. If {@code screenClass} is {@code null}, waits for the current
|
||||
* screen to be {@code null}. Fails if the screen does not open after {@link #DEFAULT_TIMEOUT} ticks.
|
||||
*
|
||||
* @param screenClass The screen class to wait to open
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
void waitForScreen(@Nullable Class<? extends Screen> screenClass);
|
||||
int waitForScreen(@Nullable Class<? extends Screen> screenClass);
|
||||
|
||||
/**
|
||||
* Opens a {@link Screen} on the client.
|
||||
|
@ -126,7 +130,14 @@ public interface ClientGameTestContext {
|
|||
*
|
||||
* @return The client gametest input handler
|
||||
*/
|
||||
ClientGameTestInput getInput();
|
||||
TestInput getInput();
|
||||
|
||||
/**
|
||||
* Creates a world builder for creating singleplayer worlds and dedicated servers.
|
||||
*
|
||||
* @return A new world builder
|
||||
*/
|
||||
TestWorldBuilder worldBuilder();
|
||||
|
||||
/**
|
||||
* Restores all game options in {@link MinecraftClient#options} to their default values for client gametests. This
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
/**
|
||||
* Context for a client gametest containing various helpful functions while a client world is open.
|
||||
*
|
||||
* <p>Functions in this class can only be called on the client gametest thread.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestClientWorldContext {
|
||||
/**
|
||||
* The default timeout in ticks to wait for chunks to load/render (1 minute).
|
||||
*/
|
||||
int DEFAULT_CHUNK_LOAD_TIMEOUT = SharedConstants.TICKS_PER_MINUTE;
|
||||
|
||||
/**
|
||||
* Waits for all chunks that will be downloaded from the server to be downloaded. Fails if the chunks haven't been
|
||||
* downloaded after {@link #DEFAULT_CHUNK_LOAD_TIMEOUT} ticks. See {@link #waitForChunksDownload(int)} for details.
|
||||
*
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
default int waitForChunksDownload() {
|
||||
return waitForChunksDownload(DEFAULT_CHUNK_LOAD_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
Waits for all chunks that will be downloaded from the server to be downloaded. After this, methods such as
|
||||
* {@link ClientWorld#getChunk(int, int)} and {@link ClientWorld#getBlockState(BlockPos)} will return the expected
|
||||
* value. However, the chunks may not yet be rendered and may not appear in screenshots, if you need this, use
|
||||
* {@link #waitForChunksRender(int)} instead. Fails if the chunks haven't been downloaded after {@code timeout}
|
||||
* ticks.
|
||||
*
|
||||
* @param timeout The number of ticks before timing out
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
int waitForChunksDownload(int timeout);
|
||||
|
||||
/**
|
||||
* Waits for all chunks to be downloaded and rendered. After this, all chunks that will ever be visible are visible
|
||||
* in screenshots. Fails if the chunks haven't been downloaded and rendered after
|
||||
* {@link #DEFAULT_CHUNK_LOAD_TIMEOUT} ticks.
|
||||
*
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
default int waitForChunksRender() {
|
||||
return waitForChunksRender(DEFAULT_CHUNK_LOAD_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for all chunks to be downloaded and rendered. After this, all chunks that will ever be visible are visible
|
||||
* in screenshots. Fails if the chunks haven't been downloaded and rendered after {@code timeout} ticks.
|
||||
*
|
||||
* @param timeout The number of ticks before timing out
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
default int waitForChunksRender(int timeout) {
|
||||
return waitForChunksRender(true, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for all chunks to be rendered, optionally waiting for chunks to be downloaded first. After this, all chunks
|
||||
* that are present in the client world will be visible in screenshots. Fails if the chunks haven't been rendered
|
||||
* (and optionally downloaded) after {@link #DEFAULT_CHUNK_LOAD_TIMEOUT} ticks.
|
||||
*
|
||||
* @param waitForDownload Whether to wait for chunks to be downloaded
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
default int waitForChunksRender(boolean waitForDownload) {
|
||||
return waitForChunksRender(waitForDownload, DEFAULT_CHUNK_LOAD_TIMEOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for all chunks to be rendered, optionally waiting for chunks to be downloaded first. After this, all chunks
|
||||
* that are present in the client world will be visible in screenshots. Fails if the chunks haven't been rendered
|
||||
* (and optionally downloaded) after {@code timeout} ticks.
|
||||
*
|
||||
* @param waitForDownload Whether to wait for chunks to be downloaded
|
||||
* @param timeout The number of ticks before timing out
|
||||
* @return The number of ticks waited
|
||||
*/
|
||||
int waitForChunksRender(boolean waitForDownload, int timeout);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Context for a client gametest containing various helpful functions while an in-process dedicated server is running.
|
||||
* This class implements {@link AutoCloseable} and is intended to be used in a try-with-resources statement. When
|
||||
* closed, the dedicated server will be stopped.
|
||||
*
|
||||
* <p>Functions in this class can only be called on the client gametest thread.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestDedicatedServerContext extends TestServerContext, AutoCloseable {
|
||||
/**
|
||||
* Connects the client to the dedicated server. The resulting connection is intended to be used in a
|
||||
* try-with-resources statement.
|
||||
*
|
||||
* @return The connection handle to the dedicated server
|
||||
*/
|
||||
TestServerConnection connect();
|
||||
|
||||
/**
|
||||
* Stops the dedicated server.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
|
@ -29,7 +29,7 @@ import net.minecraft.client.util.InputUtil;
|
|||
* The client gametest input handler used to simulate inputs to the client.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface ClientGameTestInput {
|
||||
public interface TestInput {
|
||||
/**
|
||||
* Starts holding down a key binding. The key binding will be held until it is released. The key binding must be
|
||||
* bound. Does nothing if the key binding is already being held.
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Context for a connection to a dedicated server containing various helpful functions while the connection is alive.
|
||||
* This class implements {@link AutoCloseable} and is intended to be used in a try-with-resources statement. When
|
||||
* closed, the client will be disconnected from the server.
|
||||
*
|
||||
* <p>Functions in this class can only be called on the client gametest thread.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestServerConnection extends AutoCloseable {
|
||||
/**
|
||||
* Gets the client world context for this connection.
|
||||
*
|
||||
* @return The client world context
|
||||
*/
|
||||
TestClientWorldContext getClientWorld();
|
||||
|
||||
/**
|
||||
* Disconnects the client from the dedicated server.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import org.apache.commons.lang3.function.FailableConsumer;
|
||||
import org.apache.commons.lang3.function.FailableFunction;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
/**
|
||||
* Context for a client gametest containing various helpful functions while a server (integrated or dedicated) is
|
||||
* running.
|
||||
*
|
||||
* <p>Functions in this class can only be called on the client gametest thread.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestServerContext {
|
||||
/**
|
||||
* Runs a command on the server.
|
||||
*
|
||||
* @param command The command to run
|
||||
*/
|
||||
void runCommand(String command);
|
||||
|
||||
/**
|
||||
* Runs the given action on the server thread, and waits for it to complete.
|
||||
*
|
||||
* @param action The action to run on the server thread
|
||||
* @param <E> The type of the checked exception that the action throws
|
||||
* @throws E When the action throws an exception
|
||||
*/
|
||||
<E extends Throwable> void runOnServer(FailableConsumer<MinecraftServer, E> action) throws E;
|
||||
|
||||
/**
|
||||
* Runs the given function on the server thread, and returns the result.
|
||||
*
|
||||
* @param function The function to run on the server thread
|
||||
* @return The result of the function
|
||||
* @param <T> The type of the value to return
|
||||
* @param <E> The type of the checked exception that the function throws
|
||||
* @throws E When the function throws an exception
|
||||
*/
|
||||
<T, E extends Throwable> T computeOnServer(FailableFunction<MinecraftServer, T, E> function) throws E;
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Context for a client gametest containing various helpful functions while a singleplayer game is open.
|
||||
*
|
||||
* <p>Functions in this class can only be called on the client gametest thread.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestSingleplayerContext extends AutoCloseable {
|
||||
/**
|
||||
* Gets the handle for the world save.
|
||||
*
|
||||
* @return The handle for the world save
|
||||
*/
|
||||
TestWorldSave getWorldSave();
|
||||
|
||||
/**
|
||||
* Gets the handle for the client world.
|
||||
*
|
||||
* @return The handle for the client world
|
||||
*/
|
||||
TestClientWorldContext getClientWorld();
|
||||
|
||||
/**
|
||||
* Gets the handle for the integrated server.
|
||||
*
|
||||
* @return The handle for the integrated server
|
||||
*/
|
||||
TestServerContext getServer();
|
||||
|
||||
/**
|
||||
* Closes the singleplayer world.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import net.minecraft.client.gui.screen.world.WorldCreator;
|
||||
|
||||
/**
|
||||
* A builder used for creating singleplayer worlds and dedicated servers.
|
||||
*
|
||||
* <p>Worlds from this builder default to being flat worlds with settings and game rules designed for consistency of
|
||||
* tests, see the package documentation for details. To disable this, use {@link #setUseConsistentSettings}. If you need
|
||||
* to re-enable a particular setting, you can override it using {@link #adjustSettings}.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestWorldBuilder {
|
||||
/**
|
||||
* Sets whether to use consistent world settings. Consistent settings are designed for consistency of tests. See the
|
||||
* package documentation for details on what the consistent settings are.
|
||||
*
|
||||
* <p>If disabled, the world builder will default to creating worlds with the default world preset in survival mode,
|
||||
* as if clicking straight through the create world screen without changing any settings.
|
||||
*
|
||||
* @param useConsistentSettings Whether to use consistent settings
|
||||
* @return This world builder instance
|
||||
*/
|
||||
TestWorldBuilder setUseConsistentSettings(boolean useConsistentSettings);
|
||||
|
||||
/**
|
||||
* Adjusts the world settings from the default. Can be used to adjust anything that can be changed in the create
|
||||
* world screen, including generation settings and game rules.
|
||||
*
|
||||
* @param settingsAdjuster The function to adjust the world settings
|
||||
* @return This world builder instance
|
||||
*/
|
||||
TestWorldBuilder adjustSettings(Consumer<WorldCreator> settingsAdjuster);
|
||||
|
||||
/**
|
||||
* Creates and joins a singleplayer world with the configured world settings.
|
||||
*
|
||||
* @return The singleplayer context of the world that was joined
|
||||
*/
|
||||
TestSingleplayerContext create();
|
||||
|
||||
/**
|
||||
* Creates and starts a dedicated server with the configured world settings.
|
||||
*
|
||||
* @return The dedicated server context of the server that was created
|
||||
*/
|
||||
default TestDedicatedServerContext createServer() {
|
||||
return createServer(new Properties());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts a dedicated server with the configured world settings and some custom server properties.
|
||||
*
|
||||
* @param serverProperties The custom server properties to be written to the {@code server.properties} file.
|
||||
* @return The dedicated server context of the server that was created.
|
||||
*/
|
||||
TestDedicatedServerContext createServer(Properties serverProperties);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.api.client.gametest.v1;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* A handle for a singleplayer world save. Can be used to reopen a singleplayer world that was created earlier in the
|
||||
* same gametest.
|
||||
*/
|
||||
@ApiStatus.NonExtendable
|
||||
public interface TestWorldSave {
|
||||
/**
|
||||
* Gets the directory of the world save.
|
||||
*
|
||||
* @return The world save directory
|
||||
*/
|
||||
Path getSaveDirectory();
|
||||
|
||||
/**
|
||||
* Opens and joins the singleplayer world.
|
||||
*
|
||||
* @return The singleplayer context of the world that was joined
|
||||
*/
|
||||
TestSingleplayerContext open();
|
||||
}
|
|
@ -21,6 +21,138 @@
|
|||
* <p>A few changes have been made to how the vanilla game threads run, to make tests more reproducible. Notably, there
|
||||
* is exactly one server tick per client tick while a server is running (singleplayer or multiplayer). On singleplayer,
|
||||
* packets will always arrive on a consistent tick.
|
||||
*
|
||||
* <h1>Default settings</h1>
|
||||
* The client gametest API adjusts some default settings, usually for consistency of tests. These settings can always be
|
||||
* changed back to the default value or a different value inside a gametest.
|
||||
*
|
||||
* <h2>Game options</h2>
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>Setting name</th>
|
||||
* <th>Gametest default</th>
|
||||
* <th>Vanilla default</th>
|
||||
* <th>Reason</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.option.GameOptions#tutorialStep Tutorial step}</td>
|
||||
* <td>{@link net.minecraft.client.tutorial.TutorialStep#NONE NONE}</td>
|
||||
* <td>{@link net.minecraft.client.tutorial.TutorialStep#MOVEMENT MOVEMENT}</td>
|
||||
* <td>Consistency of tests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.option.GameOptions#getCloudRenderMode() Cloud render mode}</td>
|
||||
* <td>{@link net.minecraft.client.option.CloudRenderMode#OFF OFF}</td>
|
||||
* <td>{@link net.minecraft.client.option.CloudRenderMode#FANCY FANCY}</td>
|
||||
* <td>Consistency of tests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.option.GameOptions#onboardAccessibility Onboard accessibility}</td>
|
||||
* <td>{@code false}</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Would cause the game test runner to have to click through the onboard accessibility prompt</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.option.GameOptions#getViewDistance() View distance}</td>
|
||||
* <td>{@code 5}</td>
|
||||
* <td>{@code 10}</td>
|
||||
* <td>Speeds up loading of chunks, especially for functions such as
|
||||
* {@link net.fabricmc.fabric.api.client.gametest.v1.TestClientWorldContext#waitForChunksRender() TestClientWorldContext.waitForChunksRender()}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.option.GameOptions#getSoundVolumeOption(net.minecraft.sound.SoundCategory) Music volume}</td>
|
||||
* <td>{@code 0.0}</td>
|
||||
* <td>{@code 1.0}</td>
|
||||
* <td>The game music is annoying while running gametests</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h2>World creation options</h2>
|
||||
* These adjusted defaults only apply if the world builder's
|
||||
* {@linkplain net.fabricmc.fabric.api.client.gametest.v1.TestWorldBuilder#setUseConsistentSettings(boolean) consistent settings}
|
||||
* have not been set to {@code false}.
|
||||
*
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>Setting name</th>
|
||||
* <th>Gametest default</th>
|
||||
* <th>Vanilla default</th>
|
||||
* <th>Reason</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.gui.screen.world.WorldCreator#setWorldType(net.minecraft.client.gui.screen.world.WorldCreator.WorldType) World type}</td>
|
||||
* <td>{@link net.minecraft.world.gen.WorldPresets#FLAT FLAT}</td>
|
||||
* <td>{@link net.minecraft.world.gen.WorldPresets#DEFAULT DEFAULT}</td>
|
||||
* <td>Creates cleaner test cases</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.gui.screen.world.WorldCreator#setSeed(String) Seed}</td>
|
||||
* <td>{@code 1}</td>
|
||||
* <td>Random value</td>
|
||||
* <td>Consistency of tests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.client.gui.screen.world.WorldCreator#setGenerateStructures(boolean) Generate structures}</td>
|
||||
* <td>{@code false}</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Consistency of tests and creates cleaner tests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.world.GameRules#DO_DAYLIGHT_CYCLE Do daylight cycle}</td>
|
||||
* <td>{@code false}</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Consistency of tests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.world.GameRules#DO_WEATHER_CYCLE Do weather cycle}</td>
|
||||
* <td>{@code false}</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Consistency of tests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@linkplain net.minecraft.world.GameRules#DO_MOB_SPAWNING Do mob spawning}</td>
|
||||
* <td>{@code false}</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Consistency of tests</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*
|
||||
* <h2>Dedicated server properties</h2>
|
||||
* <table>
|
||||
* <tr>
|
||||
* <th>Setting name</th>
|
||||
* <th>Gametest default</th>
|
||||
* <th>Vanilla default</th>
|
||||
* <th>Reason</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code online-mode}</td>
|
||||
* <td>{@code false}</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Allows the gametest client to connect to the dedicated server without being logged in to a Minecraft
|
||||
* account</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code sync-chunk-writes}</td>
|
||||
* <td>{@code true} on Windows, {@code false} on other operating systems</td>
|
||||
* <td>{@code true}</td>
|
||||
* <td>Causes world saving and closing to be extremely slow (on the order of many seconds to minutes) on Unix
|
||||
* systems. The vanilla default is set correctly in singleplayer but not on dedicated servers.</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code spawn-protection}</td>
|
||||
* <td>{@code 0}</td>
|
||||
* <td>{@code 16}</td>
|
||||
* <td>Spawn protection prevents non-opped players from modifying the world within a certain radius of the world
|
||||
* spawn point, a likely source of confusion when writing gametests</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@code max-players}</td>
|
||||
* <td>{@code 1}</td>
|
||||
* <td>{@code 20}</td>
|
||||
* <td>Stops other players from joining the server and interfering with the test</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
package net.fabricmc.fabric.api.client.gametest.v1;
|
||||
|
|
|
@ -48,13 +48,14 @@ import net.minecraft.text.Text;
|
|||
import net.minecraft.util.Nullables;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestWorldBuilder;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.CyclingButtonWidgetAccessor;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.GameOptionsAccessor;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.ScreenAccessor;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public final class ClientGameTestContextImpl implements ClientGameTestContext {
|
||||
private final ClientGameTestInputImpl input = new ClientGameTestInputImpl(this);
|
||||
private final TestInputImpl input = new TestInputImpl(this);
|
||||
|
||||
private static final Map<String, Object> DEFAULT_GAME_OPTIONS = new HashMap<>();
|
||||
|
||||
|
@ -66,6 +67,9 @@ public final class ClientGameTestContextImpl implements ClientGameTestContext {
|
|||
// Messes with game tests starting
|
||||
options.onboardAccessibility = false;
|
||||
|
||||
// Makes chunk rendering finish sooner
|
||||
options.getViewDistance().setValue(5);
|
||||
|
||||
// Just annoying
|
||||
options.getSoundVolumeOption(SoundCategory.MUSIC).setValue(0.0);
|
||||
|
||||
|
@ -124,27 +128,32 @@ public final class ClientGameTestContextImpl implements ClientGameTestContext {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void waitFor(Predicate<MinecraftClient> predicate) {
|
||||
public int waitFor(Predicate<MinecraftClient> predicate) {
|
||||
ThreadingImpl.checkOnGametestThread("waitFor");
|
||||
Preconditions.checkNotNull(predicate, "predicate");
|
||||
waitFor(predicate, DEFAULT_TIMEOUT);
|
||||
return waitFor(predicate, DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitFor(Predicate<MinecraftClient> predicate, int timeout) {
|
||||
public int waitFor(Predicate<MinecraftClient> predicate, int timeout) {
|
||||
ThreadingImpl.checkOnGametestThread("waitFor");
|
||||
Preconditions.checkNotNull(predicate, "predicate");
|
||||
|
||||
if (timeout == NO_TIMEOUT) {
|
||||
int ticksWaited = 0;
|
||||
|
||||
while (!computeOnClient(predicate::test)) {
|
||||
ticksWaited++;
|
||||
ThreadingImpl.runTick();
|
||||
}
|
||||
|
||||
return ticksWaited;
|
||||
} else {
|
||||
Preconditions.checkArgument(timeout > 0, "timeout must be positive");
|
||||
|
||||
for (int i = 0; i < timeout; i++) {
|
||||
if (computeOnClient(predicate::test)) {
|
||||
return;
|
||||
return i;
|
||||
}
|
||||
|
||||
ThreadingImpl.runTick();
|
||||
|
@ -153,17 +162,19 @@ public final class ClientGameTestContextImpl implements ClientGameTestContext {
|
|||
if (!computeOnClient(predicate::test)) {
|
||||
throw new AssertionError("Timed out waiting for predicate");
|
||||
}
|
||||
|
||||
return timeout;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForScreen(@Nullable Class<? extends Screen> screenClass) {
|
||||
public int waitForScreen(@Nullable Class<? extends Screen> screenClass) {
|
||||
ThreadingImpl.checkOnGametestThread("waitForScreen");
|
||||
|
||||
if (screenClass == null) {
|
||||
waitFor(client -> client.currentScreen == null);
|
||||
return waitFor(client -> client.currentScreen == null);
|
||||
} else {
|
||||
waitFor(client -> screenClass.isInstance(client.currentScreen));
|
||||
return waitFor(client -> screenClass.isInstance(client.currentScreen));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,10 +281,15 @@ public final class ClientGameTestContextImpl implements ClientGameTestContext {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ClientGameTestInputImpl getInput() {
|
||||
public TestInputImpl getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestWorldBuilder worldBuilder() {
|
||||
return new TestWorldBuilderImpl(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreDefaultGameOptions() {
|
||||
ThreadingImpl.checkOnGametestThread("restoreDefaultGameOptions");
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.ConfirmScreen;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.screen.world.BackupPromptScreen;
|
||||
import net.minecraft.client.gui.screen.world.LevelLoadingScreen;
|
||||
import net.minecraft.text.TranslatableTextContent;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
|
||||
public final class ClientGameTestImpl {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger("fabric-client-gametest-api-v1");
|
||||
|
||||
private ClientGameTestImpl() {
|
||||
}
|
||||
|
||||
public static void waitForWorldLoad(ClientGameTestContext context) {
|
||||
for (int i = 0; i < SharedConstants.TICKS_PER_MINUTE; i++) {
|
||||
if (context.computeOnClient(client -> isExperimentalWarningScreen(client.currentScreen))) {
|
||||
context.clickScreenButton("gui.yes");
|
||||
}
|
||||
|
||||
if (context.computeOnClient(client -> client.currentScreen instanceof BackupPromptScreen)) {
|
||||
context.clickScreenButton("selectWorld.backupJoinSkipButton");
|
||||
}
|
||||
|
||||
if (context.computeOnClient(ClientGameTestImpl::isWorldLoadingFinished)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.waitTick();
|
||||
}
|
||||
|
||||
if (!context.computeOnClient(ClientGameTestImpl::isWorldLoadingFinished)) {
|
||||
throw new AssertionError("Timeout loading world");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isExperimentalWarningScreen(Screen screen) {
|
||||
if (!(screen instanceof ConfirmScreen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(screen.getTitle().getContent() instanceof TranslatableTextContent translatableContents)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return "selectWorld.warning.experimental.title".equals(translatableContents.getKey());
|
||||
}
|
||||
|
||||
private static boolean isWorldLoadingFinished(MinecraftClient client) {
|
||||
return client.world != null && !(client.currentScreen instanceof LevelLoadingScreen);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.server.Main;
|
||||
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
|
||||
import net.minecraft.util.Util;
|
||||
|
||||
public final class DedicatedServerImplUtil {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-client-gametest-api-v1");
|
||||
private static final Properties DEFAULT_SERVER_PROPERTIES = Util.make(new Properties(), properties -> {
|
||||
// allow non-authenticated connections from localhost
|
||||
properties.setProperty("online-mode", "false");
|
||||
|
||||
// disable sync-chunk-writes on unix systems, it slows world saving down a LOT and doesn't really help anything
|
||||
properties.setProperty("sync-chunk-writes", String.valueOf(Util.getOperatingSystem() == Util.OperatingSystem.WINDOWS));
|
||||
|
||||
// allow non-opped players to place blocks at spawn
|
||||
properties.setProperty("spawn-protection", "0");
|
||||
|
||||
// stops other players from joining the server and interfering with the tests
|
||||
properties.setProperty("max-players", "1");
|
||||
});
|
||||
|
||||
// If this field is set, it causes the create world screen to write the level.dat file to the specified folder
|
||||
@Nullable
|
||||
public static Path saveLevelDataTo = null;
|
||||
@Nullable
|
||||
public static CompletableFuture<MinecraftDedicatedServer> serverFuture = null;
|
||||
|
||||
private DedicatedServerImplUtil() {
|
||||
}
|
||||
|
||||
public static MinecraftDedicatedServer start(Properties serverProperties) {
|
||||
setupServer(serverProperties);
|
||||
serverFuture = new CompletableFuture<>();
|
||||
|
||||
new Thread(() -> Main.main(new String[0])).start();
|
||||
|
||||
try {
|
||||
return serverFuture.get(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
serverFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupServer(Properties customServerProperties) {
|
||||
Properties serverProperties = new Properties();
|
||||
serverProperties.putAll(DEFAULT_SERVER_PROPERTIES);
|
||||
serverProperties.putAll(customServerProperties);
|
||||
|
||||
try {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(Path.of("server.properties"))) {
|
||||
serverProperties.store(writer, null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to write server properties", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,7 +54,7 @@ public class FabricClientGameTestRunner {
|
|||
}
|
||||
|
||||
context.runOnClient(client -> {
|
||||
if (client.getNetworkHandler() != null) {
|
||||
if (client.world != null) {
|
||||
throw new AssertionError("Client gametest %s finished while still connected to a server".formatted(testClassName));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.world.ClientChunkManager;
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestClientWorldContext;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.ClientChunkManagerAccessor;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.ClientChunkMapAccessor;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.ClientWorldAccessor;
|
||||
|
||||
public class TestClientWorldContextImpl implements TestClientWorldContext {
|
||||
private final ClientGameTestContext context;
|
||||
|
||||
public TestClientWorldContextImpl(ClientGameTestContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waitForChunksDownload(int timeout) {
|
||||
ThreadingImpl.checkOnGametestThread("waitForChunksDownload");
|
||||
|
||||
return context.waitFor(TestClientWorldContextImpl::areChunksLoaded, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int waitForChunksRender(boolean waitForDownload, int timeout) {
|
||||
ThreadingImpl.checkOnGametestThread("waitForChunksRender");
|
||||
|
||||
return context.waitFor(client -> (!waitForDownload || areChunksLoaded(client)) && areChunksRendered(client), timeout);
|
||||
}
|
||||
|
||||
private static boolean areChunksLoaded(MinecraftClient client) {
|
||||
int viewDistance = client.options.getClampedViewDistance();
|
||||
ClientWorld world = Objects.requireNonNull(client.world);
|
||||
ClientChunkManager.ClientChunkMap chunks = ((ClientChunkManagerAccessor) world.getChunkManager()).getChunks();
|
||||
ClientChunkMapAccessor chunksAccessor = (ClientChunkMapAccessor) (Object) chunks;
|
||||
int centerChunkX = chunksAccessor.getCenterChunkX();
|
||||
int centerChunkZ = chunksAccessor.getCenterChunkZ();
|
||||
|
||||
for (int dz = -viewDistance; dz <= viewDistance; dz++) {
|
||||
for (int dx = -viewDistance; dx <= viewDistance; dx++) {
|
||||
if (world.getChunk(centerChunkX + dx, centerChunkZ + dz, ChunkStatus.FULL, false) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean areChunksRendered(MinecraftClient client) {
|
||||
ClientWorld world = Objects.requireNonNull(client.world);
|
||||
return ((ClientWorldAccessor) world).getChunkUpdaters().isEmpty() && client.worldRenderer.isTerrainRenderComplete();
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
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.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
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) {
|
||||
ThreadingImpl.runOnServer(() -> server.getCommandManager().executeWithPrefix(server.getCommandSource(), command));
|
||||
}
|
||||
|
||||
private void run() {
|
||||
setupServer();
|
||||
Main.main(new String[]{});
|
||||
}
|
||||
|
||||
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(false);
|
||||
|
||||
while (server.getThread().isAlive()) {
|
||||
ThreadingImpl.runTick();
|
||||
}
|
||||
|
||||
executor.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
|
||||
import net.minecraft.client.network.ServerAddress;
|
||||
import net.minecraft.client.network.ServerInfo;
|
||||
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestClientWorldContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestDedicatedServerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestServerConnection;
|
||||
|
||||
public class TestDedicatedServerContextImpl extends TestServerContextImpl implements TestDedicatedServerContext {
|
||||
private final ClientGameTestContext context;
|
||||
|
||||
public TestDedicatedServerContextImpl(ClientGameTestContext context, MinecraftDedicatedServer server) {
|
||||
super(server);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestServerConnection connect() {
|
||||
ThreadingImpl.checkOnGametestThread("connect");
|
||||
|
||||
context.runOnClient(client -> {
|
||||
final var serverInfo = new ServerInfo("localhost", getConnectionAddress(), ServerInfo.ServerType.OTHER);
|
||||
ConnectScreen.connect(client.currentScreen, client, ServerAddress.parse(getConnectionAddress()), serverInfo, false, null);
|
||||
});
|
||||
|
||||
ClientGameTestImpl.waitForWorldLoad(context);
|
||||
|
||||
TestClientWorldContext clientWorld = new TestClientWorldContextImpl(context);
|
||||
return new TestServerConnectionImpl(context, clientWorld);
|
||||
}
|
||||
|
||||
private String getConnectionAddress() {
|
||||
return "localhost:" + server.getServerPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
ThreadingImpl.checkOnGametestThread("close");
|
||||
|
||||
if (!ThreadingImpl.isServerRunning || !server.getThread().isAlive()) {
|
||||
throw new AssertionError("Stopped the dedicated server before closing the dedicated server context");
|
||||
}
|
||||
|
||||
server.stop(false);
|
||||
context.waitFor(client -> !ThreadingImpl.isServerRunning && !server.getThread().isAlive());
|
||||
}
|
||||
}
|
|
@ -30,16 +30,16 @@ import net.minecraft.client.option.KeyBinding;
|
|||
import net.minecraft.client.util.InputUtil;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestInput;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestInput;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.KeyBindingAccessor;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.KeyboardAccessor;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.MouseAccessor;
|
||||
|
||||
public final class ClientGameTestInputImpl implements ClientGameTestInput {
|
||||
public final class TestInputImpl implements TestInput {
|
||||
private static final Set<InputUtil.Key> KEYS_DOWN = new HashSet<>();
|
||||
private final ClientGameTestContext context;
|
||||
|
||||
public ClientGameTestInputImpl(ClientGameTestContext context) {
|
||||
public TestInputImpl(ClientGameTestContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.TitleScreen;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestClientWorldContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestServerConnection;
|
||||
|
||||
public class TestServerConnectionImpl implements TestServerConnection {
|
||||
private final ClientGameTestContext context;
|
||||
private final TestClientWorldContext clientWorld;
|
||||
|
||||
public TestServerConnectionImpl(ClientGameTestContext context, TestClientWorldContext clientWorld) {
|
||||
this.context = context;
|
||||
this.clientWorld = clientWorld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestClientWorldContext getClientWorld() {
|
||||
return clientWorld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
ThreadingImpl.checkOnGametestThread("close");
|
||||
|
||||
context.runOnClient(client -> {
|
||||
if (client.world == null) {
|
||||
throw new AssertionError("Disconnected from server before closing the test server connection");
|
||||
}
|
||||
});
|
||||
|
||||
context.runOnClient(MinecraftClient::disconnect);
|
||||
context.waitFor(client -> client.world == null);
|
||||
context.setScreen(TitleScreen::new);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.lang3.function.FailableConsumer;
|
||||
import org.apache.commons.lang3.function.FailableFunction;
|
||||
import org.apache.commons.lang3.mutable.MutableObject;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestServerContext;
|
||||
|
||||
public class TestServerContextImpl implements TestServerContext {
|
||||
protected final MinecraftServer server;
|
||||
|
||||
public TestServerContextImpl(MinecraftServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runCommand(String command) {
|
||||
ThreadingImpl.checkOnGametestThread("runCommand");
|
||||
Preconditions.checkNotNull(command, "command");
|
||||
|
||||
runOnServer(server -> server.getCommandManager().executeWithPrefix(server.getCommandSource(), command));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <E extends Throwable> void runOnServer(FailableConsumer<MinecraftServer, E> action) throws E {
|
||||
ThreadingImpl.checkOnGametestThread("runOnServer");
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
|
||||
ThreadingImpl.runOnServer(() -> action.accept(server));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T, E extends Throwable> T computeOnServer(FailableFunction<MinecraftServer, T, E> function) throws E {
|
||||
ThreadingImpl.checkOnGametestThread("computeOnServer");
|
||||
Preconditions.checkNotNull(function, "function");
|
||||
|
||||
MutableObject<T> result = new MutableObject<>();
|
||||
ThreadingImpl.runOnServer(() -> result.setValue(function.apply(server)));
|
||||
return result.getValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.gui.screen.GameMenuScreen;
|
||||
import net.minecraft.client.gui.screen.TitleScreen;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestClientWorldContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestServerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestSingleplayerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestWorldSave;
|
||||
|
||||
public class TestSingleplayerContextImpl implements TestSingleplayerContext {
|
||||
private final ClientGameTestContext context;
|
||||
private final TestWorldSave worldSave;
|
||||
private final TestClientWorldContext clientWorld;
|
||||
private final TestServerContext server;
|
||||
|
||||
public TestSingleplayerContextImpl(ClientGameTestContext context, TestWorldSave worldSave, MinecraftServer server) {
|
||||
this.context = context;
|
||||
this.worldSave = worldSave;
|
||||
this.clientWorld = new TestClientWorldContextImpl(context);
|
||||
this.server = new TestServerContextImpl(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestWorldSave getWorldSave() {
|
||||
return worldSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestClientWorldContext getClientWorld() {
|
||||
return clientWorld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestServerContext getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
ThreadingImpl.checkOnGametestThread("close");
|
||||
|
||||
context.runOnClient(client -> {
|
||||
if (client.world == null) {
|
||||
throw new IllegalStateException("Exited the world before closing singleplayer context");
|
||||
}
|
||||
});
|
||||
|
||||
context.setScreen(() -> new GameMenuScreen(true));
|
||||
context.clickScreenButton("menu.returnToMenu");
|
||||
context.waitForScreen(TitleScreen.class);
|
||||
context.waitFor(client -> !ThreadingImpl.isServerRunning && client.world == null, SharedConstants.TICKS_PER_MINUTE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.world.CreateWorldScreen;
|
||||
import net.minecraft.client.gui.screen.world.WorldCreator;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
|
||||
import net.minecraft.world.GameRules;
|
||||
import net.minecraft.world.gen.WorldPreset;
|
||||
import net.minecraft.world.gen.WorldPresets;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestDedicatedServerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestSingleplayerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestWorldBuilder;
|
||||
import net.fabricmc.fabric.mixin.client.gametest.CreateWorldScreenAccessor;
|
||||
|
||||
public class TestWorldBuilderImpl implements TestWorldBuilder {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-client-gametest-api-v1");
|
||||
private final ClientGameTestContext context;
|
||||
private boolean useConsistentSettings = true;
|
||||
|
||||
private Consumer<WorldCreator> settingsAdjustor = creator -> {
|
||||
};
|
||||
|
||||
public TestWorldBuilderImpl(ClientGameTestContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestWorldBuilder setUseConsistentSettings(boolean useConsistentSettings) {
|
||||
this.useConsistentSettings = useConsistentSettings;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestWorldBuilder adjustSettings(Consumer<WorldCreator> settingsAdjuster) {
|
||||
Preconditions.checkNotNull(settingsAdjuster, "settingsAdjuster");
|
||||
|
||||
this.settingsAdjustor = settingsAdjuster;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestSingleplayerContext create() {
|
||||
ThreadingImpl.checkOnGametestThread("create");
|
||||
Preconditions.checkState(!ThreadingImpl.isServerRunning, "Cannot create a world when a server is running");
|
||||
|
||||
Path saveDirectory = navigateCreateWorldScreen();
|
||||
ClientGameTestImpl.waitForWorldLoad(context);
|
||||
|
||||
MinecraftServer server = context.computeOnClient(MinecraftClient::getServer);
|
||||
return new TestSingleplayerContextImpl(context, new TestWorldSaveImpl(context, saveDirectory), server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestDedicatedServerContext createServer(Properties serverProperties) {
|
||||
ThreadingImpl.checkOnGametestThread("createServer");
|
||||
Preconditions.checkState(!ThreadingImpl.isServerRunning, "Cannot create a server when a server is running");
|
||||
|
||||
DedicatedServerImplUtil.saveLevelDataTo = Path.of(serverProperties.getProperty("level-name", "world"));
|
||||
|
||||
try {
|
||||
FileUtils.deleteDirectory(DedicatedServerImplUtil.saveLevelDataTo.toFile());
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to clean up old dedicated server world", e);
|
||||
}
|
||||
|
||||
try {
|
||||
navigateCreateWorldScreen();
|
||||
} finally {
|
||||
DedicatedServerImplUtil.saveLevelDataTo = null;
|
||||
}
|
||||
|
||||
MinecraftDedicatedServer server = DedicatedServerImplUtil.start(serverProperties);
|
||||
return new TestDedicatedServerContextImpl(context, server);
|
||||
}
|
||||
|
||||
private Path navigateCreateWorldScreen() {
|
||||
Path saveDirectory = context.computeOnClient(client -> {
|
||||
CreateWorldScreen.show(client, client.currentScreen);
|
||||
|
||||
if (!(client.currentScreen instanceof CreateWorldScreen createWorldScreen)) {
|
||||
throw new AssertionError("CreateWorldScreen.show did not set the current screen");
|
||||
}
|
||||
|
||||
WorldCreator creator = ((CreateWorldScreenAccessor) createWorldScreen).getWorldCreator();
|
||||
|
||||
if (useConsistentSettings) {
|
||||
setConsistentSettings(creator);
|
||||
}
|
||||
|
||||
settingsAdjustor.accept(creator);
|
||||
|
||||
return client.getLevelStorage().getSavesDirectory().resolve(creator.getWorldDirectoryName());
|
||||
});
|
||||
|
||||
context.clickScreenButton("selectWorld.create");
|
||||
|
||||
return saveDirectory;
|
||||
}
|
||||
|
||||
private static void setConsistentSettings(WorldCreator creator) {
|
||||
RegistryEntry<WorldPreset> flatPreset = creator.getGeneratorOptionsHolder().getCombinedRegistryManager().getOrThrow(RegistryKeys.WORLD_PRESET).getOrThrow(WorldPresets.FLAT);
|
||||
creator.setWorldType(new WorldCreator.WorldType(flatPreset));
|
||||
creator.setSeed("1");
|
||||
creator.setGenerateStructures(false);
|
||||
creator.getGameRules().get(GameRules.DO_DAYLIGHT_CYCLE).set(false, null);
|
||||
creator.getGameRules().get(GameRules.DO_WEATHER_CYCLE).set(false, null);
|
||||
creator.getGameRules().get(GameRules.DO_MOB_SPAWNING).set(false, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.impl.client.gametest;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestSingleplayerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestWorldSave;
|
||||
|
||||
public final class TestWorldSaveImpl implements TestWorldSave {
|
||||
private final ClientGameTestContext context;
|
||||
private final Path saveDirectory;
|
||||
|
||||
public TestWorldSaveImpl(ClientGameTestContext context, Path saveDirectory) {
|
||||
this.context = context;
|
||||
this.saveDirectory = saveDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path getSaveDirectory() {
|
||||
return saveDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestSingleplayerContext open() {
|
||||
ThreadingImpl.checkOnGametestThread("open");
|
||||
Preconditions.checkState(!ThreadingImpl.isServerRunning, "Cannot open a world when a server is running");
|
||||
|
||||
context.runOnClient(client -> {
|
||||
client.createIntegratedServerLoader().start(saveDirectory.getFileName().toString(), () -> {
|
||||
throw new AssertionError("Level loading should not be canceled");
|
||||
});
|
||||
});
|
||||
|
||||
ClientGameTestImpl.waitForWorldLoad(context);
|
||||
|
||||
MinecraftServer server = context.computeOnClient(MinecraftClient::getServer);
|
||||
return new TestSingleplayerContextImpl(context, this, server);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.mixin.client.gametest;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import net.minecraft.client.world.ClientChunkManager;
|
||||
|
||||
@Mixin(ClientChunkManager.class)
|
||||
public interface ClientChunkManagerAccessor {
|
||||
@Accessor
|
||||
ClientChunkManager.ClientChunkMap getChunks();
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.mixin.client.gametest;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import net.minecraft.client.world.ClientChunkManager;
|
||||
|
||||
@Mixin(ClientChunkManager.ClientChunkMap.class)
|
||||
public interface ClientChunkMapAccessor {
|
||||
@Accessor
|
||||
int getCenterChunkX();
|
||||
|
||||
@Accessor
|
||||
int getCenterChunkZ();
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.mixin.client.gametest;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import net.minecraft.client.world.ClientWorld;
|
||||
|
||||
@Mixin(ClientWorld.class)
|
||||
public interface ClientWorldAccessor {
|
||||
@Accessor
|
||||
Deque<Runnable> getChunkUpdaters();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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.mixin.client.gametest;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import net.minecraft.client.gui.screen.world.CreateWorldScreen;
|
||||
import net.minecraft.client.gui.screen.world.WorldCreator;
|
||||
|
||||
@Mixin(CreateWorldScreen.class)
|
||||
public interface CreateWorldScreenAccessor {
|
||||
@Accessor
|
||||
WorldCreator getWorldCreator();
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.mixin.client.gametest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
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.CallbackInfo;
|
||||
|
||||
import net.minecraft.client.gui.screen.world.CreateWorldScreen;
|
||||
import net.minecraft.nbt.NbtCompound;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.registry.CombinedDynamicRegistries;
|
||||
import net.minecraft.registry.ServerDynamicRegistryType;
|
||||
import net.minecraft.world.level.LevelProperties;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.gametest.ClientGameTestImpl;
|
||||
import net.fabricmc.fabric.impl.client.gametest.DedicatedServerImplUtil;
|
||||
|
||||
@Mixin(CreateWorldScreen.class)
|
||||
public class CreateWorldScreenMixin {
|
||||
@Inject(method = "createLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/integrated/IntegratedServerLoader;tryLoad(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/client/gui/screen/world/CreateWorldScreen;Lcom/mojang/serialization/Lifecycle;Ljava/lang/Runnable;Z)V"), cancellable = true)
|
||||
private void createLevelDataForServers(CallbackInfo ci, @Local CombinedDynamicRegistries<ServerDynamicRegistryType> dynamicRegistries, @Local LevelProperties levelProperties) {
|
||||
if (DedicatedServerImplUtil.saveLevelDataTo != null) {
|
||||
NbtCompound levelDatInner = levelProperties.cloneWorldNbt(dynamicRegistries.getCombinedRegistryManager(), null);
|
||||
NbtCompound levelDat = new NbtCompound();
|
||||
levelDat.put("Data", levelDatInner);
|
||||
|
||||
try {
|
||||
Files.createDirectories(DedicatedServerImplUtil.saveLevelDataTo);
|
||||
NbtIo.writeCompressed(levelDat, DedicatedServerImplUtil.saveLevelDataTo.resolve("level.dat"));
|
||||
} catch (IOException e) {
|
||||
ClientGameTestImpl.LOGGER.error("Failed to save dedicated server level data", e);
|
||||
}
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,13 +24,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.gametest.ClientGameTestInputImpl;
|
||||
import net.fabricmc.fabric.impl.client.gametest.TestInputImpl;
|
||||
|
||||
@Mixin(InputUtil.class)
|
||||
public class InputUtilMixin {
|
||||
@Inject(method = "isKeyPressed", at = @At("HEAD"), cancellable = true)
|
||||
private static void useGameTestInputForKeyPressed(long window, int keyCode, CallbackInfoReturnable<Boolean> cir) {
|
||||
cir.setReturnValue(ClientGameTestInputImpl.isKeyDown(keyCode));
|
||||
cir.setReturnValue(TestInputImpl.isKeyDown(keyCode));
|
||||
}
|
||||
|
||||
@Inject(method = {"setKeyboardCallbacks", "setMouseCallbacks"}, at = @At("HEAD"), cancellable = true)
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package net.fabricmc.fabric.mixin.client.gametest;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
@ -25,14 +27,18 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
|||
|
||||
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
|
||||
|
||||
import net.fabricmc.fabric.impl.client.gametest.TestDedicatedServer;
|
||||
import net.fabricmc.fabric.impl.client.gametest.DedicatedServerImplUtil;
|
||||
|
||||
@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);
|
||||
CompletableFuture<MinecraftDedicatedServer> serverFuture = DedicatedServerImplUtil.serverFuture;
|
||||
|
||||
if (serverFuture != null) {
|
||||
serverFuture.complete((MinecraftDedicatedServer) (Object) this);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't call shutdownExecutors as we are running the dedi server within the client process.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.mixin.client.gametest;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import net.minecraft.server.Main;
|
||||
|
||||
@Mixin(Main.class)
|
||||
public class ServerMainMixin {
|
||||
@WrapWithCondition(method = "main", remap = false, at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;startTimerHack()V", remap = true))
|
||||
private static boolean dontStartAnotherTimerHack() {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
accessWidener v2 named
|
||||
accessible class net/minecraft/client/option/GameOptions$Visitor
|
||||
accessible class net/minecraft/client/world/ClientChunkManager$ClientChunkMap
|
||||
|
|
|
@ -14,10 +14,18 @@
|
|||
"MinecraftServerMixin",
|
||||
"MouseAccessor",
|
||||
"ScreenAccessor",
|
||||
"ServerMainMixin",
|
||||
"WindowMixin"
|
||||
],
|
||||
"plugin": "net.fabricmc.fabric.impl.client.gametest.ClientGameTestMixinConfigPlugin",
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
},
|
||||
"client": [
|
||||
"ClientChunkManagerAccessor",
|
||||
"ClientChunkMapAccessor",
|
||||
"ClientWorldAccessor",
|
||||
"CreateWorldScreenAccessor",
|
||||
"CreateWorldScreenMixin"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,141 +16,85 @@
|
|||
|
||||
package net.fabricmc.fabric.test.client.gametest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import org.spongepowered.asm.mixin.MixinEnvironment;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.gui.screen.ConfirmScreen;
|
||||
import net.minecraft.client.gui.screen.GameMenuScreen;
|
||||
import net.minecraft.client.gui.screen.ReconfiguringScreen;
|
||||
import net.minecraft.client.gui.screen.TitleScreen;
|
||||
import net.minecraft.client.gui.screen.multiplayer.ConnectScreen;
|
||||
import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen;
|
||||
import net.minecraft.client.gui.screen.world.CreateWorldScreen;
|
||||
import net.minecraft.client.gui.screen.world.LevelLoadingScreen;
|
||||
import net.minecraft.client.gui.screen.world.SelectWorldScreen;
|
||||
import net.minecraft.client.network.ServerAddress;
|
||||
import net.minecraft.client.network.ServerInfo;
|
||||
import net.minecraft.client.gui.screen.world.WorldCreator;
|
||||
import net.minecraft.client.option.Perspective;
|
||||
import net.minecraft.client.util.InputUtil;
|
||||
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.ClientGameTestContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
|
||||
import net.fabricmc.fabric.impl.client.gametest.TestDedicatedServer;
|
||||
import net.fabricmc.fabric.impl.client.gametest.ThreadingImpl;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestDedicatedServerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestServerConnection;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestSingleplayerContext;
|
||||
import net.fabricmc.fabric.api.client.gametest.v1.TestWorldSave;
|
||||
import net.fabricmc.fabric.test.client.gametest.mixin.TitleScreenAccessor;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
public class ClientGameTestTest implements FabricClientGameTest {
|
||||
public void runTest(ClientGameTestContext context) {
|
||||
{
|
||||
waitForTitleScreenFade(context);
|
||||
context.takeScreenshot("title_screen", 0);
|
||||
context.clickScreenButton("menu.singleplayer");
|
||||
}
|
||||
|
||||
if (!isDirEmpty(FabricLoader.getInstance().getGameDir().resolve("saves"))) {
|
||||
context.waitForScreen(SelectWorldScreen.class);
|
||||
context.takeScreenshot("select_world_screen");
|
||||
context.clickScreenButton("selectWorld.create");
|
||||
}
|
||||
TestWorldSave spWorldSave;
|
||||
try (TestSingleplayerContext singleplayer = context.worldBuilder()
|
||||
.adjustSettings(creator -> creator.setGameMode(WorldCreator.Mode.CREATIVE)).create()) {
|
||||
spWorldSave = singleplayer.getWorldSave();
|
||||
|
||||
{
|
||||
context.waitForScreen(CreateWorldScreen.class);
|
||||
context.clickScreenButton("selectWorld.gameMode");
|
||||
context.clickScreenButton("selectWorld.gameMode");
|
||||
context.takeScreenshot("create_world_screen");
|
||||
context.clickScreenButton("selectWorld.create");
|
||||
}
|
||||
|
||||
{
|
||||
// API test mods use experimental features
|
||||
context.waitForScreen(ConfirmScreen.class);
|
||||
context.clickScreenButton("gui.yes");
|
||||
}
|
||||
|
||||
{
|
||||
enableDebugHud(context);
|
||||
waitForWorldTicks(context, 200);
|
||||
context.takeScreenshot("in_game_overworld", 0);
|
||||
}
|
||||
|
||||
{
|
||||
context.getInput().pressKey(options -> options.chatKey);
|
||||
context.waitTick();
|
||||
context.getInput().typeChars("Hello, World!");
|
||||
context.getInput().pressKey(InputUtil.GLFW_KEY_ENTER);
|
||||
context.takeScreenshot("chat_message_sent", 5);
|
||||
}
|
||||
|
||||
MixinEnvironment.getCurrentEnvironment().audit();
|
||||
|
||||
{
|
||||
// See if the player render events are working.
|
||||
setPerspective(context, Perspective.THIRD_PERSON_BACK);
|
||||
context.takeScreenshot("in_game_overworld_third_person");
|
||||
setPerspective(context, Perspective.FIRST_PERSON);
|
||||
}
|
||||
|
||||
{
|
||||
context.getInput().pressKey(options -> options.inventoryKey);
|
||||
context.takeScreenshot("in_game_inventory");
|
||||
context.setScreen(() -> null);
|
||||
}
|
||||
|
||||
{
|
||||
context.setScreen(() -> new GameMenuScreen(true));
|
||||
context.takeScreenshot("game_menu");
|
||||
context.clickScreenButton("menu.returnToMenu");
|
||||
context.waitForScreen(TitleScreen.class);
|
||||
waitForServerStop(context);
|
||||
}
|
||||
|
||||
try (var server = new TestDedicatedServer()) {
|
||||
connectToServer(context, server);
|
||||
waitForWorldTicks(context, 5);
|
||||
|
||||
final GameProfile profile = context.computeOnClient(MinecraftClient::getGameProfile);
|
||||
server.runCommand("op " + profile.getName());
|
||||
server.runCommand("gamemode creative " + profile.getName());
|
||||
|
||||
waitForWorldTicks(context, 20);
|
||||
context.takeScreenshot("server_in_game", 0);
|
||||
|
||||
{ // Test that we can enter and exit configuration
|
||||
server.runCommand("debugconfig config " + profile.getName());
|
||||
context.waitForScreen(ReconfiguringScreen.class);
|
||||
context.takeScreenshot("server_config");
|
||||
server.runCommand("debugconfig unconfig " + profile.getId());
|
||||
waitForWorldTicks(context, 1);
|
||||
{
|
||||
enableDebugHud(context);
|
||||
singleplayer.getClientWorld().waitForChunksRender();
|
||||
context.takeScreenshot("in_game_overworld", 0);
|
||||
}
|
||||
|
||||
context.setScreen(() -> new GameMenuScreen(true));
|
||||
context.takeScreenshot("server_game_menu");
|
||||
context.clickScreenButton("menu.disconnect");
|
||||
{
|
||||
context.getInput().pressKey(options -> options.chatKey);
|
||||
context.waitTick();
|
||||
context.getInput().typeChars("Hello, World!");
|
||||
context.getInput().pressKey(InputUtil.GLFW_KEY_ENTER);
|
||||
context.takeScreenshot("chat_message_sent", 5);
|
||||
}
|
||||
|
||||
context.waitForScreen(MultiplayerScreen.class);
|
||||
context.clickScreenButton("gui.back");
|
||||
MixinEnvironment.getCurrentEnvironment().audit();
|
||||
|
||||
{
|
||||
// See if the player render events are working.
|
||||
setPerspective(context, Perspective.THIRD_PERSON_BACK);
|
||||
context.takeScreenshot("in_game_overworld_third_person");
|
||||
setPerspective(context, Perspective.FIRST_PERSON);
|
||||
}
|
||||
|
||||
{
|
||||
context.getInput().pressKey(options -> options.inventoryKey);
|
||||
context.takeScreenshot("in_game_inventory");
|
||||
context.setScreen(() -> null);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
context.waitForScreen(TitleScreen.class);
|
||||
try (TestSingleplayerContext singleplayer = spWorldSave.open()) {
|
||||
singleplayer.getClientWorld().waitForChunksRender();
|
||||
context.takeScreenshot("in_game_overworld_2");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDirEmpty(Path path) {
|
||||
try (DirectoryStream<Path> directory = Files.newDirectoryStream(path)) {
|
||||
return !directory.iterator().hasNext();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
try (TestDedicatedServerContext server = context.worldBuilder().createServer()) {
|
||||
try (TestServerConnection connection = server.connect()) {
|
||||
connection.getClientWorld().waitForChunksRender();
|
||||
context.takeScreenshot("server_in_game", 0);
|
||||
|
||||
{ // Test that we can enter and exit configuration
|
||||
final GameProfile profile = context.computeOnClient(MinecraftClient::getGameProfile);
|
||||
server.runCommand("debugconfig config " + profile.getName());
|
||||
context.waitForScreen(ReconfiguringScreen.class);
|
||||
context.takeScreenshot("server_config");
|
||||
server.runCommand("debugconfig unconfig " + profile.getId());
|
||||
// TODO: better way to wait for reconfiguration to end
|
||||
context.waitTicks(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,25 +111,4 @@ public class ClientGameTestTest implements FabricClientGameTest {
|
|||
private static void setPerspective(ClientGameTestContext context, Perspective perspective) {
|
||||
context.runOnClient(client -> client.options.setPerspective(perspective));
|
||||
}
|
||||
|
||||
// TODO: replace with world builder
|
||||
private static void waitForWorldTicks(ClientGameTestContext context, long ticks) {
|
||||
// Wait for the world to be loaded and get the start ticks
|
||||
context.waitFor(client -> client.world != null && !(client.currentScreen instanceof LevelLoadingScreen), 30 * SharedConstants.TICKS_PER_MINUTE);
|
||||
final long startTicks = context.computeOnClient(client -> client.world.getTime());
|
||||
context.waitFor(client -> Objects.requireNonNull(client.world).getTime() > startTicks + ticks, 10 * SharedConstants.TICKS_PER_MINUTE);
|
||||
}
|
||||
|
||||
// TODO: replace with function on TestDedicatedServer
|
||||
private static void connectToServer(ClientGameTestContext context, TestDedicatedServer server) {
|
||||
context.runOnClient(client -> {
|
||||
final var serverInfo = new ServerInfo("localhost", server.getConnectionAddress(), ServerInfo.ServerType.OTHER);
|
||||
ConnectScreen.connect(client.currentScreen, client, ServerAddress.parse(server.getConnectionAddress()), serverInfo, false, null);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: move into close methods of TestDedicatedServer and TestWorld
|
||||
private static void waitForServerStop(ClientGameTestContext context) {
|
||||
context.waitFor(client -> !ThreadingImpl.isServerRunning, SharedConstants.TICKS_PER_MINUTE);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue