From 59e25208aff28f80e35fe5fc378df6155397519d Mon Sep 17 00:00:00 2001 From: turtton Date: Thu, 27 Jun 2024 19:55:41 +0900 Subject: [PATCH 1/4] WIP: Apply patch from FabricMC/fabric#1622 --- build.gradle | 10 +- fabric-gametest-api-v1/build.gradle | 24 + .../api/gametest/v1/FabricGameTest.java | 46 ++ .../impl/gametest/FabricGameTestHelper.java | 117 ++++ .../FabricGameTestModInitializer.java | 66 +++ .../mixin/gametest/CommandManagerMixin.java | 45 ++ .../mixin/gametest/MinecraftServerMixin.java | 39 ++ .../gametest/StructureTestUtilMixin.java | 63 +++ .../mixin/gametest/TestFunctionsMixin.java | 64 +++ .../mixin/gametest/TestServerMixin.java | 34 ++ .../mixin/gametest/server/MainMixin.java | 73 +++ .../gametest/structures/empty.snbt | 522 +++++++++++++++++ .../fabric-gametest-api-v1.mixins.json | 18 + .../src/main/resources/fabric.mod.json | 33 ++ .../test/gametest/ExampleFabricTestSuite.java | 63 +++ .../test/gametest/ExampleTestSuite.java | 42 ++ .../structures/exampletestsuite.diamond.snbt | 524 ++++++++++++++++++ .../src/testmod/resources/fabric.mod.json | 14 + gradle.properties | 1 + settings.gradle | 1 + 20 files changed, 1798 insertions(+), 1 deletion(-) create mode 100644 fabric-gametest-api-v1/build.gradle create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java create mode 100644 fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt create mode 100644 fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json create mode 100644 fabric-gametest-api-v1/src/main/resources/fabric.mod.json create mode 100644 fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java create mode 100644 fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java create mode 100644 fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt create mode 100644 fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json diff --git a/build.gradle b/build.gradle index 7a179b2739..2925f13e11 100644 --- a/build.gradle +++ b/build.gradle @@ -289,11 +289,19 @@ sourceSets { testmod } +// These modules are not included in the fat jar, maven will resolve them via the pom. +def devOnlyModules = [ + "fabric-gametest-api-v1" +] + dependencies { afterEvaluate { subprojects.each { api project(path: ":${it.name}", configuration: "namedElements") - include project("${it.name}:") + + if (!(it.name in devOnlyModules)) { + include project("${it.name}:") + } testmodImplementation project("${it.name}:").sourceSets.testmod.output } diff --git a/fabric-gametest-api-v1/build.gradle b/fabric-gametest-api-v1/build.gradle new file mode 100644 index 0000000000..2d80d689bf --- /dev/null +++ b/fabric-gametest-api-v1/build.gradle @@ -0,0 +1,24 @@ +archivesBaseName = "fabric-gametest-api-v1" +version = getSubprojectVersion(project) + +moduleDependencies(project, [ + 'fabric-api-base', + 'fabric-resource-loader-v0' +]) + +loom { + runs { + gametest { + server() + name "Game Test" + vmArg "-Dfabric-api.gametest" + vmArg "-Dfabric-api.gametest.report-file=${project.buildDir}/junit.xml" + runDir "build/gametest" + + // Specific to fabric api + source sourceSets.testmod + ideConfigGenerated true + } + } +} +test.dependsOn runGametest diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java new file mode 100644 index 0000000000..8345dbcfd7 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/api/gametest/v1/FabricGameTest.java @@ -0,0 +1,46 @@ +/* + * 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.gametest.v1; + +import java.lang.reflect.Method; + +import net.minecraft.test.TestContext; + +import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper; + +/** + * This interface can be optionally implemented on your test class. + */ +public interface FabricGameTest { + /** + * Use in {@link net.minecraft.test.GameTest} structureName to use an empty 8x8 structure for the test. + */ + String EMPTY_STRUCTURE = "fabric-gametest-api-v1:empty"; + + /** + * Override this method to implement custom logic to invoke the test method. + * This can be used to run code before or after each test. + * You can also pass in custom objects into the test method if desired. + * The structure will have been placed in the world before this method is invoked. + * + * @param context The vanilla test context + * @param method The test method to invoke + */ + default void invokeTestMethod(TestContext context, Method method) { + FabricGameTestHelper.invokeTestMethod(context, method, this); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java new file mode 100644 index 0000000000..2fa207314f --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestHelper.java @@ -0,0 +1,117 @@ +/* + * 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.gametest; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.function.Consumer; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.test.GameTestBatch; +import net.minecraft.test.TestContext; +import net.minecraft.test.TestFailureLogger; +import net.minecraft.test.TestFunction; +import net.minecraft.test.TestFunctions; +import net.minecraft.test.TestServer; +import net.minecraft.test.TestUtil; +import net.minecraft.test.XmlReportingTestCompletionListener; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.world.level.storage.LevelStorage; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +public final class FabricGameTestHelper { + public static final boolean ENABLED = System.getProperty("fabric-api.gametest") != null; + + private static final Logger LOGGER = LogManager.getLogger(); + + private FabricGameTestHelper() { + } + + public static void runHeadlessServer(LevelStorage.Session session, ResourcePackManager resourcePackManager, ServerResourceManager serverResourceManager, DynamicRegistryManager.Impl registryManager) { + String reportPath = System.getProperty("fabric-api.gametest.report-file"); + + if (reportPath != null) { + try { + TestFailureLogger.setCompletionListener(new XmlReportingTestCompletionListener(new File(reportPath))); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } + } + + LOGGER.info("Starting test server"); + MinecraftServer server = TestServer.startServer(thread -> { + TestServer testServer = new TestServer(thread, session, resourcePackManager, serverResourceManager, getBatches(), BlockPos.ORIGIN, registryManager); + return testServer; + }); + } + + public static Consumer getTestMethodInvoker(Method method) { + return testContext -> { + Class testClass = method.getDeclaringClass(); + + Constructor constructor; + + try { + constructor = testClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Test class (%s) provided by (%s) must have a public default or no args constructor".formatted(testClass.getSimpleName(), FabricGameTestModInitializer.getModIdForTestClass(testClass))); + } + + Object testObject; + + try { + testObject = constructor.newInstance(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Failed to create instance of test class (%s)".formatted(testClass.getCanonicalName()), e); + } + + if (testObject instanceof FabricGameTest fabricGameTest) { + fabricGameTest.invokeTestMethod(testContext, method); + } else { + invokeTestMethod(testContext, method, testObject); + } + }; + } + + public static void invokeTestMethod(TestContext testContext, Method method, Object testObject) { + try { + method.invoke(testObject, testContext); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to invoke test method (%s) in (%s)".formatted(method.getName(), method.getDeclaringClass().getCanonicalName()), e); + } + } + + private static Collection getBatches() { + return TestUtil.createBatches(getTestFunctions()); + } + + private static Collection getTestFunctions() { + return TestFunctions.getTestFunctions(); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java new file mode 100644 index 0000000000..9ad570913c --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/FabricGameTestModInitializer.java @@ -0,0 +1,66 @@ +/* + * 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.gametest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.test.TestFunctions; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.entrypoint.EntrypointContainer; + +@ApiStatus.Internal +public final class FabricGameTestModInitializer implements ModInitializer { + private static final String ENTRYPOINT_KEY = "fabric-gametest"; + private static final Map, String> GAME_TEST_IDS = new HashMap<>(); + private static final Logger LOGGER = LogManager.getLogger(); + + @Override + public void onInitialize() { + List> entrypointContainers = FabricLoader.getInstance() + .getEntrypointContainers(ENTRYPOINT_KEY, Object.class); + + for (EntrypointContainer container : entrypointContainers) { + Class testClass = container.getEntrypoint().getClass(); + String modid = container.getProvider().getMetadata().getId(); + + if (GAME_TEST_IDS.containsKey(testClass)) { + throw new UnsupportedOperationException("Test class (%s) has already been registered with mod (%s)".formatted(testClass.getCanonicalName(), modid)); + } + + GAME_TEST_IDS.put(testClass, modid); + TestFunctions.register(testClass); + + LOGGER.debug("Registered test class {} for mod {}", testClass.getCanonicalName(), modid); + } + } + + public static String getModIdForTestClass(Class testClass) { + if (!GAME_TEST_IDS.containsKey(testClass)) { + throw new UnsupportedOperationException("The test class (%s) was not registered using the '%s' entrypoint".formatted(testClass.getCanonicalName(), ENTRYPOINT_KEY)); + } + + return GAME_TEST_IDS.get(testClass); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java new file mode 100644 index 0000000000..f6c802e466 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/CommandManagerMixin.java @@ -0,0 +1,45 @@ +/* + * 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.gametest; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.mojang.brigadier.CommandDispatcher; + +import net.minecraft.SharedConstants; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.command.TestCommand; + +@Mixin(CommandManager.class) +public abstract class CommandManagerMixin { + @Shadow + @Final + private CommandDispatcher dispatcher; + + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/command/WorldBorderCommand;register(Lcom/mojang/brigadier/CommandDispatcher;)V", shift = At.Shift.AFTER)) + private void construct(CommandManager.RegistrationEnvironment environment, CallbackInfo info) { + // Registered by vanilla when isDevelopment is enabled. + if (!SharedConstants.isDevelopment) { + TestCommand.register(this.dispatcher); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java new file mode 100644 index 0000000000..c10a15cd0f --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/MinecraftServerMixin.java @@ -0,0 +1,39 @@ +/* + * 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.gametest; + +import java.util.function.BooleanSupplier; + +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.SharedConstants; +import net.minecraft.server.MinecraftServer; +import net.minecraft.test.TestManager; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServerMixin { + @Inject(method = "tickWorlds", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;updatePlayerLatency()V", shift = At.Shift.AFTER)) + private void tickWorlds(BooleanSupplier shouldKeepTicking, CallbackInfo callbackInfo) { + // Called by vanilla when isDevelopment is enabled. + if (!SharedConstants.isDevelopment) { + TestManager.INSTANCE.tick(); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java new file mode 100644 index 0000000000..fbfc72c800 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/StructureTestUtilMixin.java @@ -0,0 +1,63 @@ +/* + * 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.gametest; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +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 com.mojang.brigadier.exceptions.CommandSyntaxException; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.resource.Resource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.structure.Structure; +import net.minecraft.test.StructureTestUtil; +import net.minecraft.util.Identifier; + +@Mixin(StructureTestUtil.class) +public abstract class StructureTestUtilMixin { + private static final String GAMETEST_STRUCTURE_PATH = "gametest/structures/"; + + // Replace the default test structure loading with something that works a bit better for mods. + @Inject(at = @At("HEAD"), method = "createStructure(Ljava/lang/String;Lnet/minecraft/server/world/ServerWorld;)Lnet/minecraft/structure/Structure;", cancellable = true) + private static void createStructure(String id, ServerWorld world, CallbackInfoReturnable cir) { + Identifier baseId = new Identifier(id); + Identifier structureId = new Identifier(baseId.getNamespace(), GAMETEST_STRUCTURE_PATH + baseId.getPath() + ".snbt"); + + try { + Resource resource = world.getServer().getResourceManager().getResource(structureId); + String snbt; + + try (InputStream inputStream = resource.getInputStream()) { + snbt = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + + NbtCompound nbtCompound = NbtHelper.method_32260(snbt); + Structure structure = world.getStructureManager().createStructure(nbtCompound); + + cir.setReturnValue(structure); + } catch (IOException | CommandSyntaxException e) { + throw new RuntimeException("Error while trying to load structure: " + structureId, e); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java new file mode 100644 index 0000000000..84d8acf5df --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestFunctionsMixin.java @@ -0,0 +1,64 @@ +/* + * 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.gametest; + +import java.lang.reflect.Method; +import java.util.Locale; + +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.test.GameTest; +import net.minecraft.test.StructureTestUtil; +import net.minecraft.test.TestFunction; +import net.minecraft.test.TestFunctions; + +import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper; +import net.fabricmc.fabric.impl.gametest.FabricGameTestModInitializer; + +@Mixin(TestFunctions.class) +public abstract class TestFunctionsMixin { + @Inject(at = @At("HEAD"), method = "getTestFunction(Ljava/lang/reflect/Method;)Lnet/minecraft/test/TestFunction;", cancellable = true) + private static void getTestFunction(Method method, CallbackInfoReturnable cir) { + GameTest gameTest = method.getAnnotation(GameTest.class); + String testSuiteName = method.getDeclaringClass().getSimpleName().toLowerCase(Locale.ROOT); + String testCaseName = testSuiteName + "." + method.getName().toLowerCase(Locale.ROOT); + + String modId = FabricGameTestModInitializer.getModIdForTestClass(method.getDeclaringClass()); + String structureName = "%s:%s".formatted(modId, testCaseName); + + if (!gameTest.structureName().isEmpty()) { + structureName = gameTest.structureName(); + } + + TestFunction testFunction = new TestFunction(gameTest.batchId(), + testCaseName, + structureName, + StructureTestUtil.getRotation(gameTest.rotation()), + gameTest.tickLimit(), + gameTest.duration(), + gameTest.required(), + gameTest.requiredSuccesses(), + gameTest.maxAttempts(), + FabricGameTestHelper.getTestMethodInvoker(method) + ); + + cir.setReturnValue(testFunction); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java new file mode 100644 index 0000000000..400261b006 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/TestServerMixin.java @@ -0,0 +1,34 @@ +/* + * 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.gametest; + +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.test.TestServer; + +@Mixin(TestServer.class) +public abstract class TestServerMixin { + @Inject(method = "isDedicated", at = @At("HEAD"), cancellable = true) + public void isDedicated(CallbackInfoReturnable cir) { + // Allow dedicated server commands to be registered. + // Should aid with mods that use this to detect if they are running on a dedicated server as well. + cir.setReturnValue(true); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java new file mode 100644 index 0000000000..545b5fe21f --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java @@ -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.mixin.gametest.server; + +import java.io.File; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +import com.mojang.authlib.GameProfileRepository; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import net.minecraft.resource.DataPackSettings; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ServerResourceManager; +import net.minecraft.server.Main; +import net.minecraft.server.dedicated.EulaReader; +import net.minecraft.server.dedicated.ServerPropertiesLoader; +import net.minecraft.util.UserCache; +import net.minecraft.util.dynamic.RegistryOps; +import net.minecraft.util.registry.DynamicRegistryManager; +import net.minecraft.world.level.storage.LevelStorage; +import net.minecraft.world.level.storage.LevelSummary; + +import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper; + +@Mixin(Main.class) +public class MainMixin { + @Redirect(method = "main", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/EulaReader;isEulaAgreedTo()Z")) + private static boolean isEulaAgreedTo(EulaReader reader) { + return FabricGameTestHelper.ENABLED || reader.isEulaAgreedTo(); + } + + @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorage$Session;readLevelProperties(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/resource/DataPackSettings;)Lnet/minecraft/world/SaveProperties;")) + private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec o1, OptionSpec o2, OptionSpec o3, OptionSpec o4, OptionSpec o5, OptionSpec o6, OptionSpec o7, OptionSpec o8, OptionSpec o9, OptionSpec o10, OptionSpec o11, OptionSpec o12, OptionSpec o13, OptionSpec o14, OptionSet optionSet, DynamicRegistryManager.Impl impl, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, DataPackSettings dataPackSettings, boolean bl, ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings2, CompletableFuture completableFuture, ServerResourceManager serverResourceManager, RegistryOps registryOps) { + if (FabricGameTestHelper.ENABLED) { + FabricGameTestHelper.runHeadlessServer(session, resourcePackManager, serverResourceManager, impl); + info.cancel(); // Do not progress in starting the normal dedicated server + } + } + + // Exit with a non-zero exit code when the server fails to start. + // Otherwise gradlew test will succeed without errors, although no tests have been run. + @Inject(method = "main", at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;fatal(Ljava/lang/String;Ljava/lang/Throwable;)V", shift = At.Shift.AFTER)) + private static void exitOnError(CallbackInfo info) { + if (FabricGameTestHelper.ENABLED) { + System.exit(-1); + } + } +} diff --git a/fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt b/fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt new file mode 100644 index 0000000000..a5c22bb576 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/resources/data/fabric-gametest-api-v1/gametest/structures/empty.snbt @@ -0,0 +1,522 @@ +{ + DataVersion: 2730, + size: [8, 8, 8], + data: [ + {pos: [0, 0, 0], state: "minecraft:air"}, + {pos: [0, 0, 1], state: "minecraft:air"}, + {pos: [0, 0, 2], state: "minecraft:air"}, + {pos: [0, 0, 3], state: "minecraft:air"}, + {pos: [0, 0, 4], state: "minecraft:air"}, + {pos: [0, 0, 5], state: "minecraft:air"}, + {pos: [0, 0, 6], state: "minecraft:air"}, + {pos: [0, 0, 7], state: "minecraft:air"}, + {pos: [1, 0, 0], state: "minecraft:air"}, + {pos: [1, 0, 1], state: "minecraft:air"}, + {pos: [1, 0, 2], state: "minecraft:air"}, + {pos: [1, 0, 3], state: "minecraft:air"}, + {pos: [1, 0, 4], state: "minecraft:air"}, + {pos: [1, 0, 5], state: "minecraft:air"}, + {pos: [1, 0, 6], state: "minecraft:air"}, + {pos: [1, 0, 7], state: "minecraft:air"}, + {pos: [2, 0, 0], state: "minecraft:air"}, + {pos: [2, 0, 1], state: "minecraft:air"}, + {pos: [2, 0, 2], state: "minecraft:air"}, + {pos: [2, 0, 3], state: "minecraft:air"}, + {pos: [2, 0, 4], state: "minecraft:air"}, + {pos: [2, 0, 5], state: "minecraft:air"}, + {pos: [2, 0, 6], state: "minecraft:air"}, + {pos: [2, 0, 7], state: "minecraft:air"}, + {pos: [3, 0, 0], state: "minecraft:air"}, + {pos: [3, 0, 1], state: "minecraft:air"}, + {pos: [3, 0, 2], state: "minecraft:air"}, + {pos: [3, 0, 3], state: "minecraft:air"}, + {pos: [3, 0, 4], state: "minecraft:air"}, + {pos: [3, 0, 5], state: "minecraft:air"}, + {pos: [3, 0, 6], state: "minecraft:air"}, + {pos: [3, 0, 7], state: "minecraft:air"}, + {pos: [4, 0, 0], state: "minecraft:air"}, + {pos: [4, 0, 1], state: "minecraft:air"}, + {pos: [4, 0, 2], state: "minecraft:air"}, + {pos: [4, 0, 3], state: "minecraft:air"}, + {pos: [4, 0, 4], state: "minecraft:air"}, + {pos: [4, 0, 5], state: "minecraft:air"}, + {pos: [4, 0, 6], state: "minecraft:air"}, + {pos: [4, 0, 7], state: "minecraft:air"}, + {pos: [5, 0, 0], state: "minecraft:air"}, + {pos: [5, 0, 1], state: "minecraft:air"}, + {pos: [5, 0, 2], state: "minecraft:air"}, + {pos: [5, 0, 3], state: "minecraft:air"}, + {pos: [5, 0, 4], state: "minecraft:air"}, + {pos: [5, 0, 5], state: "minecraft:air"}, + {pos: [5, 0, 6], state: "minecraft:air"}, + {pos: [5, 0, 7], state: "minecraft:air"}, + {pos: [6, 0, 0], state: "minecraft:air"}, + {pos: [6, 0, 1], state: "minecraft:air"}, + {pos: [6, 0, 2], state: "minecraft:air"}, + {pos: [6, 0, 3], state: "minecraft:air"}, + {pos: [6, 0, 4], state: "minecraft:air"}, + {pos: [6, 0, 5], state: "minecraft:air"}, + {pos: [6, 0, 6], state: "minecraft:air"}, + {pos: [6, 0, 7], state: "minecraft:air"}, + {pos: [7, 0, 0], state: "minecraft:air"}, + {pos: [7, 0, 1], state: "minecraft:air"}, + {pos: [7, 0, 2], state: "minecraft:air"}, + {pos: [7, 0, 3], state: "minecraft:air"}, + {pos: [7, 0, 4], state: "minecraft:air"}, + {pos: [7, 0, 5], state: "minecraft:air"}, + {pos: [7, 0, 6], state: "minecraft:air"}, + {pos: [7, 0, 7], state: "minecraft:air"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [0, 1, 5], state: "minecraft:air"}, + {pos: [0, 1, 6], state: "minecraft:air"}, + {pos: [0, 1, 7], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 5], state: "minecraft:air"}, + {pos: [1, 1, 6], state: "minecraft:air"}, + {pos: [1, 1, 7], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 5], state: "minecraft:air"}, + {pos: [2, 1, 6], state: "minecraft:air"}, + {pos: [2, 1, 7], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 5], state: "minecraft:air"}, + {pos: [3, 1, 6], state: "minecraft:air"}, + {pos: [3, 1, 7], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 5], state: "minecraft:air"}, + {pos: [4, 1, 6], state: "minecraft:air"}, + {pos: [4, 1, 7], state: "minecraft:air"}, + {pos: [5, 1, 0], state: "minecraft:air"}, + {pos: [5, 1, 1], state: "minecraft:air"}, + {pos: [5, 1, 2], state: "minecraft:air"}, + {pos: [5, 1, 3], state: "minecraft:air"}, + {pos: [5, 1, 4], state: "minecraft:air"}, + {pos: [5, 1, 5], state: "minecraft:air"}, + {pos: [5, 1, 6], state: "minecraft:air"}, + {pos: [5, 1, 7], state: "minecraft:air"}, + {pos: [6, 1, 0], state: "minecraft:air"}, + {pos: [6, 1, 1], state: "minecraft:air"}, + {pos: [6, 1, 2], state: "minecraft:air"}, + {pos: [6, 1, 3], state: "minecraft:air"}, + {pos: [6, 1, 4], state: "minecraft:air"}, + {pos: [6, 1, 5], state: "minecraft:air"}, + {pos: [6, 1, 6], state: "minecraft:air"}, + {pos: [6, 1, 7], state: "minecraft:air"}, + {pos: [7, 1, 0], state: "minecraft:air"}, + {pos: [7, 1, 1], state: "minecraft:air"}, + {pos: [7, 1, 2], state: "minecraft:air"}, + {pos: [7, 1, 3], state: "minecraft:air"}, + {pos: [7, 1, 4], state: "minecraft:air"}, + {pos: [7, 1, 5], state: "minecraft:air"}, + {pos: [7, 1, 6], state: "minecraft:air"}, + {pos: [7, 1, 7], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], state: "minecraft:air"}, + {pos: [0, 2, 5], state: "minecraft:air"}, + {pos: [0, 2, 6], state: "minecraft:air"}, + {pos: [0, 2, 7], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [1, 2, 5], state: "minecraft:air"}, + {pos: [1, 2, 6], state: "minecraft:air"}, + {pos: [1, 2, 7], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 5], state: "minecraft:air"}, + {pos: [2, 2, 6], state: "minecraft:air"}, + {pos: [2, 2, 7], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 5], state: "minecraft:air"}, + {pos: [3, 2, 6], state: "minecraft:air"}, + {pos: [3, 2, 7], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 5], state: "minecraft:air"}, + {pos: [4, 2, 6], state: "minecraft:air"}, + {pos: [4, 2, 7], state: "minecraft:air"}, + {pos: [5, 2, 0], state: "minecraft:air"}, + {pos: [5, 2, 1], state: "minecraft:air"}, + {pos: [5, 2, 2], state: "minecraft:air"}, + {pos: [5, 2, 3], state: "minecraft:air"}, + {pos: [5, 2, 4], state: "minecraft:air"}, + {pos: [5, 2, 5], state: "minecraft:air"}, + {pos: [5, 2, 6], state: "minecraft:air"}, + {pos: [5, 2, 7], state: "minecraft:air"}, + {pos: [6, 2, 0], state: "minecraft:air"}, + {pos: [6, 2, 1], state: "minecraft:air"}, + {pos: [6, 2, 2], state: "minecraft:air"}, + {pos: [6, 2, 3], state: "minecraft:air"}, + {pos: [6, 2, 4], state: "minecraft:air"}, + {pos: [6, 2, 5], state: "minecraft:air"}, + {pos: [6, 2, 6], state: "minecraft:air"}, + {pos: [6, 2, 7], state: "minecraft:air"}, + {pos: [7, 2, 0], state: "minecraft:air"}, + {pos: [7, 2, 1], state: "minecraft:air"}, + {pos: [7, 2, 2], state: "minecraft:air"}, + {pos: [7, 2, 3], state: "minecraft:air"}, + {pos: [7, 2, 4], state: "minecraft:air"}, + {pos: [7, 2, 5], state: "minecraft:air"}, + {pos: [7, 2, 6], state: "minecraft:air"}, + {pos: [7, 2, 7], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [0, 3, 5], state: "minecraft:air"}, + {pos: [0, 3, 6], state: "minecraft:air"}, + {pos: [0, 3, 7], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 5], state: "minecraft:air"}, + {pos: [1, 3, 6], state: "minecraft:air"}, + {pos: [1, 3, 7], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 5], state: "minecraft:air"}, + {pos: [2, 3, 6], state: "minecraft:air"}, + {pos: [2, 3, 7], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 5], state: "minecraft:air"}, + {pos: [3, 3, 6], state: "minecraft:air"}, + {pos: [3, 3, 7], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 5], state: "minecraft:air"}, + {pos: [4, 3, 6], state: "minecraft:air"}, + {pos: [4, 3, 7], state: "minecraft:air"}, + {pos: [5, 3, 0], state: "minecraft:air"}, + {pos: [5, 3, 1], state: "minecraft:air"}, + {pos: [5, 3, 2], state: "minecraft:air"}, + {pos: [5, 3, 3], state: "minecraft:air"}, + {pos: [5, 3, 4], state: "minecraft:air"}, + {pos: [5, 3, 5], state: "minecraft:air"}, + {pos: [5, 3, 6], state: "minecraft:air"}, + {pos: [5, 3, 7], state: "minecraft:air"}, + {pos: [6, 3, 0], state: "minecraft:air"}, + {pos: [6, 3, 1], state: "minecraft:air"}, + {pos: [6, 3, 2], state: "minecraft:air"}, + {pos: [6, 3, 3], state: "minecraft:air"}, + {pos: [6, 3, 4], state: "minecraft:air"}, + {pos: [6, 3, 5], state: "minecraft:air"}, + {pos: [6, 3, 6], state: "minecraft:air"}, + {pos: [6, 3, 7], state: "minecraft:air"}, + {pos: [7, 3, 0], state: "minecraft:air"}, + {pos: [7, 3, 1], state: "minecraft:air"}, + {pos: [7, 3, 2], state: "minecraft:air"}, + {pos: [7, 3, 3], state: "minecraft:air"}, + {pos: [7, 3, 4], state: "minecraft:air"}, + {pos: [7, 3, 5], state: "minecraft:air"}, + {pos: [7, 3, 6], state: "minecraft:air"}, + {pos: [7, 3, 7], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [0, 4, 5], state: "minecraft:air"}, + {pos: [0, 4, 6], state: "minecraft:air"}, + {pos: [0, 4, 7], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 5], state: "minecraft:air"}, + {pos: [1, 4, 6], state: "minecraft:air"}, + {pos: [1, 4, 7], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 5], state: "minecraft:air"}, + {pos: [2, 4, 6], state: "minecraft:air"}, + {pos: [2, 4, 7], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 5], state: "minecraft:air"}, + {pos: [3, 4, 6], state: "minecraft:air"}, + {pos: [3, 4, 7], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 5], state: "minecraft:air"}, + {pos: [4, 4, 6], state: "minecraft:air"}, + {pos: [4, 4, 7], state: "minecraft:air"}, + {pos: [5, 4, 0], state: "minecraft:air"}, + {pos: [5, 4, 1], state: "minecraft:air"}, + {pos: [5, 4, 2], state: "minecraft:air"}, + {pos: [5, 4, 3], state: "minecraft:air"}, + {pos: [5, 4, 4], state: "minecraft:air"}, + {pos: [5, 4, 5], state: "minecraft:air"}, + {pos: [5, 4, 6], state: "minecraft:air"}, + {pos: [5, 4, 7], state: "minecraft:air"}, + {pos: [6, 4, 0], state: "minecraft:air"}, + {pos: [6, 4, 1], state: "minecraft:air"}, + {pos: [6, 4, 2], state: "minecraft:air"}, + {pos: [6, 4, 3], state: "minecraft:air"}, + {pos: [6, 4, 4], state: "minecraft:air"}, + {pos: [6, 4, 5], state: "minecraft:air"}, + {pos: [6, 4, 6], state: "minecraft:air"}, + {pos: [6, 4, 7], state: "minecraft:air"}, + {pos: [7, 4, 0], state: "minecraft:air"}, + {pos: [7, 4, 1], state: "minecraft:air"}, + {pos: [7, 4, 2], state: "minecraft:air"}, + {pos: [7, 4, 3], state: "minecraft:air"}, + {pos: [7, 4, 4], state: "minecraft:air"}, + {pos: [7, 4, 5], state: "minecraft:air"}, + {pos: [7, 4, 6], state: "minecraft:air"}, + {pos: [7, 4, 7], state: "minecraft:air"}, + {pos: [0, 5, 0], state: "minecraft:air"}, + {pos: [0, 5, 1], state: "minecraft:air"}, + {pos: [0, 5, 2], state: "minecraft:air"}, + {pos: [0, 5, 3], state: "minecraft:air"}, + {pos: [0, 5, 4], state: "minecraft:air"}, + {pos: [0, 5, 5], state: "minecraft:air"}, + {pos: [0, 5, 6], state: "minecraft:air"}, + {pos: [0, 5, 7], state: "minecraft:air"}, + {pos: [1, 5, 0], state: "minecraft:air"}, + {pos: [1, 5, 1], state: "minecraft:air"}, + {pos: [1, 5, 2], state: "minecraft:air"}, + {pos: [1, 5, 3], state: "minecraft:air"}, + {pos: [1, 5, 4], state: "minecraft:air"}, + {pos: [1, 5, 5], state: "minecraft:air"}, + {pos: [1, 5, 6], state: "minecraft:air"}, + {pos: [1, 5, 7], state: "minecraft:air"}, + {pos: [2, 5, 0], state: "minecraft:air"}, + {pos: [2, 5, 1], state: "minecraft:air"}, + {pos: [2, 5, 2], state: "minecraft:air"}, + {pos: [2, 5, 3], state: "minecraft:air"}, + {pos: [2, 5, 4], state: "minecraft:air"}, + {pos: [2, 5, 5], state: "minecraft:air"}, + {pos: [2, 5, 6], state: "minecraft:air"}, + {pos: [2, 5, 7], state: "minecraft:air"}, + {pos: [3, 5, 0], state: "minecraft:air"}, + {pos: [3, 5, 1], state: "minecraft:air"}, + {pos: [3, 5, 2], state: "minecraft:air"}, + {pos: [3, 5, 3], state: "minecraft:air"}, + {pos: [3, 5, 4], state: "minecraft:air"}, + {pos: [3, 5, 5], state: "minecraft:air"}, + {pos: [3, 5, 6], state: "minecraft:air"}, + {pos: [3, 5, 7], state: "minecraft:air"}, + {pos: [4, 5, 0], state: "minecraft:air"}, + {pos: [4, 5, 1], state: "minecraft:air"}, + {pos: [4, 5, 2], state: "minecraft:air"}, + {pos: [4, 5, 3], state: "minecraft:air"}, + {pos: [4, 5, 4], state: "minecraft:air"}, + {pos: [4, 5, 5], state: "minecraft:air"}, + {pos: [4, 5, 6], state: "minecraft:air"}, + {pos: [4, 5, 7], state: "minecraft:air"}, + {pos: [5, 5, 0], state: "minecraft:air"}, + {pos: [5, 5, 1], state: "minecraft:air"}, + {pos: [5, 5, 2], state: "minecraft:air"}, + {pos: [5, 5, 3], state: "minecraft:air"}, + {pos: [5, 5, 4], state: "minecraft:air"}, + {pos: [5, 5, 5], state: "minecraft:air"}, + {pos: [5, 5, 6], state: "minecraft:air"}, + {pos: [5, 5, 7], state: "minecraft:air"}, + {pos: [6, 5, 0], state: "minecraft:air"}, + {pos: [6, 5, 1], state: "minecraft:air"}, + {pos: [6, 5, 2], state: "minecraft:air"}, + {pos: [6, 5, 3], state: "minecraft:air"}, + {pos: [6, 5, 4], state: "minecraft:air"}, + {pos: [6, 5, 5], state: "minecraft:air"}, + {pos: [6, 5, 6], state: "minecraft:air"}, + {pos: [6, 5, 7], state: "minecraft:air"}, + {pos: [7, 5, 0], state: "minecraft:air"}, + {pos: [7, 5, 1], state: "minecraft:air"}, + {pos: [7, 5, 2], state: "minecraft:air"}, + {pos: [7, 5, 3], state: "minecraft:air"}, + {pos: [7, 5, 4], state: "minecraft:air"}, + {pos: [7, 5, 5], state: "minecraft:air"}, + {pos: [7, 5, 6], state: "minecraft:air"}, + {pos: [7, 5, 7], state: "minecraft:air"}, + {pos: [0, 6, 0], state: "minecraft:air"}, + {pos: [0, 6, 1], state: "minecraft:air"}, + {pos: [0, 6, 2], state: "minecraft:air"}, + {pos: [0, 6, 3], state: "minecraft:air"}, + {pos: [0, 6, 4], state: "minecraft:air"}, + {pos: [0, 6, 5], state: "minecraft:air"}, + {pos: [0, 6, 6], state: "minecraft:air"}, + {pos: [0, 6, 7], state: "minecraft:air"}, + {pos: [1, 6, 0], state: "minecraft:air"}, + {pos: [1, 6, 1], state: "minecraft:air"}, + {pos: [1, 6, 2], state: "minecraft:air"}, + {pos: [1, 6, 3], state: "minecraft:air"}, + {pos: [1, 6, 4], state: "minecraft:air"}, + {pos: [1, 6, 5], state: "minecraft:air"}, + {pos: [1, 6, 6], state: "minecraft:air"}, + {pos: [1, 6, 7], state: "minecraft:air"}, + {pos: [2, 6, 0], state: "minecraft:air"}, + {pos: [2, 6, 1], state: "minecraft:air"}, + {pos: [2, 6, 2], state: "minecraft:air"}, + {pos: [2, 6, 3], state: "minecraft:air"}, + {pos: [2, 6, 4], state: "minecraft:air"}, + {pos: [2, 6, 5], state: "minecraft:air"}, + {pos: [2, 6, 6], state: "minecraft:air"}, + {pos: [2, 6, 7], state: "minecraft:air"}, + {pos: [3, 6, 0], state: "minecraft:air"}, + {pos: [3, 6, 1], state: "minecraft:air"}, + {pos: [3, 6, 2], state: "minecraft:air"}, + {pos: [3, 6, 3], state: "minecraft:air"}, + {pos: [3, 6, 4], state: "minecraft:air"}, + {pos: [3, 6, 5], state: "minecraft:air"}, + {pos: [3, 6, 6], state: "minecraft:air"}, + {pos: [3, 6, 7], state: "minecraft:air"}, + {pos: [4, 6, 0], state: "minecraft:air"}, + {pos: [4, 6, 1], state: "minecraft:air"}, + {pos: [4, 6, 2], state: "minecraft:air"}, + {pos: [4, 6, 3], state: "minecraft:air"}, + {pos: [4, 6, 4], state: "minecraft:air"}, + {pos: [4, 6, 5], state: "minecraft:air"}, + {pos: [4, 6, 6], state: "minecraft:air"}, + {pos: [4, 6, 7], state: "minecraft:air"}, + {pos: [5, 6, 0], state: "minecraft:air"}, + {pos: [5, 6, 1], state: "minecraft:air"}, + {pos: [5, 6, 2], state: "minecraft:air"}, + {pos: [5, 6, 3], state: "minecraft:air"}, + {pos: [5, 6, 4], state: "minecraft:air"}, + {pos: [5, 6, 5], state: "minecraft:air"}, + {pos: [5, 6, 6], state: "minecraft:air"}, + {pos: [5, 6, 7], state: "minecraft:air"}, + {pos: [6, 6, 0], state: "minecraft:air"}, + {pos: [6, 6, 1], state: "minecraft:air"}, + {pos: [6, 6, 2], state: "minecraft:air"}, + {pos: [6, 6, 3], state: "minecraft:air"}, + {pos: [6, 6, 4], state: "minecraft:air"}, + {pos: [6, 6, 5], state: "minecraft:air"}, + {pos: [6, 6, 6], state: "minecraft:air"}, + {pos: [6, 6, 7], state: "minecraft:air"}, + {pos: [7, 6, 0], state: "minecraft:air"}, + {pos: [7, 6, 1], state: "minecraft:air"}, + {pos: [7, 6, 2], state: "minecraft:air"}, + {pos: [7, 6, 3], state: "minecraft:air"}, + {pos: [7, 6, 4], state: "minecraft:air"}, + {pos: [7, 6, 5], state: "minecraft:air"}, + {pos: [7, 6, 6], state: "minecraft:air"}, + {pos: [7, 6, 7], state: "minecraft:air"}, + {pos: [0, 7, 0], state: "minecraft:air"}, + {pos: [0, 7, 1], state: "minecraft:air"}, + {pos: [0, 7, 2], state: "minecraft:air"}, + {pos: [0, 7, 3], state: "minecraft:air"}, + {pos: [0, 7, 4], state: "minecraft:air"}, + {pos: [0, 7, 5], state: "minecraft:air"}, + {pos: [0, 7, 6], state: "minecraft:air"}, + {pos: [0, 7, 7], state: "minecraft:air"}, + {pos: [1, 7, 0], state: "minecraft:air"}, + {pos: [1, 7, 1], state: "minecraft:air"}, + {pos: [1, 7, 2], state: "minecraft:air"}, + {pos: [1, 7, 3], state: "minecraft:air"}, + {pos: [1, 7, 4], state: "minecraft:air"}, + {pos: [1, 7, 5], state: "minecraft:air"}, + {pos: [1, 7, 6], state: "minecraft:air"}, + {pos: [1, 7, 7], state: "minecraft:air"}, + {pos: [2, 7, 0], state: "minecraft:air"}, + {pos: [2, 7, 1], state: "minecraft:air"}, + {pos: [2, 7, 2], state: "minecraft:air"}, + {pos: [2, 7, 3], state: "minecraft:air"}, + {pos: [2, 7, 4], state: "minecraft:air"}, + {pos: [2, 7, 5], state: "minecraft:air"}, + {pos: [2, 7, 6], state: "minecraft:air"}, + {pos: [2, 7, 7], state: "minecraft:air"}, + {pos: [3, 7, 0], state: "minecraft:air"}, + {pos: [3, 7, 1], state: "minecraft:air"}, + {pos: [3, 7, 2], state: "minecraft:air"}, + {pos: [3, 7, 3], state: "minecraft:air"}, + {pos: [3, 7, 4], state: "minecraft:air"}, + {pos: [3, 7, 5], state: "minecraft:air"}, + {pos: [3, 7, 6], state: "minecraft:air"}, + {pos: [3, 7, 7], state: "minecraft:air"}, + {pos: [4, 7, 0], state: "minecraft:air"}, + {pos: [4, 7, 1], state: "minecraft:air"}, + {pos: [4, 7, 2], state: "minecraft:air"}, + {pos: [4, 7, 3], state: "minecraft:air"}, + {pos: [4, 7, 4], state: "minecraft:air"}, + {pos: [4, 7, 5], state: "minecraft:air"}, + {pos: [4, 7, 6], state: "minecraft:air"}, + {pos: [4, 7, 7], state: "minecraft:air"}, + {pos: [5, 7, 0], state: "minecraft:air"}, + {pos: [5, 7, 1], state: "minecraft:air"}, + {pos: [5, 7, 2], state: "minecraft:air"}, + {pos: [5, 7, 3], state: "minecraft:air"}, + {pos: [5, 7, 4], state: "minecraft:air"}, + {pos: [5, 7, 5], state: "minecraft:air"}, + {pos: [5, 7, 6], state: "minecraft:air"}, + {pos: [5, 7, 7], state: "minecraft:air"}, + {pos: [6, 7, 0], state: "minecraft:air"}, + {pos: [6, 7, 1], state: "minecraft:air"}, + {pos: [6, 7, 2], state: "minecraft:air"}, + {pos: [6, 7, 3], state: "minecraft:air"}, + {pos: [6, 7, 4], state: "minecraft:air"}, + {pos: [6, 7, 5], state: "minecraft:air"}, + {pos: [6, 7, 6], state: "minecraft:air"}, + {pos: [6, 7, 7], state: "minecraft:air"}, + {pos: [7, 7, 0], state: "minecraft:air"}, + {pos: [7, 7, 1], state: "minecraft:air"}, + {pos: [7, 7, 2], state: "minecraft:air"}, + {pos: [7, 7, 3], state: "minecraft:air"}, + {pos: [7, 7, 4], state: "minecraft:air"}, + {pos: [7, 7, 5], state: "minecraft:air"}, + {pos: [7, 7, 6], state: "minecraft:air"}, + {pos: [7, 7, 7], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:air" + ] +} diff --git a/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json new file mode 100644 index 0000000000..1ae56b7a0b --- /dev/null +++ b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.gametest", + "compatibilityLevel": "JAVA_16", + "mixins": [ + "CommandManagerMixin", + "MinecraftServerMixin", + "StructureTestUtilMixin", + "TestFunctionsMixin", + "TestServerMixin" + ], + "server": [ + "server.MainMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-gametest-api-v1/src/main/resources/fabric.mod.json b/fabric-gametest-api-v1/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..1a52716340 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,33 @@ +{ + "schemaVersion": 1, + "id": "fabric-gametest-api-v1", + "name": "Fabric Game Test API (v1)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-gametest-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "entrypoints": { + "main" : [ + "net.fabricmc.fabric.impl.gametest.FabricGameTestModInitializer" + ] + }, + "depends": { + "fabric-resource-loader-v0": "*" + }, + "description": "Allows registration of custom game tests.", + "mixins": [ + "fabric-gametest-api-v1.mixins.json" + ], + "custom": { + "fabric-api:module-lifecycle": "stable" + } +} diff --git a/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java new file mode 100644 index 0000000000..95ce2c844b --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleFabricTestSuite.java @@ -0,0 +1,63 @@ +/* + * 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.gametest; + +import java.lang.reflect.Method; + +import net.minecraft.block.Blocks; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +// optional to impl FabricGameTest +public class ExampleFabricTestSuite implements FabricGameTest { + /** + * By overriding invokeTestMethod you can wrap the method call. + * This can be used as shown to run code before and after each test. + */ + @Override + public void invokeTestMethod(TestContext context, Method method) { + beforeEach(context); + + FabricGameTest.super.invokeTestMethod(context, method); + + afterEach(context); + } + + private void beforeEach(TestContext context) { + System.out.println("Hello beforeEach"); + context.setBlockState(0, 5, 0, Blocks.GOLD_BLOCK); + } + + private void afterEach(TestContext context) { + context.addInstantFinalTask(() -> + context.checkBlock(new BlockPos(0, 2, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be gold") + ); + } + + @GameTest(structureName = "fabric-gametest-api-v1-testmod:exampletestsuite.diamond") + public void diamond(TestContext context) { + // Nothing to do as the structure placed the block. + } + + @GameTest(structureName = EMPTY_STRUCTURE) + public void noStructure(TestContext context) { + context.setBlockState(0, 2, 0, Blocks.DIAMOND_BLOCK); + } +} diff --git a/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java new file mode 100644 index 0000000000..795c3227e6 --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/java/net/fabricmc/fabric/test/gametest/ExampleTestSuite.java @@ -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.test.gametest; + +import net.minecraft.block.Blocks; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestContext; +import net.minecraft.util.math.BlockPos; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +public class ExampleTestSuite { + @GameTest + public void diamond(TestContext context) { + context.addInstantFinalTask(() -> + context.checkBlock(new BlockPos(0, 2, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be diamond") + ); + } + + @GameTest(structureName = FabricGameTest.EMPTY_STRUCTURE) + public void noStructure(TestContext context) { + context.setBlockState(0, 2, 0, Blocks.DIAMOND_BLOCK); + + context.addInstantFinalTask(() -> + context.checkBlock(new BlockPos(0, 2, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be diamond") + ); + } +} diff --git a/fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt b/fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt new file mode 100644 index 0000000000..7ec6d74332 --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/resources/data/fabric-gametest-api-v1-testmod/gametest/structures/exampletestsuite.diamond.snbt @@ -0,0 +1,524 @@ +{ + DataVersion: 2730, + size: [8, 8, 8], + data: [ + {pos: [0, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [0, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [1, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [2, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [3, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [4, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [5, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [6, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 0], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 1], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 2], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 3], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 4], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 5], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 6], state: "minecraft:polished_andesite"}, + {pos: [7, 0, 7], state: "minecraft:polished_andesite"}, + {pos: [0, 1, 0], state: "minecraft:diamond_block"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [0, 1, 5], state: "minecraft:air"}, + {pos: [0, 1, 6], state: "minecraft:air"}, + {pos: [0, 1, 7], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 5], state: "minecraft:air"}, + {pos: [1, 1, 6], state: "minecraft:air"}, + {pos: [1, 1, 7], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 5], state: "minecraft:air"}, + {pos: [2, 1, 6], state: "minecraft:air"}, + {pos: [2, 1, 7], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 5], state: "minecraft:air"}, + {pos: [3, 1, 6], state: "minecraft:air"}, + {pos: [3, 1, 7], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 5], state: "minecraft:air"}, + {pos: [4, 1, 6], state: "minecraft:air"}, + {pos: [4, 1, 7], state: "minecraft:air"}, + {pos: [5, 1, 0], state: "minecraft:air"}, + {pos: [5, 1, 1], state: "minecraft:air"}, + {pos: [5, 1, 2], state: "minecraft:air"}, + {pos: [5, 1, 3], state: "minecraft:air"}, + {pos: [5, 1, 4], state: "minecraft:air"}, + {pos: [5, 1, 5], state: "minecraft:air"}, + {pos: [5, 1, 6], state: "minecraft:air"}, + {pos: [5, 1, 7], state: "minecraft:air"}, + {pos: [6, 1, 0], state: "minecraft:air"}, + {pos: [6, 1, 1], state: "minecraft:air"}, + {pos: [6, 1, 2], state: "minecraft:air"}, + {pos: [6, 1, 3], state: "minecraft:air"}, + {pos: [6, 1, 4], state: "minecraft:air"}, + {pos: [6, 1, 5], state: "minecraft:air"}, + {pos: [6, 1, 6], state: "minecraft:air"}, + {pos: [6, 1, 7], state: "minecraft:air"}, + {pos: [7, 1, 0], state: "minecraft:air"}, + {pos: [7, 1, 1], state: "minecraft:air"}, + {pos: [7, 1, 2], state: "minecraft:air"}, + {pos: [7, 1, 3], state: "minecraft:air"}, + {pos: [7, 1, 4], state: "minecraft:air"}, + {pos: [7, 1, 5], state: "minecraft:air"}, + {pos: [7, 1, 6], state: "minecraft:air"}, + {pos: [7, 1, 7], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], state: "minecraft:air"}, + {pos: [0, 2, 5], state: "minecraft:air"}, + {pos: [0, 2, 6], state: "minecraft:air"}, + {pos: [0, 2, 7], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [1, 2, 5], state: "minecraft:air"}, + {pos: [1, 2, 6], state: "minecraft:air"}, + {pos: [1, 2, 7], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 5], state: "minecraft:air"}, + {pos: [2, 2, 6], state: "minecraft:air"}, + {pos: [2, 2, 7], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 5], state: "minecraft:air"}, + {pos: [3, 2, 6], state: "minecraft:air"}, + {pos: [3, 2, 7], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 5], state: "minecraft:air"}, + {pos: [4, 2, 6], state: "minecraft:air"}, + {pos: [4, 2, 7], state: "minecraft:air"}, + {pos: [5, 2, 0], state: "minecraft:air"}, + {pos: [5, 2, 1], state: "minecraft:air"}, + {pos: [5, 2, 2], state: "minecraft:air"}, + {pos: [5, 2, 3], state: "minecraft:air"}, + {pos: [5, 2, 4], state: "minecraft:air"}, + {pos: [5, 2, 5], state: "minecraft:air"}, + {pos: [5, 2, 6], state: "minecraft:air"}, + {pos: [5, 2, 7], state: "minecraft:air"}, + {pos: [6, 2, 0], state: "minecraft:air"}, + {pos: [6, 2, 1], state: "minecraft:air"}, + {pos: [6, 2, 2], state: "minecraft:air"}, + {pos: [6, 2, 3], state: "minecraft:air"}, + {pos: [6, 2, 4], state: "minecraft:air"}, + {pos: [6, 2, 5], state: "minecraft:air"}, + {pos: [6, 2, 6], state: "minecraft:air"}, + {pos: [6, 2, 7], state: "minecraft:air"}, + {pos: [7, 2, 0], state: "minecraft:air"}, + {pos: [7, 2, 1], state: "minecraft:air"}, + {pos: [7, 2, 2], state: "minecraft:air"}, + {pos: [7, 2, 3], state: "minecraft:air"}, + {pos: [7, 2, 4], state: "minecraft:air"}, + {pos: [7, 2, 5], state: "minecraft:air"}, + {pos: [7, 2, 6], state: "minecraft:air"}, + {pos: [7, 2, 7], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [0, 3, 5], state: "minecraft:air"}, + {pos: [0, 3, 6], state: "minecraft:air"}, + {pos: [0, 3, 7], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 5], state: "minecraft:air"}, + {pos: [1, 3, 6], state: "minecraft:air"}, + {pos: [1, 3, 7], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 5], state: "minecraft:air"}, + {pos: [2, 3, 6], state: "minecraft:air"}, + {pos: [2, 3, 7], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 5], state: "minecraft:air"}, + {pos: [3, 3, 6], state: "minecraft:air"}, + {pos: [3, 3, 7], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 5], state: "minecraft:air"}, + {pos: [4, 3, 6], state: "minecraft:air"}, + {pos: [4, 3, 7], state: "minecraft:air"}, + {pos: [5, 3, 0], state: "minecraft:air"}, + {pos: [5, 3, 1], state: "minecraft:air"}, + {pos: [5, 3, 2], state: "minecraft:air"}, + {pos: [5, 3, 3], state: "minecraft:air"}, + {pos: [5, 3, 4], state: "minecraft:air"}, + {pos: [5, 3, 5], state: "minecraft:air"}, + {pos: [5, 3, 6], state: "minecraft:air"}, + {pos: [5, 3, 7], state: "minecraft:air"}, + {pos: [6, 3, 0], state: "minecraft:air"}, + {pos: [6, 3, 1], state: "minecraft:air"}, + {pos: [6, 3, 2], state: "minecraft:air"}, + {pos: [6, 3, 3], state: "minecraft:air"}, + {pos: [6, 3, 4], state: "minecraft:air"}, + {pos: [6, 3, 5], state: "minecraft:air"}, + {pos: [6, 3, 6], state: "minecraft:air"}, + {pos: [6, 3, 7], state: "minecraft:air"}, + {pos: [7, 3, 0], state: "minecraft:air"}, + {pos: [7, 3, 1], state: "minecraft:air"}, + {pos: [7, 3, 2], state: "minecraft:air"}, + {pos: [7, 3, 3], state: "minecraft:air"}, + {pos: [7, 3, 4], state: "minecraft:air"}, + {pos: [7, 3, 5], state: "minecraft:air"}, + {pos: [7, 3, 6], state: "minecraft:air"}, + {pos: [7, 3, 7], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [0, 4, 5], state: "minecraft:air"}, + {pos: [0, 4, 6], state: "minecraft:air"}, + {pos: [0, 4, 7], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 5], state: "minecraft:air"}, + {pos: [1, 4, 6], state: "minecraft:air"}, + {pos: [1, 4, 7], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 5], state: "minecraft:air"}, + {pos: [2, 4, 6], state: "minecraft:air"}, + {pos: [2, 4, 7], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 5], state: "minecraft:air"}, + {pos: [3, 4, 6], state: "minecraft:air"}, + {pos: [3, 4, 7], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 5], state: "minecraft:air"}, + {pos: [4, 4, 6], state: "minecraft:air"}, + {pos: [4, 4, 7], state: "minecraft:air"}, + {pos: [5, 4, 0], state: "minecraft:air"}, + {pos: [5, 4, 1], state: "minecraft:air"}, + {pos: [5, 4, 2], state: "minecraft:air"}, + {pos: [5, 4, 3], state: "minecraft:air"}, + {pos: [5, 4, 4], state: "minecraft:air"}, + {pos: [5, 4, 5], state: "minecraft:air"}, + {pos: [5, 4, 6], state: "minecraft:air"}, + {pos: [5, 4, 7], state: "minecraft:air"}, + {pos: [6, 4, 0], state: "minecraft:air"}, + {pos: [6, 4, 1], state: "minecraft:air"}, + {pos: [6, 4, 2], state: "minecraft:air"}, + {pos: [6, 4, 3], state: "minecraft:air"}, + {pos: [6, 4, 4], state: "minecraft:air"}, + {pos: [6, 4, 5], state: "minecraft:air"}, + {pos: [6, 4, 6], state: "minecraft:air"}, + {pos: [6, 4, 7], state: "minecraft:air"}, + {pos: [7, 4, 0], state: "minecraft:air"}, + {pos: [7, 4, 1], state: "minecraft:air"}, + {pos: [7, 4, 2], state: "minecraft:air"}, + {pos: [7, 4, 3], state: "minecraft:air"}, + {pos: [7, 4, 4], state: "minecraft:air"}, + {pos: [7, 4, 5], state: "minecraft:air"}, + {pos: [7, 4, 6], state: "minecraft:air"}, + {pos: [7, 4, 7], state: "minecraft:air"}, + {pos: [0, 5, 0], state: "minecraft:air"}, + {pos: [0, 5, 1], state: "minecraft:air"}, + {pos: [0, 5, 2], state: "minecraft:air"}, + {pos: [0, 5, 3], state: "minecraft:air"}, + {pos: [0, 5, 4], state: "minecraft:air"}, + {pos: [0, 5, 5], state: "minecraft:air"}, + {pos: [0, 5, 6], state: "minecraft:air"}, + {pos: [0, 5, 7], state: "minecraft:air"}, + {pos: [1, 5, 0], state: "minecraft:air"}, + {pos: [1, 5, 1], state: "minecraft:air"}, + {pos: [1, 5, 2], state: "minecraft:air"}, + {pos: [1, 5, 3], state: "minecraft:air"}, + {pos: [1, 5, 4], state: "minecraft:air"}, + {pos: [1, 5, 5], state: "minecraft:air"}, + {pos: [1, 5, 6], state: "minecraft:air"}, + {pos: [1, 5, 7], state: "minecraft:air"}, + {pos: [2, 5, 0], state: "minecraft:air"}, + {pos: [2, 5, 1], state: "minecraft:air"}, + {pos: [2, 5, 2], state: "minecraft:air"}, + {pos: [2, 5, 3], state: "minecraft:air"}, + {pos: [2, 5, 4], state: "minecraft:air"}, + {pos: [2, 5, 5], state: "minecraft:air"}, + {pos: [2, 5, 6], state: "minecraft:air"}, + {pos: [2, 5, 7], state: "minecraft:air"}, + {pos: [3, 5, 0], state: "minecraft:air"}, + {pos: [3, 5, 1], state: "minecraft:air"}, + {pos: [3, 5, 2], state: "minecraft:air"}, + {pos: [3, 5, 3], state: "minecraft:air"}, + {pos: [3, 5, 4], state: "minecraft:air"}, + {pos: [3, 5, 5], state: "minecraft:air"}, + {pos: [3, 5, 6], state: "minecraft:air"}, + {pos: [3, 5, 7], state: "minecraft:air"}, + {pos: [4, 5, 0], state: "minecraft:air"}, + {pos: [4, 5, 1], state: "minecraft:air"}, + {pos: [4, 5, 2], state: "minecraft:air"}, + {pos: [4, 5, 3], state: "minecraft:air"}, + {pos: [4, 5, 4], state: "minecraft:air"}, + {pos: [4, 5, 5], state: "minecraft:air"}, + {pos: [4, 5, 6], state: "minecraft:air"}, + {pos: [4, 5, 7], state: "minecraft:air"}, + {pos: [5, 5, 0], state: "minecraft:air"}, + {pos: [5, 5, 1], state: "minecraft:air"}, + {pos: [5, 5, 2], state: "minecraft:air"}, + {pos: [5, 5, 3], state: "minecraft:air"}, + {pos: [5, 5, 4], state: "minecraft:air"}, + {pos: [5, 5, 5], state: "minecraft:air"}, + {pos: [5, 5, 6], state: "minecraft:air"}, + {pos: [5, 5, 7], state: "minecraft:air"}, + {pos: [6, 5, 0], state: "minecraft:air"}, + {pos: [6, 5, 1], state: "minecraft:air"}, + {pos: [6, 5, 2], state: "minecraft:air"}, + {pos: [6, 5, 3], state: "minecraft:air"}, + {pos: [6, 5, 4], state: "minecraft:air"}, + {pos: [6, 5, 5], state: "minecraft:air"}, + {pos: [6, 5, 6], state: "minecraft:air"}, + {pos: [6, 5, 7], state: "minecraft:air"}, + {pos: [7, 5, 0], state: "minecraft:air"}, + {pos: [7, 5, 1], state: "minecraft:air"}, + {pos: [7, 5, 2], state: "minecraft:air"}, + {pos: [7, 5, 3], state: "minecraft:air"}, + {pos: [7, 5, 4], state: "minecraft:air"}, + {pos: [7, 5, 5], state: "minecraft:air"}, + {pos: [7, 5, 6], state: "minecraft:air"}, + {pos: [7, 5, 7], state: "minecraft:air"}, + {pos: [0, 6, 0], state: "minecraft:air"}, + {pos: [0, 6, 1], state: "minecraft:air"}, + {pos: [0, 6, 2], state: "minecraft:air"}, + {pos: [0, 6, 3], state: "minecraft:air"}, + {pos: [0, 6, 4], state: "minecraft:air"}, + {pos: [0, 6, 5], state: "minecraft:air"}, + {pos: [0, 6, 6], state: "minecraft:air"}, + {pos: [0, 6, 7], state: "minecraft:air"}, + {pos: [1, 6, 0], state: "minecraft:air"}, + {pos: [1, 6, 1], state: "minecraft:air"}, + {pos: [1, 6, 2], state: "minecraft:air"}, + {pos: [1, 6, 3], state: "minecraft:air"}, + {pos: [1, 6, 4], state: "minecraft:air"}, + {pos: [1, 6, 5], state: "minecraft:air"}, + {pos: [1, 6, 6], state: "minecraft:air"}, + {pos: [1, 6, 7], state: "minecraft:air"}, + {pos: [2, 6, 0], state: "minecraft:air"}, + {pos: [2, 6, 1], state: "minecraft:air"}, + {pos: [2, 6, 2], state: "minecraft:air"}, + {pos: [2, 6, 3], state: "minecraft:air"}, + {pos: [2, 6, 4], state: "minecraft:air"}, + {pos: [2, 6, 5], state: "minecraft:air"}, + {pos: [2, 6, 6], state: "minecraft:air"}, + {pos: [2, 6, 7], state: "minecraft:air"}, + {pos: [3, 6, 0], state: "minecraft:air"}, + {pos: [3, 6, 1], state: "minecraft:air"}, + {pos: [3, 6, 2], state: "minecraft:air"}, + {pos: [3, 6, 3], state: "minecraft:air"}, + {pos: [3, 6, 4], state: "minecraft:air"}, + {pos: [3, 6, 5], state: "minecraft:air"}, + {pos: [3, 6, 6], state: "minecraft:air"}, + {pos: [3, 6, 7], state: "minecraft:air"}, + {pos: [4, 6, 0], state: "minecraft:air"}, + {pos: [4, 6, 1], state: "minecraft:air"}, + {pos: [4, 6, 2], state: "minecraft:air"}, + {pos: [4, 6, 3], state: "minecraft:air"}, + {pos: [4, 6, 4], state: "minecraft:air"}, + {pos: [4, 6, 5], state: "minecraft:air"}, + {pos: [4, 6, 6], state: "minecraft:air"}, + {pos: [4, 6, 7], state: "minecraft:air"}, + {pos: [5, 6, 0], state: "minecraft:air"}, + {pos: [5, 6, 1], state: "minecraft:air"}, + {pos: [5, 6, 2], state: "minecraft:air"}, + {pos: [5, 6, 3], state: "minecraft:air"}, + {pos: [5, 6, 4], state: "minecraft:air"}, + {pos: [5, 6, 5], state: "minecraft:air"}, + {pos: [5, 6, 6], state: "minecraft:air"}, + {pos: [5, 6, 7], state: "minecraft:air"}, + {pos: [6, 6, 0], state: "minecraft:air"}, + {pos: [6, 6, 1], state: "minecraft:air"}, + {pos: [6, 6, 2], state: "minecraft:air"}, + {pos: [6, 6, 3], state: "minecraft:air"}, + {pos: [6, 6, 4], state: "minecraft:air"}, + {pos: [6, 6, 5], state: "minecraft:air"}, + {pos: [6, 6, 6], state: "minecraft:air"}, + {pos: [6, 6, 7], state: "minecraft:air"}, + {pos: [7, 6, 0], state: "minecraft:air"}, + {pos: [7, 6, 1], state: "minecraft:air"}, + {pos: [7, 6, 2], state: "minecraft:air"}, + {pos: [7, 6, 3], state: "minecraft:air"}, + {pos: [7, 6, 4], state: "minecraft:air"}, + {pos: [7, 6, 5], state: "minecraft:air"}, + {pos: [7, 6, 6], state: "minecraft:air"}, + {pos: [7, 6, 7], state: "minecraft:air"}, + {pos: [0, 7, 0], state: "minecraft:air"}, + {pos: [0, 7, 1], state: "minecraft:air"}, + {pos: [0, 7, 2], state: "minecraft:air"}, + {pos: [0, 7, 3], state: "minecraft:air"}, + {pos: [0, 7, 4], state: "minecraft:air"}, + {pos: [0, 7, 5], state: "minecraft:air"}, + {pos: [0, 7, 6], state: "minecraft:air"}, + {pos: [0, 7, 7], state: "minecraft:air"}, + {pos: [1, 7, 0], state: "minecraft:air"}, + {pos: [1, 7, 1], state: "minecraft:air"}, + {pos: [1, 7, 2], state: "minecraft:air"}, + {pos: [1, 7, 3], state: "minecraft:air"}, + {pos: [1, 7, 4], state: "minecraft:air"}, + {pos: [1, 7, 5], state: "minecraft:air"}, + {pos: [1, 7, 6], state: "minecraft:air"}, + {pos: [1, 7, 7], state: "minecraft:air"}, + {pos: [2, 7, 0], state: "minecraft:air"}, + {pos: [2, 7, 1], state: "minecraft:air"}, + {pos: [2, 7, 2], state: "minecraft:air"}, + {pos: [2, 7, 3], state: "minecraft:air"}, + {pos: [2, 7, 4], state: "minecraft:air"}, + {pos: [2, 7, 5], state: "minecraft:air"}, + {pos: [2, 7, 6], state: "minecraft:air"}, + {pos: [2, 7, 7], state: "minecraft:air"}, + {pos: [3, 7, 0], state: "minecraft:air"}, + {pos: [3, 7, 1], state: "minecraft:air"}, + {pos: [3, 7, 2], state: "minecraft:air"}, + {pos: [3, 7, 3], state: "minecraft:air"}, + {pos: [3, 7, 4], state: "minecraft:air"}, + {pos: [3, 7, 5], state: "minecraft:air"}, + {pos: [3, 7, 6], state: "minecraft:air"}, + {pos: [3, 7, 7], state: "minecraft:air"}, + {pos: [4, 7, 0], state: "minecraft:air"}, + {pos: [4, 7, 1], state: "minecraft:air"}, + {pos: [4, 7, 2], state: "minecraft:air"}, + {pos: [4, 7, 3], state: "minecraft:air"}, + {pos: [4, 7, 4], state: "minecraft:air"}, + {pos: [4, 7, 5], state: "minecraft:air"}, + {pos: [4, 7, 6], state: "minecraft:air"}, + {pos: [4, 7, 7], state: "minecraft:air"}, + {pos: [5, 7, 0], state: "minecraft:air"}, + {pos: [5, 7, 1], state: "minecraft:air"}, + {pos: [5, 7, 2], state: "minecraft:air"}, + {pos: [5, 7, 3], state: "minecraft:air"}, + {pos: [5, 7, 4], state: "minecraft:air"}, + {pos: [5, 7, 5], state: "minecraft:air"}, + {pos: [5, 7, 6], state: "minecraft:air"}, + {pos: [5, 7, 7], state: "minecraft:air"}, + {pos: [6, 7, 0], state: "minecraft:air"}, + {pos: [6, 7, 1], state: "minecraft:air"}, + {pos: [6, 7, 2], state: "minecraft:air"}, + {pos: [6, 7, 3], state: "minecraft:air"}, + {pos: [6, 7, 4], state: "minecraft:air"}, + {pos: [6, 7, 5], state: "minecraft:air"}, + {pos: [6, 7, 6], state: "minecraft:air"}, + {pos: [6, 7, 7], state: "minecraft:air"}, + {pos: [7, 7, 0], state: "minecraft:air"}, + {pos: [7, 7, 1], state: "minecraft:air"}, + {pos: [7, 7, 2], state: "minecraft:air"}, + {pos: [7, 7, 3], state: "minecraft:air"}, + {pos: [7, 7, 4], state: "minecraft:air"}, + {pos: [7, 7, 5], state: "minecraft:air"}, + {pos: [7, 7, 6], state: "minecraft:air"}, + {pos: [7, 7, 7], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:polished_andesite", + "minecraft:diamond_block", + "minecraft:air" + ] +} diff --git a/fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json b/fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..078fb5752c --- /dev/null +++ b/fabric-gametest-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,14 @@ +{ + "schemaVersion": 1, + "id": "fabric-gametest-api-v1-testmod", + "name": "Fabric Game Test API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "entrypoints": { + "fabric-gametest" : [ + "net.fabricmc.fabric.test.gametest.ExampleFabricTestSuite", + "net.fabricmc.fabric.test.gametest.ExampleTestSuite" + ] + } +} diff --git a/gradle.properties b/gradle.properties index ee34015de0..cc1e88bd1b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,6 +24,7 @@ fabric-entity-events-v1-version=1.2.4 fabric-events-interaction-v0-version=0.4.5 fabric-events-lifecycle-v0-version=0.2.2 fabric-game-rule-api-v1-version=1.0.7 +fabric-gametest-api-v1-version=1.0.0 fabric-item-api-v1-version=1.2.2 fabric-item-groups-v0-version=0.3.1 fabric-key-binding-api-v1-version=1.0.5 diff --git a/settings.gradle b/settings.gradle index 0f2cce2eea..fc9760ef22 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ include 'fabric-entity-events-v1' include 'fabric-events-interaction-v0' include 'fabric-events-lifecycle-v0' include 'fabric-game-rule-api-v1' +include 'fabric-gametest-api-v1' include 'fabric-item-api-v1' include 'fabric-item-groups-v0' include 'fabric-keybindings-v0' From 58a1a885e90e9869c290414016a2efcb550b6e97 Mon Sep 17 00:00:00 2001 From: turtton Date: Thu, 27 Jun 2024 20:46:34 +0900 Subject: [PATCH 2/4] Fix arguments --- .../net/fabricmc/fabric/mixin/gametest/server/MainMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java index 545b5fe21f..c4ff318ef5 100644 --- a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java @@ -55,7 +55,7 @@ private static boolean isEulaAgreedTo(EulaReader reader) { } @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorage$Session;readLevelProperties(Lcom/mojang/serialization/DynamicOps;Lnet/minecraft/resource/DataPackSettings;)Lnet/minecraft/world/SaveProperties;")) - private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec o1, OptionSpec o2, OptionSpec o3, OptionSpec o4, OptionSpec o5, OptionSpec o6, OptionSpec o7, OptionSpec o8, OptionSpec o9, OptionSpec o10, OptionSpec o11, OptionSpec o12, OptionSpec o13, OptionSpec o14, OptionSet optionSet, DynamicRegistryManager.Impl impl, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, DataPackSettings dataPackSettings, boolean bl, ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings2, CompletableFuture completableFuture, ServerResourceManager serverResourceManager, RegistryOps registryOps) { + private static void main(String[] args, CallbackInfo ci, OptionParser optionParser, OptionSpec optionSpec, OptionSpec optionSpec2, OptionSpec optionSpec3, OptionSpec optionSpec4, OptionSpec optionSpec5, OptionSpec optionSpec6, OptionSpec optionSpec7, OptionSpec optionSpec8, OptionSpec optionSpec9, OptionSpec optionSpec10, OptionSpec optionSpec11, OptionSpec optionSpec12, OptionSpec optionSpec13, OptionSpec optionSpec14, OptionSet optionSet, DynamicRegistryManager.Impl impl, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, DataPackSettings dataPackSettings, boolean bl, ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings2, CompletableFuture completableFuture, ServerResourceManager serverResourceManager, RegistryOps exception, OptionParser optionParser, OptionSpec optionSpec, OptionSpec optionSpec2, OptionSpec optionSpec3, OptionSpec optionSpec4, OptionSpec optionSpec5, OptionSpec optionSpec6, OptionSpec optionSpec7, OptionSpec optionSpec8, OptionSpec optionSpec9, OptionSpec optionSpec10, OptionSpec optionSpec11, OptionSpec optionSpec12, OptionSpec optionSpec13, OptionSpec optionSpec14, OptionSet optionSet, DynamicRegistryManager.Impl impl, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, DataPackSettings dataPackSettings, boolean bl, ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings2, CompletableFuture completableFuture, ServerResourceManager serverResourceManager, RegistryOps exception, OptionParser optionParser, OptionSpec o1, OptionSpec o2, OptionSpec o3, OptionSpec o4, OptionSpec o5, OptionSpec o6, OptionSpec o7, OptionSpec o8, OptionSpec o9, OptionSpec o10, OptionSpec o11, OptionSpec o12, OptionSpec o13, OptionSpec o14, OptionSet optionSet, DynamicRegistryManager.Impl impl, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, DataPackSettings dataPackSettings, boolean bl, ResourcePackManager resourcePackManager, DataPackSettings dataPackSettings2, CompletableFuture completableFuture, ServerResourceManager serverResourceManager, RegistryOps registryOps) { if (FabricGameTestHelper.ENABLED) { FabricGameTestHelper.runHeadlessServer(session, resourcePackManager, serverResourceManager, impl); info.cancel(); // Do not progress in starting the normal dedicated server From 9c36db79c2de38c10bae74c03a403aee1dff9d4a Mon Sep 17 00:00:00 2001 From: turtton Date: Sat, 15 Feb 2025 16:10:39 +0900 Subject: [PATCH 3/4] Add XmlReportingTestCompletionListener --- .../impl/gametest/GameTestExtensions.java | 5 ++ .../XmlReportingTestCompletionListener.java | 90 +++++++++++++++++++ .../fabric/mixin/gametest/GameTestMixin.java | 25 ++++++ .../fabric-gametest-api-v1.mixins.json | 1 + 4 files changed, 121 insertions(+) create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/GameTestExtensions.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/XmlReportingTestCompletionListener.java create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/GameTestMixin.java diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/GameTestExtensions.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/GameTestExtensions.java new file mode 100644 index 0000000000..9cbb5bb325 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/GameTestExtensions.java @@ -0,0 +1,5 @@ +package net.fabricmc.fabric.impl.gametest; + +public interface GameTestExtensions { + long fabric_getElapsedMilliseconds(); +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/XmlReportingTestCompletionListener.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/XmlReportingTestCompletionListener.java new file mode 100644 index 0000000000..1275f770e3 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/XmlReportingTestCompletionListener.java @@ -0,0 +1,90 @@ +package net.fabricmc.fabric.impl.gametest.vanilla; + + +import com.google.common.base.Stopwatch; + +import net.fabricmc.fabric.impl.gametest.GameTestExtensions; + +import net.minecraft.test.GameTest; + +import net.minecraft.test.TestCompletionListener; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import java.io.File; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.TimeUnit; + +public class XmlReportingTestCompletionListener implements TestCompletionListener { + private final Document document; + private final Element testSuiteElement; + private final Stopwatch stopwatch; + private final File file; + + public XmlReportingTestCompletionListener(File file) throws ParserConfigurationException { + this.file = file; + this.document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + this.testSuiteElement = this.document.createElement("testsuite"); + Element element = this.document.createElement("testsuite"); + element.appendChild(this.testSuiteElement); + this.document.appendChild(element); + this.testSuiteElement.setAttribute("timestamp", DateTimeFormatter.ISO_INSTANT.format(Instant.now())); + this.stopwatch = Stopwatch.createStarted(); + } + + private Element addTestCase(GameTest test, String name) { + Element element = this.document.createElement("testcase"); + element.setAttribute("name", name); + element.setAttribute("classname", test.getStructureName()); + element.setAttribute("time", String.valueOf((double) ((GameTestExtensions) test).fabric_getElapsedMilliseconds() / 1000.0)); + this.testSuiteElement.appendChild(element); + return element; + } + + @Override + public void onTestFailed(GameTest test) { + String string = test.getStructurePath(); + Throwable throwable = test.getThrowable(); + String string2 = throwable != null ? throwable.getMessage() : "Unknown error"; + Element element = this.document.createElement(test.isRequired() ? "failure" : "skipped"); + element.setAttribute("message", "(" + test.getPos().toShortString() + ") " + string2); + Element element2 = this.addTestCase(test, string); + element2.appendChild(element); + } + + // @Override + public void onTestPassed(GameTest test) { + String string = test.getStructurePath(); + this.addTestCase(test, string); + } + + // @Override + public void onStopped() { + this.stopwatch.stop(); + this.testSuiteElement.setAttribute("time", String.valueOf((double) this.stopwatch.elapsed(TimeUnit.MILLISECONDS) / 1000.0)); + + try { + this.saveReport(this.file); + } catch (TransformerException var2) { + throw new Error("Couldn't save test report", var2); + } + } + + public void saveReport(File file) throws TransformerException { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource dOMSource = new DOMSource(this.document); + StreamResult streamResult = new StreamResult(file); + transformer.transform(dOMSource, streamResult); + } +} diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/GameTestMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/GameTestMixin.java new file mode 100644 index 0000000000..6ee815c35e --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/GameTestMixin.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.mixin.gametest; + +import com.google.common.base.Stopwatch; + +import net.fabricmc.fabric.impl.gametest.GameTestExtensions; + +import net.minecraft.test.GameTest; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.concurrent.TimeUnit; + +@Mixin(GameTest.class) +public abstract class GameTestMixin implements GameTestExtensions { + @Shadow + @Final + private Stopwatch stopwatch; + + @Override + public long fabric_getElapsedMilliseconds() { + return stopwatch.elapsed(TimeUnit.MILLISECONDS); + } +} diff --git a/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json index 1ae56b7a0b..ab0632bb63 100644 --- a/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json +++ b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json @@ -4,6 +4,7 @@ "compatibilityLevel": "JAVA_16", "mixins": [ "CommandManagerMixin", + "GameTestMixin", "MinecraftServerMixin", "StructureTestUtilMixin", "TestFunctionsMixin", From 45c1681136dc77948e7b96a49fe5458245081282 Mon Sep 17 00:00:00 2001 From: turtton Date: Sat, 15 Feb 2025 16:13:04 +0900 Subject: [PATCH 4/4] Add TestFailureLogger --- .../gametest/vanilla/TestFailureLogger.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/TestFailureLogger.java diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/TestFailureLogger.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/TestFailureLogger.java new file mode 100644 index 0000000000..9bba443171 --- /dev/null +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/vanilla/TestFailureLogger.java @@ -0,0 +1,25 @@ +package net.fabricmc.fabric.impl.gametest.vanilla; + +import net.minecraft.test.FailureLoggingTestCompletionListener; +import net.minecraft.test.GameTest; +import net.minecraft.test.TestCompletionListener; + +public class TestFailureLogger { + private static TestCompletionListener completionListener = new FailureLoggingTestCompletionListener(); + + public static void setCompletionListener(TestCompletionListener listener) { + completionListener = listener; + } + + public static void failTest(GameTest test) { + completionListener.onTestFailed(test); + } + + public static void passTest(GameTest test) { +// completionListener.onTestPassed(test); + } + + public static void stop() { +// completionListener.onStopped(); + } +}