From a5127fc4544b6f44f5525ba0abe3c1c23aa797f9 Mon Sep 17 00:00:00 2001 From: drullkus Date: Sun, 26 Oct 2025 13:42:52 -0700 Subject: [PATCH 1/4] Begin Gametests --- .gitignore | 1 + build.gradle | 6 ++ .../twilightforest/gametests/TFGameTests.java | 60 ++++++++++++++++++ .../data/twilightforest/structure/empty.nbt | Bin 0 -> 138 bytes 4 files changed, 67 insertions(+) create mode 100644 src/main/java/twilightforest/gametests/TFGameTests.java create mode 100644 src/main/resources/data/twilightforest/structure/empty.nbt diff --git a/.gitignore b/.gitignore index 78ee2b5035..00861b9243 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ run classes logs run-data +run-test gource.sh .noai diff --git a/build.gradle b/build.gradle index 3162cab990..37c290cf14 100644 --- a/build.gradle +++ b/build.gradle @@ -148,6 +148,12 @@ neoForge { gameDirectory = project.file('run-data') programArguments.addAll '--mod', mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() } + + gametest { + type = 'gameTestServer' + gameDirectory = project.file('run-test') + systemProperty 'neoforge.enabledGameTestNamespaces', mod_id + } } } diff --git a/src/main/java/twilightforest/gametests/TFGameTests.java b/src/main/java/twilightforest/gametests/TFGameTests.java new file mode 100644 index 0000000000..94ad6ee69d --- /dev/null +++ b/src/main/java/twilightforest/gametests/TFGameTests.java @@ -0,0 +1,60 @@ +package twilightforest.gametests; + +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; +import net.neoforged.neoforge.registries.DeferredHolder; +import twilightforest.TwilightForestMod; +import twilightforest.init.TFBlocks; + +import java.util.Iterator; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; + +@GameTestHolder(TwilightForestMod.ID) +@PrefixGameTestTemplate(false) +public class TFGameTests { + + private static Stream extractBlockStates(DeferredHolder holder) { + return holder.value().getStateDefinition().getPossibleStates().stream(); + } + + /** + * Fundamental test that sets every single block. + *
+ * Helps catch any fundamental problems such as using clientside-specific code that would crash on a dedicated server. + */ + @GameTest(templateNamespace = TwilightForestMod.ID, template = "empty") + public static void setTFBlocks(final GameTestHelper test) { + + Set> excludeBlocks = Set.of( + TFBlocks.TWILIGHT_PORTAL, // Twilight Portal instantly reverts without supporting blocks + TFBlocks.UNCRAFTING_TABLE, // FIXME What's going on with its powered on state? + TFBlocks.CANDELABRA, // FIXME Candle property behavior + TFBlocks.CINDER_FURNACE // Unimplemented block + ); + + Iterator blockStatesForTesting = TFBlocks.BLOCKS.getEntries().stream() + .filter(Predicate.not(excludeBlocks::contains)) + .flatMap(TFGameTests::extractBlockStates) + .iterator(); + + while (blockStatesForTesting.hasNext()) { + test.setBlock(BlockPos.ZERO, Blocks.AIR); + + BlockState state = blockStatesForTesting.next(); + test.setBlock(BlockPos.ZERO, state); + test.assertBlockState(BlockPos.ZERO, state::equals, () -> "Expected placement of " + state + ", detected " + test.getBlockState(BlockPos.ZERO)); + } + + test.succeed(); // All assertions passed + + } + +} diff --git a/src/main/resources/data/twilightforest/structure/empty.nbt b/src/main/resources/data/twilightforest/structure/empty.nbt new file mode 100644 index 0000000000000000000000000000000000000000..f886b5790d567d9c1b66040546011041238fc346 GIT binary patch literal 138 zcmV;50CoQ#iwFP!00000|2>Ss4uUWcM8_1d#Tfs`GiQ$8SF*+pr6t|%#jjT|ILSLq zCi6NVpiz0V1F&+~3GkduOjxldpnb$VPTKuew`AQ<|2Q1g$apIrEmeEv0^gS!<5V@^ sv`}O--Yg!?Kr`bqOX9K6({3c?YdQC1bmyXy6L3|003bZ6!KnZM09V335&!@I literal 0 HcmV?d00001 From 483be0f6856680a860a984003fcf36f99ebb9a02 Mon Sep 17 00:00:00 2001 From: drullkus Date: Sun, 26 Oct 2025 13:58:14 -0700 Subject: [PATCH 2/4] Gametest for Block#getStateForPlacement --- .../twilightforest/gametests/TFGameTests.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/main/java/twilightforest/gametests/TFGameTests.java b/src/main/java/twilightforest/gametests/TFGameTests.java index 94ad6ee69d..10e8ce624e 100644 --- a/src/main/java/twilightforest/gametests/TFGameTests.java +++ b/src/main/java/twilightforest/gametests/TFGameTests.java @@ -1,11 +1,19 @@ package twilightforest.gametests; +import net.minecraft.commands.arguments.EntityAnchorArgument; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.gametest.framework.GameTest; import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.gametest.GameTestHolder; import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; import net.neoforged.neoforge.registries.DeferredHolder; @@ -57,4 +65,90 @@ public static void setTFBlocks(final GameTestHelper test) { } + /** + * Fundamental test that places every single block with a mock player. + *
+ * Triggers `Block#getStateForPlacement` + *
+ * Helps catch any fundamental problems such as using clientside-specific code that would crash on a dedicated server. + */ + @GameTest(templateNamespace = TwilightForestMod.ID, template = "empty") + public static void playerPlaceTFBlocks(final GameTestHelper test) { + + Set> excludeBlocks = Set.of( + // TODO Requires dirt above + TFBlocks.TORCHBERRY_PLANT, + TFBlocks.ROOT_STRAND, + TFBlocks.TROLLVIDR, + TFBlocks.UNRIPE_TROLLBER, + TFBlocks.TROLLBER, + // TODO Requires water below + TFBlocks.HUGE_LILY_PAD, + TFBlocks.HUGE_WATER_LILY, + // TODO Requires adjacent support + TFBlocks.IRON_LADDER, + TFBlocks.ROPE, + TFBlocks.THORN_ROSE, + // TODO Requires block above + TFBlocks.TWILIGHT_OAK_HANGING_SIGN, + TFBlocks.CANOPY_HANGING_SIGN, + TFBlocks.MANGROVE_HANGING_SIGN, + TFBlocks.DARK_HANGING_SIGN, + TFBlocks.TIME_HANGING_SIGN, + TFBlocks.TRANSFORMATION_HANGING_SIGN, + TFBlocks.MINING_HANGING_SIGN, + TFBlocks.SORTING_HANGING_SIGN, + // TODO Requires block sideways + TFBlocks.TWILIGHT_OAK_WALL_HANGING_SIGN, + TFBlocks.CANOPY_WALL_HANGING_SIGN, + TFBlocks.MANGROVE_WALL_HANGING_SIGN, + TFBlocks.DARK_WALL_HANGING_SIGN, + TFBlocks.TIME_WALL_HANGING_SIGN, + TFBlocks.TRANSFORMATION_WALL_HANGING_SIGN, + TFBlocks.MINING_WALL_HANGING_SIGN, + TFBlocks.SORTING_WALL_HANGING_SIGN, + // FIXME Crashes game, cherry-pick 44c2d5650e41ade9cbca70be7ce9b5b9e402b5ac into 1.21.1 + TFBlocks.TROLLSTEINN, + // FIXME why do these fail? + TFBlocks.FALLEN_LEAVES, + TFBlocks.GIANT_COBBLESTONE, + TFBlocks.GIANT_LOG, + TFBlocks.GIANT_LEAVES, + TFBlocks.GIANT_OBSIDIAN, + // NYI + TFBlocks.CINDER_FURNACE + ); + + Iterator blockForTesting = TFBlocks.BLOCKS.getEntries().stream() + .filter(Predicate.not(excludeBlocks::contains)) + .map(DeferredHolder::value) + .iterator(); + + BlockPos worldPos = test.absolutePos(BlockPos.ZERO); + + Player player = test.makeMockPlayer(GameType.CREATIVE); + { // Positions the player and makes them look at the block + Vec3 worldVecPos = Vec3.atBottomCenterOf(worldPos); + player.teleportTo(worldVecPos.x + 1, worldVecPos.y, worldVecPos.z + 1); + player.lookAt(EntityAnchorArgument.Anchor.EYES, worldVecPos); + } + + test.setBlock(BlockPos.ZERO.below(), Blocks.DIRT); // Plant support + + while (blockForTesting.hasNext()) { + test.setBlock(BlockPos.ZERO, Blocks.AIR); + + Item blockItem = blockForTesting.next().asItem(); + if (blockItem.getDefaultInstance().isEmpty()) continue; + ItemStack stack = new ItemStack(blockItem); + player.setItemInHand(InteractionHand.MAIN_HAND, stack); + + test.placeAt(player, stack, BlockPos.ZERO.above(), Direction.DOWN); + test.assertBlockState(BlockPos.ZERO, state -> !state.is(Blocks.AIR), () -> "Expected placement of " + blockItem + ", detected " + test.getBlockState(BlockPos.ZERO)); + } + + test.succeed(); // All assertions passed + + } + } From dd5f10b50381998d0f1f1272ce0f0d22f9851cb8 Mon Sep 17 00:00:00 2001 From: drullkus Date: Sun, 26 Oct 2025 17:30:39 -0700 Subject: [PATCH 3/4] Separate gametests to new sourceset --- build.gradle | 32 ++++++++++++++++++- gradle.properties | 2 +- .../java/twilightforest}/TFGameTests.java | 19 ++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) rename src/{main/java/twilightforest/gametests => gametest/java/twilightforest}/TFGameTests.java (92%) diff --git a/build.gradle b/build.gradle index 37c290cf14..d8933e50fa 100644 --- a/build.gradle +++ b/build.gradle @@ -98,6 +98,21 @@ subprojects { version = project(":").version } +sourceSets { + gametest { + compileClasspath += main.compileClasspath + main.output + + java { + srcDirs += ['src/main/java', 'src/gametest/java'] + } + resources { + srcDirs += ['src/gametest/generated'] + } + } +} + +project.configurations.maybeCreate(sourceSets.gametest.getTaskName(null, 'implementation')).extendsFrom(project.configurations.named('implementation').get()) + neoForge { // we need the subprojects to evaluate first so that the sourceSets are properly constructed evaluationDependsOnChildren() @@ -107,6 +122,8 @@ neoForge { validateAccessTransformers = true + addModdingDependenciesTo sourceSets.gametest + mods { "${mod_id}" { it.sourceSet this.sourceSets.main @@ -115,6 +132,11 @@ neoForge { 'tf-asm' { it.sourceSet project(":tf-asm").sourceSets.main } + + 'gametest' { + sourceSet sourceSets.main + sourceSet sourceSets.gametest + } } def mainMod = mods."${project.mod_id}" @@ -149,10 +171,12 @@ neoForge { programArguments.addAll '--mod', mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() } - gametest { + 'gametest-server' { type = 'gameTestServer' gameDirectory = project.file('run-test') systemProperty 'neoforge.enabledGameTestNamespaces', mod_id + sourceSet = sourceSets.gametest + loadedMods = [mods.'tf-asm', mods.'gametest'] } } } @@ -212,12 +236,18 @@ repositories { dependencies { jarJar implementation(project(":tf-asm")) + // gametestImplementation("net.neoforged:testframework:${neo_version}") { + // transitive = false + // } + def beanification = "tamaized:beanification:${project.beanification_version}" implementation beanification shade beanification testImplementation "${beanification}:tests" testCompileOnly "${beanification}:test-sources" + gametestImplementation(beanification) + //make sure to only pick one of these when testing (switch others to compileOnly) implementation "mezz.jei:jei-${project.base_minecraft_version}-neoforge:${project.jei_version}" compileOnly "me.shedaniel:RoughlyEnoughItems-neoforge:${project.rei_version}" diff --git a/gradle.properties b/gradle.properties index acf5497b57..561f5c7d7e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ minecraft_version=1.21.1 # NeoForge: https://projects.neoforged.net/neoforged/neoforge neo_version=21.1.83 -mdg_version=2.0.76 +mdg_version=2.0.107 # Deps beanification_version=1.6.108 diff --git a/src/main/java/twilightforest/gametests/TFGameTests.java b/src/gametest/java/twilightforest/TFGameTests.java similarity index 92% rename from src/main/java/twilightforest/gametests/TFGameTests.java rename to src/gametest/java/twilightforest/TFGameTests.java index 10e8ce624e..f39bb41aec 100644 --- a/src/main/java/twilightforest/gametests/TFGameTests.java +++ b/src/gametest/java/twilightforest/TFGameTests.java @@ -1,4 +1,4 @@ -package twilightforest.gametests; +package twilightforest; import net.minecraft.commands.arguments.EntityAnchorArgument; import net.minecraft.core.BlockPos; @@ -17,7 +17,6 @@ import net.neoforged.neoforge.gametest.GameTestHolder; import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; import net.neoforged.neoforge.registries.DeferredHolder; -import twilightforest.TwilightForestMod; import twilightforest.init.TFBlocks; import java.util.Iterator; @@ -53,6 +52,11 @@ public static void setTFBlocks(final GameTestHelper test) { .flatMap(TFGameTests::extractBlockStates) .iterator(); + if (!blockStatesForTesting.hasNext()) { + test.fail("No blockstates available to place"); + return; + } + while (blockStatesForTesting.hasNext()) { test.setBlock(BlockPos.ZERO, Blocks.AIR); @@ -119,11 +123,16 @@ public static void playerPlaceTFBlocks(final GameTestHelper test) { TFBlocks.CINDER_FURNACE ); - Iterator blockForTesting = TFBlocks.BLOCKS.getEntries().stream() + Iterator blocksForTesting = TFBlocks.BLOCKS.getEntries().stream() .filter(Predicate.not(excludeBlocks::contains)) .map(DeferredHolder::value) .iterator(); + if (!blocksForTesting.hasNext()) { + test.fail("No blocks available to place"); + return; + } + BlockPos worldPos = test.absolutePos(BlockPos.ZERO); Player player = test.makeMockPlayer(GameType.CREATIVE); @@ -135,10 +144,10 @@ public static void playerPlaceTFBlocks(final GameTestHelper test) { test.setBlock(BlockPos.ZERO.below(), Blocks.DIRT); // Plant support - while (blockForTesting.hasNext()) { + while (blocksForTesting.hasNext()) { test.setBlock(BlockPos.ZERO, Blocks.AIR); - Item blockItem = blockForTesting.next().asItem(); + Item blockItem = blocksForTesting.next().asItem(); if (blockItem.getDefaultInstance().isEmpty()) continue; ItemStack stack = new ItemStack(blockItem); player.setItemInHand(InteractionHand.MAIN_HAND, stack); From 7689f3b95a8627151bedb325eddcbf63e1323619 Mon Sep 17 00:00:00 2001 From: drullkus Date: Sun, 26 Oct 2025 21:43:04 -0700 Subject: [PATCH 4/4] Separate setBlock and placeBlock tests --- build.gradle | 8 +- .../java/twilightforest/TFBlockTests.java | 160 ++++++++++++++++ .../java/twilightforest/TFGameTests.java | 181 +++--------------- 3 files changed, 187 insertions(+), 162 deletions(-) create mode 100644 src/gametest/java/twilightforest/TFBlockTests.java diff --git a/build.gradle b/build.gradle index d8933e50fa..12199e5c39 100644 --- a/build.gradle +++ b/build.gradle @@ -155,13 +155,11 @@ neoForge { client { client() - systemProperty 'forge.enabledGameTestNamespaces', mod_id programArguments.addAll '--username', secrets.getProperty("username") ?: 'Dev', secrets.getProperty("uuid") ? '--uuid' : '', secrets.getProperty("uuid") ?: '' } server { server() - systemProperty 'forge.enabledGameTestNamespaces', mod_id programArgument '--nogui' } @@ -236,9 +234,9 @@ repositories { dependencies { jarJar implementation(project(":tf-asm")) - // gametestImplementation("net.neoforged:testframework:${neo_version}") { - // transitive = false - // } + gametestImplementation("net.neoforged:testframework:${neo_version}") { + transitive = false + } def beanification = "tamaized:beanification:${project.beanification_version}" implementation beanification diff --git a/src/gametest/java/twilightforest/TFBlockTests.java b/src/gametest/java/twilightforest/TFBlockTests.java new file mode 100644 index 0000000000..65c31c653a --- /dev/null +++ b/src/gametest/java/twilightforest/TFBlockTests.java @@ -0,0 +1,160 @@ +package twilightforest; + +import com.google.common.collect.Streams; +import net.minecraft.commands.arguments.EntityAnchorArgument; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.gametest.framework.TestFunction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.registries.DeferredHolder; +import twilightforest.init.TFBlocks; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +@GameTestHolder(TwilightForestMod.ID) +public class TFBlockTests { + + public static List generateBlockRegistryTests() { + Collection> tfBlocks = TFBlocks.BLOCKS.getEntries(); + + Stream setBlocks = tfBlocks.stream() + .filter(Predicate.not(EXCLUDE_SETBLOCKS::contains)) + .map(TFBlockTests::setBlockTest); + + Stream placeBlocks = tfBlocks.stream() + .filter(Predicate.not(EXCLUDE_PLACEBLOCKS::contains)) + .map(DeferredHolder::value) + .map(Block::asItem) + .filter(blockItem -> !blockItem.getDefaultInstance().isEmpty()) + .map(TFBlockTests::placeBlockTest); + + return Streams.concat(setBlocks, placeBlocks).toList(); + } + + /** + * Fundamental test that sets every single block. + *
+ * Helps catch any fundamental problems such as using clientside-specific code that would crash on a dedicated server. + */ + private static TestFunction setBlockTest(DeferredHolder blockHolder) { + Consumer setBlockTest = test -> { + for (BlockState state : blockHolder.value().getStateDefinition().getPossibleStates()) { + test.setBlock(BlockPos.ZERO, Blocks.AIR); + test.setBlock(BlockPos.ZERO, state); + test.assertBlockState(BlockPos.ZERO, state::equals, () -> "Expected placement of " + state + ", detected " + test.getBlockState(BlockPos.ZERO)); + } + test.succeed(); // Assertion passed + }; + return new TestFunction("setBlock", blockHolder.getId().toString(), "twilightforest:empty", Rotation.NONE, 1000, 0, true, false, 1, 1, false, setBlockTest); + } + + private static final Set> EXCLUDE_SETBLOCKS = Set.of( + TFBlocks.TWILIGHT_PORTAL, // Twilight Portal instantly reverts without supporting blocks + TFBlocks.UNCRAFTING_TABLE, // FIXME What's going on with its powered on state? + TFBlocks.CANDELABRA, // FIXME Candle property behavior + TFBlocks.CINDER_FURNACE // Unimplemented block + ); + + /** + * Fundamental test that places every single block with a mock player. + *
+ * Triggers `Block#getStateForPlacement` + *
+ * Helps catch any fundamental problems such as using clientside-specific code that would crash on a dedicated server. + */ + private static TestFunction placeBlockTest(Item item) { + Consumer placeBlockTest = test -> { + test.setBlock(BlockPos.ZERO.below(), Blocks.DIRT); // Plant support + //test.setBlock(BlockPos.ZERO, Blocks.AIR); + ItemStack stack = new ItemStack(item); + + Player player = test.makeMockPlayer(GameType.CREATIVE); + { // Positions the player and makes them look at the block + Vec3 worldVecPos = Vec3.atBottomCenterOf(test.absolutePos(BlockPos.ZERO)); + player.teleportTo(worldVecPos.x + 1, worldVecPos.y, worldVecPos.z + 1); + player.lookAt(EntityAnchorArgument.Anchor.EYES, worldVecPos); + } + player.setItemInHand(InteractionHand.MAIN_HAND, stack); + + test.placeAt(player, stack, BlockPos.ZERO.above(), Direction.DOWN); + test.assertBlockState(BlockPos.ZERO, state -> !state.is(Blocks.AIR), () -> "Expected placement of " + item + ", detected " + test.getBlockState(BlockPos.ZERO)); + + test.succeed(); // All assertions passed + }; + + return new TestFunction( + "placeBlock", + item.toString(), + "twilightforest:empty", + Rotation.NONE, + 1000, + 0, + true, + false, + 1, + 1, + false, + placeBlockTest); + } + + private static final Set> EXCLUDE_PLACEBLOCKS = Set.of( + // TODO Requires dirt above + TFBlocks.TORCHBERRY_PLANT, + TFBlocks.ROOT_STRAND, + TFBlocks.TROLLVIDR, + TFBlocks.UNRIPE_TROLLBER, + TFBlocks.TROLLBER, + // TODO Requires water below + TFBlocks.HUGE_LILY_PAD, + TFBlocks.HUGE_WATER_LILY, + // TODO Requires adjacent support + TFBlocks.IRON_LADDER, + TFBlocks.ROPE, + TFBlocks.THORN_ROSE, + // TODO Requires block above + TFBlocks.TWILIGHT_OAK_HANGING_SIGN, + TFBlocks.CANOPY_HANGING_SIGN, + TFBlocks.MANGROVE_HANGING_SIGN, + TFBlocks.DARK_HANGING_SIGN, + TFBlocks.TIME_HANGING_SIGN, + TFBlocks.TRANSFORMATION_HANGING_SIGN, + TFBlocks.MINING_HANGING_SIGN, + TFBlocks.SORTING_HANGING_SIGN, + // TODO Requires block sideways + TFBlocks.TWILIGHT_OAK_WALL_HANGING_SIGN, + TFBlocks.CANOPY_WALL_HANGING_SIGN, + TFBlocks.MANGROVE_WALL_HANGING_SIGN, + TFBlocks.DARK_WALL_HANGING_SIGN, + TFBlocks.TIME_WALL_HANGING_SIGN, + TFBlocks.TRANSFORMATION_WALL_HANGING_SIGN, + TFBlocks.MINING_WALL_HANGING_SIGN, + TFBlocks.SORTING_WALL_HANGING_SIGN, + // FIXME Crashes game, cherry-pick 44c2d5650e41ade9cbca70be7ce9b5b9e402b5ac into 1.21.1 + TFBlocks.TROLLSTEINN, + // FIXME why do these fail? + TFBlocks.FALLEN_LEAVES, + TFBlocks.GIANT_COBBLESTONE, + TFBlocks.GIANT_LOG, + TFBlocks.GIANT_LEAVES, + TFBlocks.GIANT_OBSIDIAN, + // NYI + TFBlocks.CINDER_FURNACE + ); + +} diff --git a/src/gametest/java/twilightforest/TFGameTests.java b/src/gametest/java/twilightforest/TFGameTests.java index f39bb41aec..31900004d2 100644 --- a/src/gametest/java/twilightforest/TFGameTests.java +++ b/src/gametest/java/twilightforest/TFGameTests.java @@ -1,163 +1,30 @@ package twilightforest; -import net.minecraft.commands.arguments.EntityAnchorArgument; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.gametest.framework.GameTest; -import net.minecraft.gametest.framework.GameTestHelper; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.GameType; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.Vec3; -import net.neoforged.neoforge.gametest.GameTestHolder; -import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; -import net.neoforged.neoforge.registries.DeferredHolder; -import twilightforest.init.TFBlocks; - -import java.util.Iterator; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Stream; - -@GameTestHolder(TwilightForestMod.ID) -@PrefixGameTestTemplate(false) -public class TFGameTests { - - private static Stream extractBlockStates(DeferredHolder holder) { - return holder.value().getStateDefinition().getPossibleStates().stream(); - } - - /** - * Fundamental test that sets every single block. - *
- * Helps catch any fundamental problems such as using clientside-specific code that would crash on a dedicated server. - */ - @GameTest(templateNamespace = TwilightForestMod.ID, template = "empty") - public static void setTFBlocks(final GameTestHelper test) { - - Set> excludeBlocks = Set.of( - TFBlocks.TWILIGHT_PORTAL, // Twilight Portal instantly reverts without supporting blocks - TFBlocks.UNCRAFTING_TABLE, // FIXME What's going on with its powered on state? - TFBlocks.CANDELABRA, // FIXME Candle property behavior - TFBlocks.CINDER_FURNACE // Unimplemented block - ); - - Iterator blockStatesForTesting = TFBlocks.BLOCKS.getEntries().stream() - .filter(Predicate.not(excludeBlocks::contains)) - .flatMap(TFGameTests::extractBlockStates) - .iterator(); - - if (!blockStatesForTesting.hasNext()) { - test.fail("No blockstates available to place"); - return; - } - - while (blockStatesForTesting.hasNext()) { - test.setBlock(BlockPos.ZERO, Blocks.AIR); - - BlockState state = blockStatesForTesting.next(); - test.setBlock(BlockPos.ZERO, state); - test.assertBlockState(BlockPos.ZERO, state::equals, () -> "Expected placement of " + state + ", detected " + test.getBlockState(BlockPos.ZERO)); - } - - test.succeed(); // All assertions passed - +import net.minecraft.gametest.framework.GameTestGenerator; +import net.minecraft.gametest.framework.TestFunction; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.event.RegisterGameTestsEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collection; + +/** + * Entrypoint for Twilight Forest's game-integrated tests + */ +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = TwilightForestMod.ID) +public final class TFGameTests { + public static final Logger LOGGER = LogManager.getLogger(TwilightForestMod.ID + "tests"); + + @SubscribeEvent + public static void registerBlockTests(RegisterGameTestsEvent event) { + LOGGER.info("Starting registerBlockTests"); + event.register(TFGameTests.class); } - /** - * Fundamental test that places every single block with a mock player. - *
- * Triggers `Block#getStateForPlacement` - *
- * Helps catch any fundamental problems such as using clientside-specific code that would crash on a dedicated server. - */ - @GameTest(templateNamespace = TwilightForestMod.ID, template = "empty") - public static void playerPlaceTFBlocks(final GameTestHelper test) { - - Set> excludeBlocks = Set.of( - // TODO Requires dirt above - TFBlocks.TORCHBERRY_PLANT, - TFBlocks.ROOT_STRAND, - TFBlocks.TROLLVIDR, - TFBlocks.UNRIPE_TROLLBER, - TFBlocks.TROLLBER, - // TODO Requires water below - TFBlocks.HUGE_LILY_PAD, - TFBlocks.HUGE_WATER_LILY, - // TODO Requires adjacent support - TFBlocks.IRON_LADDER, - TFBlocks.ROPE, - TFBlocks.THORN_ROSE, - // TODO Requires block above - TFBlocks.TWILIGHT_OAK_HANGING_SIGN, - TFBlocks.CANOPY_HANGING_SIGN, - TFBlocks.MANGROVE_HANGING_SIGN, - TFBlocks.DARK_HANGING_SIGN, - TFBlocks.TIME_HANGING_SIGN, - TFBlocks.TRANSFORMATION_HANGING_SIGN, - TFBlocks.MINING_HANGING_SIGN, - TFBlocks.SORTING_HANGING_SIGN, - // TODO Requires block sideways - TFBlocks.TWILIGHT_OAK_WALL_HANGING_SIGN, - TFBlocks.CANOPY_WALL_HANGING_SIGN, - TFBlocks.MANGROVE_WALL_HANGING_SIGN, - TFBlocks.DARK_WALL_HANGING_SIGN, - TFBlocks.TIME_WALL_HANGING_SIGN, - TFBlocks.TRANSFORMATION_WALL_HANGING_SIGN, - TFBlocks.MINING_WALL_HANGING_SIGN, - TFBlocks.SORTING_WALL_HANGING_SIGN, - // FIXME Crashes game, cherry-pick 44c2d5650e41ade9cbca70be7ce9b5b9e402b5ac into 1.21.1 - TFBlocks.TROLLSTEINN, - // FIXME why do these fail? - TFBlocks.FALLEN_LEAVES, - TFBlocks.GIANT_COBBLESTONE, - TFBlocks.GIANT_LOG, - TFBlocks.GIANT_LEAVES, - TFBlocks.GIANT_OBSIDIAN, - // NYI - TFBlocks.CINDER_FURNACE - ); - - Iterator blocksForTesting = TFBlocks.BLOCKS.getEntries().stream() - .filter(Predicate.not(excludeBlocks::contains)) - .map(DeferredHolder::value) - .iterator(); - - if (!blocksForTesting.hasNext()) { - test.fail("No blocks available to place"); - return; - } - - BlockPos worldPos = test.absolutePos(BlockPos.ZERO); - - Player player = test.makeMockPlayer(GameType.CREATIVE); - { // Positions the player and makes them look at the block - Vec3 worldVecPos = Vec3.atBottomCenterOf(worldPos); - player.teleportTo(worldVecPos.x + 1, worldVecPos.y, worldVecPos.z + 1); - player.lookAt(EntityAnchorArgument.Anchor.EYES, worldVecPos); - } - - test.setBlock(BlockPos.ZERO.below(), Blocks.DIRT); // Plant support - - while (blocksForTesting.hasNext()) { - test.setBlock(BlockPos.ZERO, Blocks.AIR); - - Item blockItem = blocksForTesting.next().asItem(); - if (blockItem.getDefaultInstance().isEmpty()) continue; - ItemStack stack = new ItemStack(blockItem); - player.setItemInHand(InteractionHand.MAIN_HAND, stack); - - test.placeAt(player, stack, BlockPos.ZERO.above(), Direction.DOWN); - test.assertBlockState(BlockPos.ZERO, state -> !state.is(Blocks.AIR), () -> "Expected placement of " + blockItem + ", detected " + test.getBlockState(BlockPos.ZERO)); - } - - test.succeed(); // All assertions passed - + @GameTestGenerator + public static Collection generateBlockTests() { + return TFBlockTests.generateBlockRegistryTests(); } - }