25w03a game tests ()

* 25w03a game tests

* Cleanup and improvements

* Update fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/impl/gametest/TestAnnotationLocator.java

Co-authored-by: Joseph Burton <burtonjae@hotmail.co.uk>

* Use an 8x8 empty structure by default

* Use a dedicated RegistryLoaderMixin instead of hacking around registry sync's api

* Fix

* Checkstyle

---------

Co-authored-by: Joseph Burton <burtonjae@hotmail.co.uk>
This commit is contained in:
modmuss 2025-01-21 18:50:26 +00:00 committed by GitHub
parent bcdf965b28
commit 73a52b4b18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 849 additions and 802 deletions
build.gradle
fabric-api-base/src/testmod/java/net/fabricmc/fabric/test/base
fabric-command-api-v2/src/testmod/java/net/fabricmc/fabric/test/command
fabric-content-registries-v0/src/testmod/java/net/fabricmc/fabric/test/content/registry
fabric-data-attachment-api-v1/src/testmod/java/net/fabricmc/fabric/test/attachment/gametest
fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction
fabric-gametest-api-v1
fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest
fabric-object-builder-api-v1/src/testmod/java/net/fabricmc/fabric/test/object/builder
fabric-recipe-api-v1/src/testmod
java/net/fabricmc/fabric/test/recipe/ingredient
resources
fabric-resource-conditions-api-v1/src/testmod/java/net/fabricmc/fabric/test/resource/conditions
fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader
fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/gametests
gradle.propertiessettings.gradle

View file

@ -631,12 +631,12 @@ subprojects {
testmodImplementation sourceSets.main.output
// Make all modules depend on the gametest api (and thus res loader) to try and promote its usage.
// if (project.name != "fabric-gametest-api-v1") {
// testmodImplementation project(path: ':fabric-gametest-api-v1', configuration: 'namedElements')
// testmodClientImplementation project(":fabric-gametest-api-v1").sourceSets.client.output
// testmodImplementation project(path: ':fabric-resource-loader-v0', configuration: 'namedElements')
// testmodClientImplementation project(":fabric-resource-loader-v0").sourceSets.client.output
// }
if (project.name != "fabric-gametest-api-v1") {
testmodImplementation project(path: ':fabric-gametest-api-v1', configuration: 'namedElements')
testmodClientImplementation project(":fabric-gametest-api-v1").sourceSets.client.output
testmodImplementation project(path: ':fabric-resource-loader-v0', configuration: 'namedElements')
testmodClientImplementation project(":fabric-resource-loader-v0").sourceSets.client.output
}
// Make all testmods run with registry-sync-v0 as it is required to register new objects.
if (project.name != "fabric-registry-sync-v0") {

View file

@ -20,8 +20,10 @@ import org.spongepowered.asm.mixin.MixinEnvironment;
import net.minecraft.test.TestContext;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class FabricApiBaseGameTest {
//@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) TODO 1.21.5 tests
@GameTest
public void auditMixins(TestContext context) {
MixinEnvironment.getCurrentEnvironment().audit();

View file

@ -24,6 +24,8 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class EntitySelectorGameTest {
private void spawn(TestContext context, float health) {
MobEntity entity = context.spawnMob(EntityType.CREEPER, BlockPos.ORIGIN);
@ -31,7 +33,7 @@ public class EntitySelectorGameTest {
entity.setHealth(health);
}
// @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) TODO 1.21.5 tests
@GameTest
public void testEntitySelector(TestContext context) {
BlockPos absolute = context.getAbsolutePos(BlockPos.ORIGIN);

View file

@ -16,9 +16,33 @@
package net.fabricmc.fabric.test.content.registry;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ComposterBlock;
import net.minecraft.block.HopperBlock;
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.Potions;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.GameMode;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class ContentRegistryGameTest {
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testCompostingChanceRegistry(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.COMPOSTER);
@ -28,11 +52,11 @@ public class ContentRegistryGameTest {
// If on level 0, composting always increases composter level
context.useBlock(pos, player);
context.expectBlockProperty(pos, ComposterBlock.LEVEL, 1);
context.assertEquals(obsidian.getCount(), 63, "obsidian stack count");
context.assertEquals(obsidian.getCount(), 63, Text.literal("obsidian stack count"));
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testFlattenableBlockRegistry(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.RED_WOOL);
@ -41,7 +65,7 @@ public class ContentRegistryGameTest {
player.setStackInHand(Hand.MAIN_HAND, shovel);
context.useBlock(pos, player);
context.expectBlock(Blocks.YELLOW_WOOL, pos);
context.assertEquals(shovel.getDamage(), 1, "shovel damage");
context.assertEquals(shovel.getDamage(), 1, Text.literal("shovel damage"));
context.complete();
}
@ -52,10 +76,7 @@ public class ContentRegistryGameTest {
BlockState furnaceState = Blocks.BLAST_FURNACE.getDefaultState();
context.setBlockState(furnacePos, furnaceState);
if (!(context.getBlockEntity(furnacePos) instanceof AbstractFurnaceBlockEntity furnace)) {
throw new AssertionError("Furnace was not placed");
}
AbstractFurnaceBlockEntity furnace = context.getBlockEntity(furnacePos, AbstractFurnaceBlockEntity.class);
// Create a hopper that attempts to insert fuel into the furnace
BlockPos hopperPos = furnacePos.east();
@ -63,10 +84,7 @@ public class ContentRegistryGameTest {
.with(HopperBlock.FACING, context.getRotation().rotate(Direction.WEST));
context.setBlockState(hopperPos, hopperState);
if (!(context.getBlockEntity(hopperPos) instanceof HopperBlockEntity hopper)) {
throw new AssertionError("Hopper was not placed");
}
HopperBlockEntity hopper = context.getBlockEntity(hopperPos, HopperBlockEntity.class);
// Insert the fuel into the hopper, which transfers it into the furnace
hopper.setStack(0, fuelStack.copy());
@ -80,11 +98,11 @@ public class ContentRegistryGameTest {
private void smeltCompleted(TestContext context, ItemStack fuelStack) {
smelt(context, fuelStack, (furnace, hopper) -> {
context.assertTrue(hopper.isEmpty(), "fuel hopper should have been emptied");
context.assertTrue(hopper.isEmpty(), Text.literal("fuel hopper should have been emptied"));
context.assertTrue(furnace.getStack(0).isEmpty(), "furnace input slot should have been emptied");
context.assertTrue(furnace.getStack(0).isEmpty(), "furnace fuel slot should have been emptied");
context.assertTrue(ItemStack.areEqual(furnace.getStack(2), new ItemStack(Items.IRON_INGOT, 1)), "one iron ingot should have been smelted and placed into the furnace output slot");
context.assertTrue(furnace.getStack(0).isEmpty(), Text.literal("furnace input slot should have been emptied"));
context.assertTrue(furnace.getStack(0).isEmpty(), Text.literal("furnace fuel slot should have been emptied"));
context.assertTrue(ItemStack.areEqual(furnace.getStack(2), new ItemStack(Items.IRON_INGOT, 1)), Text.literal("one iron ingot should have been smelted and placed into the furnace output slot"));
context.complete();
});
@ -92,41 +110,41 @@ public class ContentRegistryGameTest {
private void smeltFailed(TestContext context, ItemStack fuelStack) {
smelt(context, fuelStack, (furnace, hopper) -> {
context.assertTrue(ItemStack.areEqual(hopper.getStack(0), fuelStack), "fuel hopper should not have been emptied");
context.assertTrue(ItemStack.areEqual(hopper.getStack(0), fuelStack), Text.literal("fuel hopper should not have been emptied"));
context.assertTrue(ItemStack.areEqual(furnace.getStack(0), new ItemStack(Items.RAW_IRON, 1)), "furnace input slot should not have been emptied");
context.assertTrue(furnace.getStack(1).isEmpty(), "furnace fuel slot should not have been filled");
context.assertTrue(furnace.getStack(2).isEmpty(), "furnace output slot should not have been filled");
context.assertTrue(ItemStack.areEqual(furnace.getStack(0), new ItemStack(Items.RAW_IRON, 1)), Text.literal("furnace input slot should not have been emptied"));
context.assertTrue(furnace.getStack(1).isEmpty(), Text.literal("furnace fuel slot should not have been filled"));
context.assertTrue(furnace.getStack(2).isEmpty(), Text.literal("furnace output slot should not have been filled"));
context.complete();
});
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
@GameTest(maxTicks = 110)
public void testSmeltingFuelIncludedByItem(TestContext context) {
// Item with 50 fuel time x4 = 200 fuel time
smeltCompleted(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_INCLUDED_BY_ITEM, 4));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
@GameTest(maxTicks = 110)
public void testSmeltingFuelIncludedByTag(TestContext context) {
// Item in tag with 100 fuel time x2 = 200 fuel time
smeltCompleted(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_INCLUDED_BY_TAG, 2));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
@GameTest(maxTicks = 110)
public void testSmeltingFuelExcludedByTag(TestContext context) {
// Item is in both the smelting fuels tag and the excluded smithing fuels tag
smeltFailed(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_EXCLUDED_BY_TAG));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
@GameTest(maxTicks = 110)
public void testSmeltingFuelExcludedByVanillaTag(TestContext context) {
// Item is in both the smelting fuel tag and vanilla's excluded non-flammable wood tag
smeltFailed(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_EXCLUDED_BY_VANILLA_TAG));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testStrippableBlockRegistry(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.QUARTZ_PILLAR);
@ -135,11 +153,11 @@ public class ContentRegistryGameTest {
player.setStackInHand(Hand.MAIN_HAND, axe);
context.useBlock(pos, player);
context.expectBlock(Blocks.HAY_BLOCK, pos);
context.assertEquals(axe.getDamage(), 1, "axe damage");
context.assertEquals(axe.getDamage(), 1, Text.literal("axe damage"));
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testTillableBlockRegistry(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.GREEN_WOOL);
@ -148,11 +166,11 @@ public class ContentRegistryGameTest {
player.setStackInHand(Hand.MAIN_HAND, hoe);
context.useBlock(pos, player);
context.expectBlock(Blocks.LIME_WOOL, pos);
context.assertEquals(hoe.getDamage(), 1, "hoe damage");
context.assertEquals(hoe.getDamage(), 1, Text.literal("hoe damage"));
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testOxidizableBlocksRegistry(TestContext context) {
// Test de-oxidation. (the registry does not make the blocks oxidize.)
PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL);
@ -162,7 +180,7 @@ public class ContentRegistryGameTest {
player.setStackInHand(Hand.MAIN_HAND, axe);
context.useBlock(pos, player);
context.expectBlock(Blocks.GOLD_ORE, pos);
context.assertEquals(axe.getDamage(), 1, "axe damage");
context.assertEquals(axe.getDamage(), 1, Text.literal("axe damage"));
context.useBlock(pos, player);
context.expectBlock(Blocks.IRON_ORE, pos);
context.useBlock(pos, player);
@ -170,7 +188,7 @@ public class ContentRegistryGameTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testWaxableBlocksRegistry(TestContext context) {
PlayerEntity player = context.createMockPlayer(GameMode.SURVIVAL);
BlockPos pos = new BlockPos(0, 1, 0);
@ -179,22 +197,19 @@ public class ContentRegistryGameTest {
player.setStackInHand(Hand.MAIN_HAND, honeycomb);
context.useBlock(pos, player);
context.expectBlock(Blocks.DEEPSLATE_DIAMOND_ORE, pos);
context.assertEquals(honeycomb.getCount(), 63, "honeycomb count");
context.assertEquals(honeycomb.getCount(), 63, Text.literal("honeycomb count"));
ItemStack axe = new ItemStack(Items.NETHERITE_AXE);
player.setStackInHand(Hand.MAIN_HAND, axe);
context.useBlock(pos, player);
context.expectBlock(Blocks.DIAMOND_ORE, pos);
context.assertEquals(axe.getDamage(), 1, "axe damage");
context.assertEquals(axe.getDamage(), 1, Text.literal("axe damage"));
context.complete();
}
private void brew(TestContext context, ItemStack input, ItemStack bottle, Consumer<BrewingStandBlockEntity> callback) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.BREWING_STAND);
if (!(context.getBlockEntity(pos) instanceof BrewingStandBlockEntity brewingStand)) {
throw new AssertionError("Brewing stand was not placed");
}
BrewingStandBlockEntity brewingStand = context.getBlockEntity(pos, BrewingStandBlockEntity.class);
brewingStand.setStack(0, bottle);
brewingStand.setStack(3, input);
@ -202,24 +217,22 @@ public class ContentRegistryGameTest {
context.waitAndRun(401, () -> callback.accept(brewingStand));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 410)
@GameTest(maxTicks = 410)
public void testBrewingFlower(TestContext context) {
brew(context, new ItemStack(Items.DANDELION), PotionContentsComponent.createStack(Items.POTION, Potions.AWKWARD), brewingStand -> {
ItemStack bottle = brewingStand.getStack(0);
PotionContentsComponent potion = bottle.getOrDefault(DataComponentTypes.POTION_CONTENTS, PotionContentsComponent.DEFAULT);
context.assertEquals(potion.potion().orElseThrow(), Potions.HEALING, "brewed potion");
context.assertEquals(potion.potion().orElseThrow(), Potions.HEALING, Text.literal("brewed potion"));
context.complete();
});
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 410)
@GameTest(maxTicks = 410)
public void testBrewingDirt(TestContext context) {
brew(context, new ItemStack(Items.DIRT), PotionContentsComponent.createStack(Items.POTION, Potions.AWKWARD), brewingStand -> {
ItemStack bottle = brewingStand.getStack(0);
context.assertTrue(bottle.getItem() instanceof ContentRegistryTest.DirtyPotionItem, "potion became dirty");
context.assertTrue(bottle.getItem() instanceof ContentRegistryTest.DirtyPotionItem, Text.literal("potion became dirty"));
context.complete();
});
}
*/
}

View file

@ -20,16 +20,17 @@ import net.minecraft.block.Blocks;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
public class FlammableTest {
/**
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/2108">FlammableBlockRegistry ignoring tags on first load</a>.
*/
// @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) TODO 1.21.5 tests
@GameTest
public void testFlammableTag(TestContext context) {
if (FlammableBlockRegistry.getDefaultInstance().get(Blocks.SAND).getBurnChance() != 4) {
context.method_66943(Text.literal("Expected blocks in the sand tag to be flammable!"));
throw context.createError(Text.literal("Expected blocks in the sand tag to be flammable!"));
}
context.complete();

View file

@ -16,13 +16,28 @@
package net.fabricmc.fabric.test.attachment.gametest;
import java.util.List;
import java.util.Objects;
import java.util.function.IntSupplier;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.mob.DrownedEntity;
import net.minecraft.entity.mob.ZombieEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.test.TestContext;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.TeleportTarget;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry;
import net.fabricmc.fabric.api.attachment.v1.AttachmentType;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.test.attachment.AttachmentTestMod;
import net.fabricmc.fabric.test.attachment.mixin.ZombieEntityAccessor;
public class AttachmentCopyTests {
// using a lambda type because serialization shouldn't play a role in this
@ -34,8 +49,7 @@ public class AttachmentCopyTests {
AttachmentRegistry.Builder::copyOnDeath
);
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testCrossWorldTeleport(TestContext context) {
MinecraftServer server = context.getWorld().getServer();
ServerWorld overworld = server.getOverworld();
@ -48,20 +62,20 @@ public class AttachmentCopyTests {
entity.setAttached(COPY_ON_DEATH, () -> 10);
Entity moved = entity.teleportTo(new TeleportTarget(end, entity, TeleportTarget.NO_OP));
if (moved == null) throw new GameTestException("Cross-world teleportation failed");
if (moved == null) throw context.createError("Cross-world teleportation failed");
IntSupplier attached1 = moved.getAttached(DUMMY);
IntSupplier attached2 = moved.getAttached(COPY_ON_DEATH);
if (attached1 == null || attached1.getAsInt() != 10 || attached2 == null || attached2.getAsInt() != 10) {
throw new GameTestException("Attachment copying failed during cross-world teleportation");
throw context.createError("Attachment copying failed during cross-world teleportation");
}
moved.discard();
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testMobConversion(TestContext context) {
ZombieEntity mob = context.spawnEntity(EntityType.ZOMBIE, BlockPos.ORIGIN);
mob.setAttached(DUMMY, () -> 42);
@ -72,25 +86,23 @@ public class AttachmentCopyTests {
List<DrownedEntity> drowned = context.getEntities(EntityType.DROWNED);
if (drowned.size() != 1) {
throw new GameTestException("Conversion failed");
throw context.createError("Conversion failed");
}
DrownedEntity converted = drowned.getFirst();
if (converted == null) throw new GameTestException("Conversion failed");
if (converted == null) throw context.createError("Conversion failed");
if (converted.hasAttached(DUMMY)) {
throw new GameTestException("Attachment shouldn't have been copied on mob conversion");
throw context.createError("Attachment shouldn't have been copied on mob conversion");
}
IntSupplier attached = converted.getAttached(COPY_ON_DEATH);
if (attached == null || attached.getAsInt() != 42) {
throw new GameTestException("Attachment copying failed during mob conversion");
throw context.createError("Attachment copying failed during mob conversion");
}
converted.discard();
context.complete();
}
*/
}

View file

@ -19,11 +19,27 @@ package net.fabricmc.fabric.test.attachment.gametest;
import com.mojang.logging.LogUtils;
import org.slf4j.Logger;
import net.minecraft.block.Block;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.attachment.v1.AttachmentTarget;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.test.attachment.AttachmentTestMod;
import net.fabricmc.fabric.test.attachment.mixin.BlockEntityTypeAccessor;
public class BlockEntityTests {
private static final Logger LOGGER = LogUtils.getLogger();
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testBlockEntitySync(TestContext context) {
BlockPos pos = BlockPos.ORIGIN.up();
@ -60,12 +76,10 @@ public class BlockEntityTests {
if (nbt != null && nbt.contains(AttachmentTarget.NBT_ATTACHMENT_KEY)) {
// Note: this is a vanilla bug (it called createNbt, instead of the correct createComponentlessNbt)
throw new GameTestException("Packet NBT for " + entry + " had persistent data: " + nbt.asString());
throw context.createError("Packet NBT for " + entry + " had persistent data: " + nbt.asString());
}
}
context.complete();
}
*/
}

View file

@ -16,12 +16,29 @@
package net.fabricmc.fabric.test.event.interaction;
import net.minecraft.block.Blocks;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.item.Items;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.fabricmc.fabric.api.entity.FakePlayer;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class FakePlayerTests {
/**
* Try placing a sign with a fake player.
*/
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testFakePlayerPlaceSign(TestContext context) {
// This is for Fabric internal testing only, if you copy this to your mod you're on your own...
@ -41,18 +58,15 @@ public class FakePlayerTests {
BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, context.getAbsolutePos(basePos), false);
signStack.useOnBlock(new ItemUsageContext(fakePlayer, Hand.MAIN_HAND, hitResult));
context.checkBlockState(signPos, x -> x.isOf(Blocks.OAK_SIGN), () -> "Sign was not placed");
context.assertTrue(signStack.isEmpty(), "Sign stack was not emptied");
context.checkBlockState(signPos, x -> x.isOf(Blocks.OAK_SIGN), (b) -> Text.literal("Sign was not placed"));
context.assertTrue(signStack.isEmpty(), Text.literal("Sign stack was not emptied"));
context.complete();
}
*/
/**
* Try breaking a beehive with a fake player (see {@code BeehiveBlockMixin}).
*/
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testFakePlayerBreakBeehive(TestContext context) {
BlockPos basePos = new BlockPos(0, 1, 0);
context.setBlockState(basePos, Blocks.BEEHIVE);
@ -63,10 +77,8 @@ public class FakePlayerTests {
BlockPos fakePlayerPos = context.getAbsolutePos(basePos.add(2, 0, 2));
fakePlayer.setPosition(fakePlayerPos.getX(), fakePlayerPos.getY(), fakePlayerPos.getZ());
context.assertTrue(fakePlayer.interactionManager.tryBreakBlock(context.getAbsolutePos(basePos)), "Block was not broken");
context.assertTrue(fakePlayer.interactionManager.tryBreakBlock(context.getAbsolutePos(basePos)), Text.literal("Block was not broken"));
context.expectBlock(Blocks.AIR, basePos);
context.complete();
}
*/
}

View file

@ -17,5 +17,5 @@ loom {
moduleDependencies(project, [
'fabric-api-base',
'fabric-resource-loader-v0'
'fabric-resource-loader-v0',
])

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.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);
}
}

View file

@ -0,0 +1,89 @@
/*
* 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.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.minecraft.util.BlockRotation;
/**
* {@link GameTest} is an annotation that can be used to mark a method as a game test.
*
* <p>{@link GameTest} methods must be {@code public} not {@code static}, return {@code void } and take exactly one argument of type {@link net.minecraft.test.TestContext}.
*
* <p>The values in this class directly correspond to the values in {@link net.minecraft.test.TestData}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface GameTest {
/**
* A namespaced ID of an entry within the {@link net.minecraft.registry.RegistryKeys#TEST_ENVIRONMENT} registry.
*/
String environment() default "minecraft:default";
/**
* A namespaced ID pointing to a structure resource in the {@code modid/gametest/structure/} directory.
*
* <p>Defaults to an 8x8 structure with no blocks.
*/
String structure() default "fabric-gametest-api-v1:empty";
/**
* The maximum number of ticks the test is allowed to run for.
*/
int maxTicks() default 20;
/**
* The number of ticks to wait before starting the test after placing the structure.
*/
int setupTicks() default 0;
/**
* Whether the test is required to pass for the test suite to pass.
*/
boolean required() default true;
/**
* The rotation of the structure when placed.
*/
BlockRotation rotation() default BlockRotation.NONE;
/**
* When set the test must be ran manually.
*/
boolean manualOnly() default false;
/**
* The number of times the test should be re attempted if it fails.
*/
int maxAttempts() default 1;
/**
* The number of times the test should be successfully ran before it is considered a success.
*/
int requiredSuccesses() default 1;
/**
* Whether the test should have sky access. When {@code false} the test will be enclosed by barrier blocks.
*/
boolean skyAccess() default false;
}

View file

@ -33,8 +33,8 @@
* <p>Each "test method" represents a set of code that sets up the testing site and checks the
* behavior of the code - for example, it could check that using a flint and steel on a creeper
* causes explosion, or that hoppers can insert items into barrels. A test method is always annotated
* with {@link net.minecraft.test.GameTest}. For most cases you can set the {@link
* net.minecraft.test.GameTest#templateName()} as {@link net.fabricmc.fabric.api.gametest.v1.FabricGameTest#EMPTY_STRUCTURE}.
* with {@link net.fabricmc.fabric.api.gametest.v1.GameTest}. By default the test will run with
* an empty structure, you can specify a structure using {@link net.fabricmc.fabric.api.gametest.v1.GameTest#structure()}
* For complex tests, you can also save a structure as an SNBT file under {@code modid/gametest/structure/}
* in the test mod's data pack and reference that structure. It will then be loaded before the test.
*
@ -46,7 +46,7 @@
* <p>Example of a test method:
* <pre>{@code
* public class MyTest {
* @GameTest(templateName=FabricGameTest.EMPTY_STRUCTURE)
* @FabricGameTest
* public void testSomething(TestContext context) {
* context.assertTrue(MyMod.getSomeValue(context.getWorld()) > 0, "SomeValue should be positive.");
* context.complete(); // do not forget!
@ -78,7 +78,6 @@
* }
* }</pre>
*
* @see net.minecraft.test.GameTest
* @see net.fabricmc.fabric.api.gametest.v1.FabricGameTest
* @see net.fabricmc.fabric.api.gametest.v1.GameTest
*/
package net.fabricmc.fabric.api.gametest.v1;

View file

@ -1,132 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.resource.ResourceFinder;
import net.minecraft.resource.ResourcePackManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.TestCommand;
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.util.math.BlockPos;
import net.minecraft.world.level.storage.LevelStorage;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.loader.api.FabricLoader;
public final class FabricGameTestHelper {
public static final boolean ENABLED = System.getProperty("fabric-api.gametest") != null;
/**
* When enabled the {@link TestCommand} and related arguments will be registered.
*
* <p>When {@link EnvType#CLIENT} the default value is true.
*
* <p>When {@link EnvType#SERVER} the default value is false.
*/
public static final boolean COMMAND_ENABLED = Boolean.parseBoolean(System.getProperty("fabric-api.gametest.command", FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ? "true" : "false"));
private static final Logger LOGGER = LoggerFactory.getLogger(FabricGameTestHelper.class);
private static final String GAMETEST_STRUCTURE_PATH = "gametest/structure";
public static final ResourceFinder GAMETEST_STRUCTURE_FINDER = new ResourceFinder(GAMETEST_STRUCTURE_PATH, ".snbt");
private FabricGameTestHelper() {
}
public static void runHeadlessServer(LevelStorage.Session session, ResourcePackManager resourcePackManager) {
String reportPath = System.getProperty("fabric-api.gametest.report-file");
if (reportPath != null) {
try {
TestFailureLogger.setCompletionListener(new SavingXmlReportingTestCompletionListener(new File(reportPath)));
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
LOGGER.info("Starting test server");
MinecraftServer server = TestServer.startServer(thread -> {
return TestServer.create(thread, session, resourcePackManager, getTestFunctions(), BlockPos.ORIGIN);
});
}
public static Consumer<TestContext> 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 e) {
throw new RuntimeException("Failed to invoke test method (%s) in (%s) because %s".formatted(method.getName(), method.getDeclaringClass().getCanonicalName(), e.getMessage()), e);
} catch (InvocationTargetException e) {
LOGGER.error("Exception occurred when invoking test method {} in ({})", method.getName(), method.getDeclaringClass().getCanonicalName(), e);
if (e.getCause() instanceof RuntimeException runtimeException) {
throw runtimeException;
} else {
throw new RuntimeException(e.getCause());
}
}
}
private static Collection<TestFunction> getTestFunctions() {
return TestFunctions.getTestFunctions();
}
}

View file

@ -16,49 +16,55 @@
package net.fabricmc.fabric.impl.gametest;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.test.TestFunctions;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryLoader;
import net.minecraft.test.TestEnvironmentDefinition;
import net.minecraft.test.TestInstance;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
public final class FabricGameTestModInitializer implements ModInitializer {
private static final String ENTRYPOINT_KEY = "fabric-gametest";
private static final Map<Class<?>, String> GAME_TEST_IDS = new HashMap<>();
private static final Logger LOGGER = LoggerFactory.getLogger(FabricGameTestModInitializer.class);
private static TestAnnotationLocator locator = new TestAnnotationLocator(FabricLoader.getInstance());
@Override
public void onInitialize() {
List<EntrypointContainer<Object>> entrypointContainers = FabricLoader.getInstance()
.getEntrypointContainers(ENTRYPOINT_KEY, Object.class);
if (!(FabricGameTestRunner.ENABLED || FabricLoader.getInstance().isDevelopmentEnvironment())) {
// Don't try to load the tests if the game test runner is disabled or we are not in a development environment
return;
}
for (EntrypointContainer<Object> 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);
for (TestAnnotationLocator.TestMethod testMethod : locator.getTestMethods()) {
LOGGER.debug("Registering test method: {}", testMethod.identifier());
Registry.register(Registries.TEST_FUNCTION, testMethod.identifier(), testMethod.testFunction());
}
}
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));
public static void registerDynamicEntries(List<RegistryLoader.Loader<?>> registriesList) {
Map<RegistryKey<? extends Registry<?>>, Registry<?>> registries = new IdentityHashMap<>(registriesList.size());
for (RegistryLoader.Loader<?> entry : registriesList) {
registries.put(entry.registry().getKey(), entry.registry());
}
return GAME_TEST_IDS.get(testClass);
Registry<TestInstance> testInstances = (Registry<TestInstance>) registries.get(RegistryKeys.TEST_INSTANCE);
Registry<TestEnvironmentDefinition> testEnvironmentDefinitionRegistry = (Registry<TestEnvironmentDefinition>) Objects.requireNonNull(registries.get(RegistryKeys.TEST_ENVIRONMENT));
for (TestAnnotationLocator.TestMethod testMethod : locator.getTestMethods()) {
TestInstance testInstance = testMethod.testInstance(testEnvironmentDefinitionRegistry);
Registry.register(testInstances, testMethod.identifier(), testInstance);
}
}
}

View file

@ -0,0 +1,62 @@
/*
* 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.util.Optional;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.resource.ResourceFinder;
import net.minecraft.resource.ResourcePackManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.test.TestFailureLogger;
import net.minecraft.test.TestServer;
import net.minecraft.world.level.storage.LevelStorage;
public final class FabricGameTestRunner {
public static final boolean ENABLED = System.getProperty(GameTestSystemProperties.ENABLED) != null;
private static final Logger LOGGER = LoggerFactory.getLogger(FabricGameTestRunner.class);
private static final String GAMETEST_STRUCTURE_PATH = "gametest/structure";
public static final ResourceFinder GAMETEST_STRUCTURE_FINDER = new ResourceFinder(GAMETEST_STRUCTURE_PATH, ".snbt");
private FabricGameTestRunner() {
}
public static void runHeadlessServer(LevelStorage.Session session, ResourcePackManager resourcePackManager) {
String reportPath = System.getProperty(GameTestSystemProperties.REPORT_FILE);
if (reportPath != null) {
try {
TestFailureLogger.setCompletionListener(new SavingXmlReportingTestCompletionListener(new File(reportPath)));
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
LOGGER.info("Starting test server");
Optional<String> filter = Optional.ofNullable(System.getProperty(GameTestSystemProperties.FILTER));
boolean verify = Boolean.getBoolean(GameTestSystemProperties.VERIFY);
MinecraftServer.startServer((thread) -> TestServer.create(thread, session, resourcePackManager, filter, verify));
}
}

View file

@ -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.impl.gametest;
public final class GameTestSystemProperties {
/**
* Enable the game test system.
*/
public static final String ENABLED = "fabric-api.gametest";
/**
* A JUnit XML report file to write the test results to.
*/
public static final String REPORT_FILE = "fabric-api.gametest.report-file";
/**
* Filter the tests to run by the test name.
*/
public static final String FILTER = "fabric-api.gametest.filter";
/**
* Run the enabled tests 100 times for each 90 degree rotation.
*/
public static final String VERIFY = "fabric-api.gametest.verify";
private GameTestSystemProperties() {
}
}

View file

@ -0,0 +1,157 @@
/*
* 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.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.test.FunctionTestInstance;
import net.minecraft.test.TestContext;
import net.minecraft.test.TestData;
import net.minecraft.test.TestEnvironmentDefinition;
import net.minecraft.test.TestInstance;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
final class TestAnnotationLocator {
private static final String ENTRYPOINT_KEY = "fabric-gametest";
private static final Logger LOGGER = LoggerFactory.getLogger(TestAnnotationLocator.class);
private final FabricLoader fabricLoader;
private List<TestMethod> testMethods = null;
TestAnnotationLocator(FabricLoader fabricLoader) {
this.fabricLoader = fabricLoader;
}
public List<TestMethod> getTestMethods() {
if (testMethods != null) {
return testMethods;
}
List<EntrypointContainer<Object>> entrypointContainers = fabricLoader
.getEntrypointContainers(ENTRYPOINT_KEY, Object.class);
return testMethods = entrypointContainers.stream()
.flatMap(entrypoint -> findMagicMethods(entrypoint).stream())
.toList();
}
private List<TestMethod> findMagicMethods(EntrypointContainer<Object> entrypoint) {
Class<?> testClass = entrypoint.getEntrypoint().getClass();
List<TestMethod> methods = new ArrayList<>();
findMagicMethods(entrypoint, testClass, methods);
if (methods.isEmpty()) {
LOGGER.warn("No methods with the FabricGameTest annotation were found in {}", testClass.getName());
}
return methods;
}
// Recursively find all methods with the GameTest annotation
private void findMagicMethods(EntrypointContainer<Object> entrypoint, Class<?> testClass, List<TestMethod> methods) {
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(GameTest.class)) {
validateMethod(method);
methods.add(new TestMethod(method, method.getAnnotation(GameTest.class), entrypoint));
}
}
if (testClass.getSuperclass() != null) {
findMagicMethods(entrypoint, testClass.getSuperclass(), methods);
}
}
private void validateMethod(Method method) {
if (method.getParameterCount() != 1 || method.getParameterTypes()[0] != TestContext.class) {
throw new UnsupportedOperationException("Method %s must have a single parameter of type TestContext".formatted(method.getName()));
}
if (!Modifier.isPublic(method.getModifiers())) {
throw new UnsupportedOperationException("Method %s must be public".formatted(method.getName()));
}
if (Modifier.isStatic(method.getModifiers())) {
throw new UnsupportedOperationException("Method %s must not be static".formatted(method.getName()));
}
if (method.getReturnType() != void.class) {
throw new UnsupportedOperationException("Method %s must return void".formatted(method.getName()));
}
}
public record TestMethod(Method method, GameTest gameTest, EntrypointContainer<Object> entrypoint) {
Identifier identifier() {
String name = camelToSnake(entrypoint.getEntrypoint().getClass().getSimpleName() + "_" + method.getName());
return Identifier.of(entrypoint.getProvider().getMetadata().getId(), name);
}
Consumer<TestContext> testFunction() {
return context -> {
try {
method.invoke(entrypoint.getEntrypoint(), context);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to invoke test method", e);
}
};
}
TestData<RegistryEntry<TestEnvironmentDefinition>> testData(Registry<TestEnvironmentDefinition> testEnvironmentDefinitionRegistry) {
RegistryEntry<TestEnvironmentDefinition> testEnvironment = testEnvironmentDefinitionRegistry.getOrThrow(RegistryKey.of(RegistryKeys.TEST_ENVIRONMENT, Identifier.of(gameTest.environment())));
return new TestData<>(
testEnvironment,
Identifier.of(gameTest.structure()),
gameTest.maxTicks(),
gameTest.setupTicks(),
gameTest.required(),
gameTest.rotation(),
gameTest.manualOnly(),
gameTest.maxAttempts(),
gameTest.requiredSuccesses(),
gameTest.skyAccess()
);
}
TestInstance testInstance(Registry<TestEnvironmentDefinition> testEnvironmentDefinitionRegistry) {
return new FunctionTestInstance(
Registries.TEST_FUNCTION.getEntry(identifier()).get(),
testData(testEnvironmentDefinitionRegistry)
);
}
private static String camelToSnake(String input) {
return input.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
}
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gametest;
import com.mojang.brigadier.arguments.ArgumentType;
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.CallbackInfoReturnable;
import net.minecraft.SharedConstants;
import net.minecraft.command.argument.ArgumentTypes;
import net.minecraft.command.argument.TestClassArgumentType;
import net.minecraft.command.argument.TestFunctionArgumentType;
import net.minecraft.command.argument.serialize.ArgumentSerializer;
import net.minecraft.command.argument.serialize.ConstantArgumentSerializer;
import net.minecraft.registry.Registry;
import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper;
@Mixin(ArgumentTypes.class)
public abstract class ArgumentTypesMixin {
@Shadow
private static <A extends ArgumentType<?>, T extends ArgumentSerializer.ArgumentTypeProperties<A>> ArgumentSerializer<A, T> register(Registry<ArgumentSerializer<?, ?>> registry, String string, Class<? extends A> clazz, ArgumentSerializer<A, T> argumentSerializer) {
throw new AssertionError("Nope.");
}
@Inject(method = "register(Lnet/minecraft/registry/Registry;)Lnet/minecraft/command/argument/serialize/ArgumentSerializer;", at = @At("RETURN"))
private static void register(Registry<ArgumentSerializer<?, ?>> registry, CallbackInfoReturnable<ArgumentSerializer<?, ?>> ci) {
// Registered by vanilla when isDevelopment is enabled.
if (FabricGameTestHelper.COMMAND_ENABLED && !SharedConstants.isDevelopment) {
register(registry, "test_argument", TestFunctionArgumentType.class, ConstantArgumentSerializer.of(TestFunctionArgumentType::testFunction));
register(registry, "test_class", TestClassArgumentType.class, ConstantArgumentSerializer.of(TestClassArgumentType::testClass));
}
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gametest;
import com.mojang.brigadier.CommandDispatcher;
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 net.minecraft.SharedConstants;
import net.minecraft.command.CommandRegistryAccess;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.command.TestCommand;
import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper;
@Mixin(CommandManager.class)
public abstract class CommandManagerMixin {
@Shadow
@Final
private CommandDispatcher<ServerCommandSource> dispatcher;
@Inject(method = "<init>", 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, CommandRegistryAccess registryAccess, CallbackInfo info) {
// Registered by vanilla when isDevelopment is enabled.
if (FabricGameTestHelper.COMMAND_ENABLED && !SharedConstants.isDevelopment) {
TestCommand.register(this.dispatcher);
}
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gametest;
import java.util.function.BooleanSupplier;
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 net.minecraft.SharedConstants;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTickManager;
import net.minecraft.test.TestManager;
@Mixin(MinecraftServer.class)
public abstract class MinecraftServerMixin {
@Shadow
@Final
private ServerTickManager tickManager;
@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 && this.tickManager.shouldTick()) {
TestManager.INSTANCE.tick();
}
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.llamalad7.mixinextras.sugar.Local;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryLoader;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.resource.ResourceManager;
import net.fabricmc.fabric.impl.gametest.FabricGameTestModInitializer;
@Mixin(RegistryLoader.class)
public class RegistryLoaderMixin {
@Unique
private static final AtomicBoolean LOADING_DYNAMIC_REGISTRIES = new AtomicBoolean(false);
@Inject(method = "loadFromResource(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Ljava/util/List;)Lnet/minecraft/registry/DynamicRegistryManager$Immutable;", at = @At("HEAD"))
private static void loadFromResources(ResourceManager resourceManager, List<RegistryWrapper.Impl<?>> registries, List<RegistryLoader.Entry<?>> entries, CallbackInfoReturnable<DynamicRegistryManager.Immutable> cir) {
LOADING_DYNAMIC_REGISTRIES.set(entries.stream().anyMatch(entry -> entry.key() == RegistryKeys.TEST_INSTANCE));
}
@Inject(
method = "load(Lnet/minecraft/registry/RegistryLoader$RegistryLoadable;Ljava/util/List;Ljava/util/List;)Lnet/minecraft/registry/DynamicRegistryManager$Immutable;",
at = @At(
value = "INVOKE",
target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V",
ordinal = 1
)
)
private static void beforeFreeze(@Coerce Object loadable, List<RegistryWrapper.Impl<?>> wrappers, List<RegistryLoader.Entry<?>> entries, CallbackInfoReturnable<DynamicRegistryManager.Immutable> cir, @Local(ordinal = 2) List<RegistryLoader.Loader<?>> registriesList) {
if (LOADING_DYNAMIC_REGISTRIES.getAndSet(false)) {
FabricGameTestModInitializer.registerDynamicEntries(registriesList);
}
}
}

View file

@ -43,7 +43,7 @@ import net.minecraft.structure.StructureTemplateManager;
import net.minecraft.util.Identifier;
import net.minecraft.world.level.storage.LevelStorage;
import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper;
import net.fabricmc.fabric.impl.gametest.FabricGameTestRunner;
@Mixin(StructureTemplateManager.class)
public abstract class StructureTemplateManagerMixin {
@ -54,7 +54,7 @@ public abstract class StructureTemplateManagerMixin {
public abstract StructureTemplate createTemplate(NbtCompound nbt);
private Optional<StructureTemplate> fabric_loadSnbtFromResource(Identifier id) {
Identifier path = FabricGameTestHelper.GAMETEST_STRUCTURE_FINDER.toResourcePath(id);
Identifier path = FabricGameTestRunner.GAMETEST_STRUCTURE_FINDER.toResourcePath(id);
Optional<Resource> resource = this.resourceManager.getResource(path);
if (resource.isPresent()) {
@ -71,7 +71,7 @@ public abstract class StructureTemplateManagerMixin {
}
private Stream<Identifier> fabric_streamTemplatesFromResource() {
ResourceFinder finder = FabricGameTestHelper.GAMETEST_STRUCTURE_FINDER;
ResourceFinder finder = FabricGameTestRunner.GAMETEST_STRUCTURE_FINDER;
return finder.findResources(this.resourceManager).keySet().stream().map(finder::toResourceId);
}

View file

@ -1,45 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.gametest;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import net.minecraft.server.command.TestCommand;
@Mixin(TestCommand.class)
public class TestCommandMixin {
@Unique
private static final String OUTPUT_DIR = System.getProperty("fabric-api.gametest.structures.output-dir");
@ModifyArg(
method = "executeExport(Lnet/minecraft/server/command/ServerCommandSource;Ljava/lang/String;)I",
at = @At(
value = "INVOKE",
target = "Ljava/nio/file/Paths;get(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;"
)
)
private static String useCustomOutputDirectory(String first) {
if (OUTPUT_DIR != null) {
return OUTPUT_DIR;
}
return first;
}
}

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.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<TestFunction> 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.templateName().isEmpty()) {
structureName = gameTest.templateName();
}
TestFunction testFunction = new TestFunction(gameTest.batchId(),
testCaseName,
structureName,
StructureTestUtil.getRotation(gameTest.rotation()),
gameTest.tickLimit(),
gameTest.duration(),
gameTest.required(),
gameTest.manualOnly(),
gameTest.maxAttempts(),
gameTest.requiredSuccesses(),
gameTest.skyAccess(),
FabricGameTestHelper.getTestMethodInvoker(method)
);
cir.setReturnValue(testFunction);
}
}

View file

@ -27,20 +27,20 @@ import net.minecraft.resource.ResourcePackManager;
import net.minecraft.server.Main;
import net.minecraft.world.level.storage.LevelStorage;
import net.fabricmc.fabric.impl.gametest.FabricGameTestHelper;
import net.fabricmc.fabric.impl.gametest.FabricGameTestRunner;
@Mixin(Main.class)
public class MainMixin {
@ModifyExpressionValue(method = "main", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/dedicated/EulaReader;isEulaAgreedTo()Z"))
private static boolean isEulaAgreedTo(boolean isEulaAgreedTo) {
return FabricGameTestHelper.ENABLED || isEulaAgreedTo;
return FabricGameTestRunner.ENABLED || isEulaAgreedTo;
}
// Inject after resourcePackManager is stored
@Inject(method = "main", cancellable = true, at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/resource/VanillaDataPackProvider;createManager(Lnet/minecraft/world/level/storage/LevelStorage$Session;)Lnet/minecraft/resource/ResourcePackManager;"))
private static void main(String[] args, CallbackInfo info, @Local LevelStorage.Session session, @Local ResourcePackManager resourcePackManager) {
if (FabricGameTestHelper.ENABLED) {
FabricGameTestHelper.runHeadlessServer(session, resourcePackManager);
if (FabricGameTestRunner.ENABLED) {
FabricGameTestRunner.runHeadlessServer(session, resourcePackManager);
info.cancel(); // Do not progress in starting the normal dedicated server
}
}
@ -49,7 +49,7 @@ public class MainMixin {
// Otherwise gradlew test will succeed without errors, although no tests have been run.
@Inject(method = "main", at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;error(Lorg/slf4j/Marker;Ljava/lang/String;Ljava/lang/Throwable;)V", shift = At.Shift.AFTER, remap = false), remap = false)
private static void exitOnError(CallbackInfo info) {
if (FabricGameTestHelper.ENABLED) {
if (FabricGameTestRunner.ENABLED) {
System.exit(-1);
}
}

View file

@ -2,3 +2,5 @@ accessWidener v2 named
accessible class net/minecraft/structure/StructureTemplateManager$Provider
accessible method net/minecraft/structure/StructureTemplateManager$Provider <init> (Ljava/util/function/Function;Ljava/util/function/Supplier;)V
accessible class net/minecraft/registry/RegistryLoader$Loader

View file

@ -3,12 +3,8 @@
"package": "net.fabricmc.fabric.mixin.gametest",
"compatibilityLevel": "JAVA_21",
"mixins": [
"ArgumentTypesMixin",
"CommandManagerMixin",
"MinecraftServerMixin",
"RegistryLoaderMixin",
"StructureTemplateManagerMixin",
"TestCommandMixin",
"TestFunctionsMixin",
"TestServerMixin"
],
"server": [

View file

@ -16,48 +16,27 @@
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.text.Text;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
// 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) {
public class ExampleFabricTestSuite {
@GameTest(structure = "fabric-gametest-api-v1-testmod:exampletestsuite.diamond")
public void diamond(TestContext context) {
// Nothing to do as the structure placed the block.
context.addInstantFinalTask(() ->
context.checkBlock(new BlockPos(0, 1, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be diamond")
context.checkBlock(new BlockPos(0, 1, 0), (block) -> block == Blocks.DIAMOND_BLOCK, (b) -> Text.literal("Expect block to be diamond"))
);
}
@GameTest(templateName = "fabric-gametest-api-v1-testmod:exampletestsuite.diamond")
public void diamond(TestContext context) {
// Nothing to do as the structure placed the block.
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void noStructure(TestContext context) {
context.setBlockState(0, 1, 0, Blocks.DIAMOND_BLOCK);
context.addInstantFinalTask(() ->
context.checkBlock(new BlockPos(0, 1, 0), (block) -> block == Blocks.DIAMOND_BLOCK, (b) -> Text.literal("Expect block to be diamond"))
);
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.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, 1, 0), (block) -> block == Blocks.DIAMOND_BLOCK, "Expect block to be diamond")
);
}
@GameTest(templateName = 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")
);
}
}

View file

@ -7,8 +7,7 @@
"license": "Apache-2.0",
"entrypoints": {
"fabric-gametest" : [
"net.fabricmc.fabric.test.gametest.ExampleFabricTestSuite",
"net.fabricmc.fabric.test.gametest.ExampleTestSuite"
"net.fabricmc.fabric.test.gametest.ExampleFabricTestSuite"
]
}
}

View file

@ -16,17 +16,29 @@
package net.fabricmc.fabric.test.item.gametest;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.Potion;
import net.minecraft.potion.Potions;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.test.item.CustomDamageTest;
public class BrewingStandGameTest {
private static final int BREWING_TIME = 800;
private static final BlockPos POS = new BlockPos(0, 1, 0);
/* TODO 1.21.5 tests
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void basicBrewing(TestContext context) {
context.setBlockState(POS, Blocks.BREWING_STAND);
BrewingStandBlockEntity blockEntity = context.getBlockEntity(POS);
BrewingStandBlockEntity blockEntity = context.getBlockEntity(POS, BrewingStandBlockEntity.class);
loadFuel(blockEntity, context);
@ -44,10 +56,10 @@ public class BrewingStandGameTest {
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void vanillaRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.BREWING_STAND);
BrewingStandBlockEntity blockEntity = context.getBlockEntity(POS);
BrewingStandBlockEntity blockEntity = context.getBlockEntity(POS, BrewingStandBlockEntity.class);
loadFuel(blockEntity, context);
@ -69,7 +81,7 @@ public class BrewingStandGameTest {
// Skip see: https://github.com/FabricMC/fabric/pull/2874
public void fabricRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.BREWING_STAND);
BrewingStandBlockEntity blockEntity = context.getBlockEntity(POS);
BrewingStandBlockEntity blockEntity = context.getBlockEntity(POS, BrewingStandBlockEntity.class);
loadFuel(blockEntity, context);
@ -140,6 +152,4 @@ public class BrewingStandGameTest {
itemStack.set(DataComponentTypes.POTION_CONTENTS, new PotionContentsComponent(potion));
return itemStack;
}
*/
}

View file

@ -16,9 +16,34 @@
package net.fabricmc.fabric.test.item.gametest;
import java.util.List;
import java.util.Optional;
import net.minecraft.component.EnchantmentEffectComponentTypes;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.effect.EnchantmentEffectEntry;
import net.minecraft.enchantment.effect.EnchantmentValueEffect;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.mob.CreeperEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameMode;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.test.item.CustomEnchantmentEffectsTest;
public class CustomEnchantmentEffectsGameTest {
/* TODO 1.21.5 tests
@GameTest(templateName = "fabric-item-api-v1-testmod:bedrock_platform")
@GameTest
public void weirdImpalingSetsFireToTargets(TestContext context) {
BlockPos pos = new BlockPos(3, 3, 3);
CreeperEntity creeper = context.spawnEntity(EntityType.CREEPER, pos);
@ -28,7 +53,7 @@ public class CustomEnchantmentEffectsGameTest {
Optional<RegistryEntry.Reference<Enchantment>> impaling = getEnchantmentRegistry(context)
.getOptional(CustomEnchantmentEffectsTest.WEIRD_IMPALING);
if (impaling.isEmpty()) {
throw new GameTestException("Weird Impaling enchantment is not present");
throw context.createError("Weird Impaling enchantment is not present");
}
trident.addEnchantment(impaling.get(), 1);
@ -40,12 +65,12 @@ public class CustomEnchantmentEffectsGameTest {
context.expectEntityWithDataEnd(pos, EntityType.CREEPER, Entity::isOnFire, true);
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void weirdImpalingHasTwoDamageEffects(TestContext context) {
Enchantment impaling = getEnchantmentRegistry(context).get(CustomEnchantmentEffectsTest.WEIRD_IMPALING);
if (impaling == null) {
throw new GameTestException("Weird Impaling enchantment is not present");
throw context.createError("Weird Impaling enchantment is not present");
}
List<EnchantmentEffectEntry<EnchantmentValueEffect>> damageEffects = impaling
@ -53,7 +78,7 @@ public class CustomEnchantmentEffectsGameTest {
context.assertTrue(
damageEffects.size() == 2,
String.format("Weird Impaling has %d damage effect(s), not the expected 2", damageEffects.size())
Text.literal(String.format("Weird Impaling has %d damage effect(s), not the expected 2", damageEffects.size()))
);
context.complete();
}
@ -62,6 +87,4 @@ public class CustomEnchantmentEffectsGameTest {
DynamicRegistryManager registryManager = context.getWorld().getRegistryManager();
return registryManager.getOrThrow(RegistryKeys.ENCHANTMENT);
}
*/
}

View file

@ -16,17 +16,28 @@
package net.fabricmc.fabric.test.item.gametest;
import java.util.function.Consumer;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.FireworksComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class DefaultItemComponentGameTest {
/* TODO 1.21.5 tests
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void modify(TestContext context) {
Consumer<Text> checkText = text -> {
if (text == null) {
throw new GameTestException("Item name component not found on gold ingot");
throw context.createError("Item name component not found on gold ingot");
}
if (!"Fool's Gold".equals(text.getString())) {
throw new GameTestException("Item name component on gold ingot is not set");
throw context.createError("Item name component on gold ingot is not set");
}
};
@ -39,30 +50,30 @@ public class DefaultItemComponentGameTest {
boolean isBeefFood = Items.BEEF.getComponents().contains(DataComponentTypes.FOOD);
if (isBeefFood) {
throw new GameTestException("Food component not removed from beef");
throw context.createError("Food component not removed from beef");
}
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void afterModify(TestContext context) {
FireworksComponent fireworksComponent = Items.GOLD_NUGGET.getComponents().get(DataComponentTypes.FIREWORKS);
if (fireworksComponent == null) {
throw new GameTestException("Fireworks component not found on gold nugget");
throw context.createError("Fireworks component not found on gold nugget");
}
Boolean enchantGlint = Items.GOLD_NUGGET.getComponents().get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE);
if (enchantGlint != Boolean.TRUE) {
throw new GameTestException("Enchantment glint override not set on gold nugget");
throw context.createError("Enchantment glint override not set on gold nugget");
}
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void diamondPickaxeIsRenamed(TestContext context) {
Item testItem = Items.DIAMOND_PICKAXE;
ItemStack stack = testItem.getDefaultStack();
@ -73,10 +84,8 @@ public class DefaultItemComponentGameTest {
String errorMessage = "Expected '%s' to be contained in '%s', but it was not!";
// if they contain each other, then they are equal
context.assertTrue(itemName.contains(expectedName), errorMessage.formatted(expectedName, itemName));
context.assertTrue(expectedName.contains(itemName), errorMessage.formatted(itemName, expectedName));
context.assertTrue(itemName.contains(expectedName), Text.literal(errorMessage.formatted(expectedName, itemName)));
context.assertTrue(expectedName.contains(itemName), Text.literal(errorMessage.formatted(itemName, expectedName)));
context.complete();
}
*/
}

View file

@ -16,21 +16,25 @@
package net.fabricmc.fabric.test.item.gametest;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.block.entity.FurnaceBlockEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.test.item.CustomDamageTest;
public class FurnaceGameTest {
private static final int COOK_TIME = 200;
private static final BlockPos POS = new BlockPos(0, 1, 0);
/* TODO 1.21.5 tests
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void basicSmelt(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS, FurnaceBlockEntity.class);
setInputs(blockEntity, new ItemStack(Blocks.COBBLESTONE, 8), new ItemStack(Items.COAL, 2));
@ -49,10 +53,10 @@ public class FurnaceGameTest {
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void vanillaRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS, FurnaceBlockEntity.class);
setInputs(blockEntity, new ItemStack(Blocks.COBBLESTONE, 64), new ItemStack(Items.LAVA_BUCKET));
@ -65,10 +69,10 @@ public class FurnaceGameTest {
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void fabricRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS, FurnaceBlockEntity.class);
setInputs(blockEntity, new ItemStack(Blocks.COBBLESTONE, 32), new ItemStack(CustomDamageTest.WEIRD_PICK));
@ -93,8 +97,6 @@ public class FurnaceGameTest {
context.complete();
}
*/
private void setInputs(FurnaceBlockEntity blockEntity, ItemStack ingredient, ItemStack fuel) {
blockEntity.setStack(0, ingredient);
blockEntity.setStack(1, fuel);
@ -105,8 +107,7 @@ public class FurnaceGameTest {
ItemStack currentStack = blockEntity.getStack(i);
ItemStack expectedStack = stacks[i];
throw new AssertionError("Not implemented yet");
// RecipeGameTest.assertStacks(currentStack, expectedStack, extraErrorInfo);
RecipeGameTest.assertStacks(currentStack, expectedStack, extraErrorInfo);
}
}

View file

@ -16,9 +16,20 @@
package net.fabricmc.fabric.test.item.gametest;
import java.util.List;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.recipe.CraftingRecipe;
import net.minecraft.recipe.input.CraftingRecipeInput;
import net.minecraft.test.TestContext;
import net.minecraft.util.collection.DefaultedList;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.test.item.CustomDamageTest;
public class RecipeGameTest {
/* TODO 1.21.5 tests
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void vanillaRemainderTest(TestContext context) {
CraftingRecipeInput inventory = CraftingRecipeInput.create(1, 2, List.of(
new ItemStack(Items.WATER_BUCKET),
@ -33,7 +44,7 @@ public class RecipeGameTest {
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
@GameTest
public void fabricRemainderTest(TestContext context) {
CraftingRecipeInput inventory = CraftingRecipeInput.create(1, 4, List.of(
new ItemStack(CustomDamageTest.WEIRD_PICK),
@ -67,15 +78,15 @@ public class RecipeGameTest {
}
if (!currentStack.isOf(expectedStack.getItem())) {
throw new GameTestException("Item stacks dont match. " + extraErrorInfo);
throw new RuntimeException("Item stacks dont match. " + extraErrorInfo);
}
if (currentStack.getCount() != expectedStack.getCount()) {
throw new GameTestException("Size doesnt match. " + extraErrorInfo);
throw new RuntimeException("Size doesnt match. " + extraErrorInfo);
}
if (!ItemStack.areItemsAndComponentsEqual(currentStack, expectedStack)) {
throw new GameTestException("Stack doesnt match. " + extraErrorInfo);
throw new RuntimeException("Stack doesnt match. " + extraErrorInfo);
}
}
@ -83,6 +94,4 @@ public class RecipeGameTest {
stack.setDamage(damage);
return stack;
}
*/
}

View file

@ -22,8 +22,10 @@ import net.minecraft.block.Block;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class ObjectBuilderGameTest {
// @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) TODO 1.21.5 tests
@GameTest
public void testBlockUse(TestContext context) {
List<Block> blocks = List.of(BlockEntityTypeBuilderTest.INITIAL_BETRAYAL_BLOCK, BlockEntityTypeBuilderTest.ADDED_BETRAYAL_BLOCK, BlockEntityTypeBuilderTest.FIRST_MULTI_BETRAYAL_BLOCK, BlockEntityTypeBuilderTest.SECOND_MULTI_BETRAYAL_BLOCK);
BlockPos.Mutable pos = BlockPos.ORIGIN.up().mutableCopy();

View file

@ -32,11 +32,13 @@ import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class ShapelessRecipeMatchTests {
/**
* The recipe requires at least one undamaged pickaxe.
*/
// @GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) TODO 1.21.5 tests
@GameTest
public void testShapelessMatch(TestContext context) {
RegistryKey<Recipe<?>> recipeKey = RegistryKey.of(RegistryKeys.RECIPE, Identifier.of("fabric-recipe-api-v1-testmod", "test_shapeless_match"));
ShapelessRecipe recipe = (ShapelessRecipe) context.getWorld().getRecipeManager().get(recipeKey).get().value();

View file

@ -10,8 +10,6 @@
},
"entrypoints": {
"fabric-gametest": [
"net.fabricmc.fabric.test.recipe.ingredient.IngredientMatchTests",
"net.fabricmc.fabric.test.recipe.ingredient.SerializationTests",
"net.fabricmc.fabric.test.recipe.ingredient.ShapelessRecipeMatchTests"
]
}

View file

@ -16,8 +16,19 @@
package net.fabricmc.fabric.test.resource.conditions;
import net.minecraft.block.entity.BannerPattern;
import net.minecraft.loot.LootTable;
import net.minecraft.recipe.ServerRecipeManager;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryEntryLookup;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.ReloadableRegistries;
import net.minecraft.test.TestContext;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class ConditionalResourcesTest {
private static final String MOD_ID = "fabric-resource-conditions-api-v1-testmod";
@ -25,8 +36,7 @@ public class ConditionalResourcesTest {
return Identifier.of(MOD_ID, path);
}
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void conditionalRecipes(TestContext context) {
ServerRecipeManager manager = context.getWorld().getRecipeManager();
@ -64,7 +74,7 @@ public class ConditionalResourcesTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void conditionalPredicates(TestContext context) {
// Predicates are internally handled as a kind of loot data,
// hence the yarn name "loot condition".
@ -82,7 +92,7 @@ public class ConditionalResourcesTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void conditionalLootTables(TestContext context) {
ReloadableRegistries.Lookup registries = context.getWorld().getServer().getReloadableRegistries();
@ -97,7 +107,7 @@ public class ConditionalResourcesTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void conditionalDynamicRegistry(TestContext context) {
Registry<BannerPattern> registry = context.getWorld().getRegistryManager().getOrThrow(RegistryKeys.BANNER_PATTERN);
@ -112,7 +122,7 @@ public class ConditionalResourcesTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void conditionalOverlays(TestContext context) {
RegistryEntryLookup.RegistryLookup registries = context.getWorld().getServer().getReloadableRegistries().createRegistryLookup();
@ -126,6 +136,4 @@ public class ConditionalResourcesTest {
context.complete();
}
*/
}

View file

@ -16,16 +16,29 @@
package net.fabricmc.fabric.test.resource.conditions;
import java.util.stream.Collectors;
import com.mojang.serialization.JsonOps;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.tag.BiomeTags;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.resource.featuretoggle.FeatureFlag;
import net.minecraft.resource.featuretoggle.FeatureFlags;
import net.minecraft.test.TestContext;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.BiomeKeys;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl;
public class DefaultResourceConditionsTest {
private static final String TESTMOD_ID = "fabric-resource-conditions-api-v1-testmod";
@ -46,8 +59,7 @@ public class DefaultResourceConditionsTest {
ResourceCondition.CODEC.encodeStart(JsonOps.INSTANCE, condition).getOrThrow(message -> new AssertionError("Could not serialize \"%s\": %s".formatted(name, message)));
}
/* TODO 1.21.5 tests
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void featuresEnabled(TestContext context) {
ResourceCondition vanilla = ResourceConditions.featuresEnabled(FeatureFlags.VANILLA);
// Reminder: GameTest enables all features by default
@ -65,7 +77,7 @@ public class DefaultResourceConditionsTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void registryContains(TestContext context) {
// Dynamic registry (in vitro; separate testmod needs to determine if this actually functions while loading)
ResourceCondition plains = ResourceConditions.registryContains(BiomeKeys.PLAINS);
@ -79,7 +91,7 @@ public class DefaultResourceConditionsTest {
context.complete();
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void tagsPopulated(TestContext context) {
// We need to set the tags ourselves as it is cleared outside the resource loading context.
ResourceConditionsImpl.LOADED_TAGS.set(
@ -113,6 +125,4 @@ public class DefaultResourceConditionsTest {
context.complete();
}
*/
}

View file

@ -51,7 +51,7 @@ import net.minecraft.resource.ResourceType;
import net.minecraft.resource.metadata.ResourceMetadataSerializer;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.PathUtil;
import net.minecraft.util.path.PathUtil;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;

View file

@ -16,17 +16,51 @@
package net.fabricmc.fabric.test.transfer.gametests;
import org.apache.commons.lang3.mutable.MutableInt;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ComparatorBlock;
import net.minecraft.block.ComposterBlock;
import net.minecraft.block.JukeboxBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.block.entity.ChiseledBookshelfBlockEntity;
import net.minecraft.block.entity.FurnaceBlockEntity;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.state.property.Properties;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
import net.fabricmc.fabric.test.transfer.mixin.AbstractFurnaceBlockEntityAccessor;
public class VanillaStorageTests {
/**
* Regression test for https://github.com/FabricMC/fabric/issues/1972.
* Ensures that furnace cook time is only reset when extraction is actually committed.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testFurnaceCookTime(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.FURNACE.getDefaultState());
FurnaceBlockEntity furnace = context.getBlockEntity(pos);
FurnaceBlockEntity furnace = context.getBlockEntity(pos, FurnaceBlockEntity.class);
AbstractFurnaceBlockEntityAccessor accessor = (AbstractFurnaceBlockEntityAccessor) furnace;
ItemVariant rawIron = ItemVariant.of(Items.RAW_IRON);
@ -36,50 +70,47 @@ public class VanillaStorageTests {
context.runAtTick(5, () -> {
if (accessor.getCookingTimeSpent() <= 0) {
throw new GameTestException("Furnace should have started cooking.");
throw context.createError("Furnace should have started cooking.");
}
try (Transaction transaction = Transaction.openOuter()) {
if (furnaceWrapper.extract(rawIron, 64, transaction) != 64) {
throw new GameTestException("Failed to extract 64 raw iron.");
throw context.createError("Failed to extract 64 raw iron.");
}
}
if (accessor.getCookingTimeSpent() <= 0) {
throw new GameTestException("Furnace should still cook after simulation.");
throw context.createError("Furnace should still cook after simulation.");
}
try (Transaction transaction = Transaction.openOuter()) {
if (furnaceWrapper.extract(rawIron, 64, transaction) != 64) {
throw new GameTestException("Failed to extract 64 raw iron.");
throw context.createError("Failed to extract 64 raw iron.");
}
transaction.commit();
}
if (accessor.getCookingTimeSpent() != 0) {
throw new GameTestException("Furnace should have reset cook time after being emptied.");
throw context.createError("Furnace should have reset cook time after being emptied.");
}
context.complete();
});
}
*/
/**
* Tests that the passed block doesn't update adjacent comparators until the very end of a committed transaction.
*
* @param block A block with an Inventory block entity.
* @param variant The variant to try to insert (needs to be supported by the Inventory).
*/
/*
private static void testComparatorOnInventory(TestContext context, Block block, ItemVariant variant) {
private static <T extends BlockEntity & Inventory> void testComparatorOnInventory(TestContext context, Block block, ItemVariant variant, Class<T> inventoryClass) {
World world = context.getWorld();
BlockPos pos = new BlockPos(0, 2, 0);
context.setBlockState(pos, block.getDefaultState());
Inventory inventory = context.getBlockEntity(pos);
T inventory = context.getBlockEntity(pos, inventoryClass);
InventoryStorage storage = InventoryStorage.of(inventory, null);
BlockPos comparatorPos = new BlockPos(1, 2, 0);
@ -91,220 +122,199 @@ public class VanillaStorageTests {
try (Transaction transaction = Transaction.openOuter()) {
if (world.getBlockTickScheduler().isQueued(context.getAbsolutePos(comparatorPos), Blocks.COMPARATOR)) {
throw new GameTestException("Comparator should not have a tick scheduled.");
throw context.createError("Comparator should not have a tick scheduled.");
}
storage.insert(variant, 1000000, transaction);
// uncommitted insert should not schedule an update
if (world.getBlockTickScheduler().isQueued(context.getAbsolutePos(comparatorPos), Blocks.COMPARATOR)) {
throw new GameTestException("Comparator should not have a tick scheduled.");
throw context.createError("Comparator should not have a tick scheduled.");
}
transaction.commit();
// committed insert should schedule an update
if (!world.getBlockTickScheduler().isQueued(context.getAbsolutePos(comparatorPos), Blocks.COMPARATOR)) {
throw new GameTestException("Comparator should have a tick scheduled.");
throw context.createError("Comparator should have a tick scheduled.");
}
}
context.complete();
}
*/
/**
* Tests that containers such as chests don't update adjacent comparators until the very end of a committed transaction.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testChestComparator(TestContext context) {
testComparatorOnInventory(context, Blocks.CHEST, ItemVariant.of(Items.DIAMOND));
testComparatorOnInventory(context, Blocks.CHEST, ItemVariant.of(Items.DIAMOND), ChestBlockEntity.class);
}
*/
/**
* Same as {@link #testChestComparator} but for chiseled bookshelves, because their implementation is very... strange.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testChiseledBookshelfComparator(TestContext context) {
testComparatorOnInventory(context, Blocks.CHISELED_BOOKSHELF, ItemVariant.of(Items.BOOK));
testComparatorOnInventory(context, Blocks.CHISELED_BOOKSHELF, ItemVariant.of(Items.BOOK), ChiseledBookshelfBlockEntity.class);
}
*/
/**
* Test for chiseled bookshelves, because their implementation is very... strange.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testChiseledBookshelf(TestContext context) {
ItemVariant book = ItemVariant.of(Items.BOOK);
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.CHISELED_BOOKSHELF.getDefaultState());
ChiseledBookshelfBlockEntity bookshelf = context.getBlockEntity(pos);
ChiseledBookshelfBlockEntity bookshelf = context.getBlockEntity(pos, ChiseledBookshelfBlockEntity.class);
InventoryStorage storage = InventoryStorage.of(bookshelf, null);
// First, check that we can correctly undo insert operations, because vanilla's setStack doesn't permit it without our patches.
try (Transaction transaction = Transaction.openOuter()) {
if (storage.insert(book, 2, transaction) != 2) throw new GameTestException("Should have inserted 2 books");
if (storage.insert(book, 2, transaction) != 2) throw context.createError("Should have inserted 2 books");
if (bookshelf.getStack(0).getCount() != 1) throw new GameTestException("Bookshelf stack 0 should have size 1");
if (!book.matches(bookshelf.getStack(0))) throw new GameTestException("Bookshelf stack 0 should be a book");
if (bookshelf.getStack(1).getCount() != 1) throw new GameTestException("Bookshelf stack 1 should have size 1");
if (!book.matches(bookshelf.getStack(1))) throw new GameTestException("Bookshelf stack 1 should be a book");
if (bookshelf.getStack(0).getCount() != 1) throw context.createError("Bookshelf stack 0 should have size 1");
if (!book.matches(bookshelf.getStack(0))) throw context.createError("Bookshelf stack 0 should be a book");
if (bookshelf.getStack(1).getCount() != 1) throw context.createError("Bookshelf stack 1 should have size 1");
if (!book.matches(bookshelf.getStack(1))) throw context.createError("Bookshelf stack 1 should be a book");
}
if (!bookshelf.getStack(0).isEmpty()) throw new GameTestException("Bookshelf stack 0 should be empty again after aborting transaction");
if (!bookshelf.getStack(1).isEmpty()) throw new GameTestException("Bookshelf stack 1 should be empty again after aborting transaction");
if (!bookshelf.getStack(0).isEmpty()) throw context.createError("Bookshelf stack 0 should be empty again after aborting transaction");
if (!bookshelf.getStack(1).isEmpty()) throw context.createError("Bookshelf stack 1 should be empty again after aborting transaction");
// Second, check that we correctly update the last modified slot.
try (Transaction tx = Transaction.openOuter()) {
if (storage.getSlot(1).insert(book, 1, tx) != 1) throw new GameTestException("Should have inserted 1 book");
if (bookshelf.getLastInteractedSlot() != 1) throw new GameTestException("Last modified slot should be 1");
if (storage.getSlot(1).insert(book, 1, tx) != 1) throw context.createError("Should have inserted 1 book");
if (bookshelf.getLastInteractedSlot() != 1) throw context.createError("Last modified slot should be 1");
if (storage.getSlot(2).insert(book, 1, tx) != 1) throw new GameTestException("Should have inserted 1 book");
if (bookshelf.getLastInteractedSlot() != 2) throw new GameTestException("Last modified slot should be 2");
if (storage.getSlot(2).insert(book, 1, tx) != 1) throw context.createError("Should have inserted 1 book");
if (bookshelf.getLastInteractedSlot() != 2) throw context.createError("Last modified slot should be 2");
if (storage.getSlot(1).extract(book, 1, tx) != 1) throw new GameTestException("Should have extracted 1 book");
if (bookshelf.getLastInteractedSlot() != 1) throw new GameTestException("Last modified slot should be 1");
if (storage.getSlot(1).extract(book, 1, tx) != 1) throw context.createError("Should have extracted 1 book");
if (bookshelf.getLastInteractedSlot() != 1) throw context.createError("Last modified slot should be 1");
// Now, create an aborted nested transaction.
try (Transaction nested = tx.openNested()) {
if (storage.insert(book, 100, nested) != 5) throw new GameTestException("Should have inserted 5 books");
if (storage.insert(book, 100, nested) != 5) throw context.createError("Should have inserted 5 books");
// Now, last modified slot should be 5.
if (bookshelf.getLastInteractedSlot() != 5) throw new GameTestException("Last modified slot should be 5");
if (bookshelf.getLastInteractedSlot() != 5) throw context.createError("Last modified slot should be 5");
}
// And it's back to 1 in theory.
if (bookshelf.getLastInteractedSlot() != 1) throw new GameTestException("Last modified slot should be 1");
if (bookshelf.getLastInteractedSlot() != 1) throw context.createError("Last modified slot should be 1");
tx.commit();
}
if (bookshelf.getLastInteractedSlot() != 1) throw new GameTestException("Last modified slot should be 1 after committing transaction");
if (bookshelf.getLastInteractedSlot() != 1) throw context.createError("Last modified slot should be 1 after committing transaction");
// Let's also check the state properties. Only slot 2 should be occupied.
BlockState state = bookshelf.getCachedState();
if (state.get(Properties.SLOT_0_OCCUPIED)) throw new GameTestException("Slot 0 should not be occupied");
if (state.get(Properties.SLOT_1_OCCUPIED)) throw new GameTestException("Slot 1 should not be occupied");
if (!state.get(Properties.SLOT_2_OCCUPIED)) throw new GameTestException("Slot 2 should be occupied");
if (state.get(Properties.SLOT_3_OCCUPIED)) throw new GameTestException("Slot 3 should not be occupied");
if (state.get(Properties.SLOT_4_OCCUPIED)) throw new GameTestException("Slot 4 should not be occupied");
if (state.get(Properties.SLOT_5_OCCUPIED)) throw new GameTestException("Slot 5 should not be occupied");
if (state.get(Properties.SLOT_0_OCCUPIED)) throw context.createError("Slot 0 should not be occupied");
if (state.get(Properties.SLOT_1_OCCUPIED)) throw context.createError("Slot 1 should not be occupied");
if (!state.get(Properties.SLOT_2_OCCUPIED)) throw context.createError("Slot 2 should be occupied");
if (state.get(Properties.SLOT_3_OCCUPIED)) throw context.createError("Slot 3 should not be occupied");
if (state.get(Properties.SLOT_4_OCCUPIED)) throw context.createError("Slot 4 should not be occupied");
if (state.get(Properties.SLOT_5_OCCUPIED)) throw context.createError("Slot 5 should not be occupied");
context.complete();
}
*/
/**
* Tests that shulker boxes cannot be inserted into other shulker boxes.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testShulkerNoInsert(TestContext context) {
BlockPos pos = new BlockPos(0, 2, 0);
context.setBlockState(pos, Blocks.SHULKER_BOX);
ShulkerBoxBlockEntity shulker = context.getBlockEntity(pos);
ShulkerBoxBlockEntity shulker = context.getBlockEntity(pos, ShulkerBoxBlockEntity.class);
InventoryStorage storage = InventoryStorage.of(shulker, null);
if (StorageUtil.simulateInsert(storage, ItemVariant.of(Items.SHULKER_BOX), 1, null) > 0) {
context.throwPositionedException("Expected shulker box to be rejected", pos);
context.throwPositionedException(Text.literal("Expected shulker box to be rejected"), pos);
}
context.complete();
}
*/
/**
* {@link Inventory#isValid(int, ItemStack)} is supposed to be independent of the stack size.
* However, to limit some stackable inputs to a size of 1, brewing stands and furnaces don't follow this rule in all cases.
* This test ensures that the Transfer API works around this issue for furnaces.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testBadFurnaceIsValid(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.FURNACE.getDefaultState());
FurnaceBlockEntity furnace = context.getBlockEntity(pos);
FurnaceBlockEntity furnace = context.getBlockEntity(pos, FurnaceBlockEntity.class);
InventoryStorage furnaceWrapper = InventoryStorage.of(furnace, null);
try (Transaction tx = Transaction.openOuter()) {
if (furnaceWrapper.getSlot(1).insert(ItemVariant.of(Items.BUCKET), 2, tx) != 1) {
throw new GameTestException("Exactly 1 bucket should have been inserted");
throw context.createError("Exactly 1 bucket should have been inserted");
}
}
context.complete();
}
*/
/**
* Same as {@link #testBadFurnaceIsValid(TestContext)}, but for brewing stands.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testBadBrewingStandIsValid(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
context.setBlockState(pos, Blocks.BREWING_STAND.getDefaultState());
BrewingStandBlockEntity brewingStand = context.getBlockEntity(pos);
BrewingStandBlockEntity brewingStand = context.getBlockEntity(pos, BrewingStandBlockEntity.class);
InventoryStorage brewingStandWrapper = InventoryStorage.of(brewingStand, null);
try (Transaction tx = Transaction.openOuter()) {
for (int bottleSlot = 0; bottleSlot < 3; ++bottleSlot) {
if (brewingStandWrapper.getSlot(bottleSlot).insert(ItemVariant.of(Items.GLASS_BOTTLE), 2, tx) != 1) {
throw new GameTestException("Exactly 1 glass bottle should have been inserted");
throw context.createError("Exactly 1 glass bottle should have been inserted");
}
}
if (brewingStandWrapper.getSlot(3).insert(ItemVariant.of(Items.REDSTONE), 2, tx) != 2) {
throw new GameTestException("Brewing ingredient insertion should not be limited");
throw context.createError("Brewing ingredient insertion should not be limited");
}
}
context.complete();
}
*/
/**
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/2810">double chest wrapper only updating modified halves</a>.
*/
/*
@GameTest(templateName = "fabric-transfer-api-v1-testmod:double_chest_comparators", skyAccess = true)
@GameTest(structure = "fabric-transfer-api-v1-testmod:double_chest_comparators", skyAccess = true)
public void testDoubleChestComparator(TestContext context) {
BlockPos chestPos = new BlockPos(2, 1, 2);
Storage<ItemVariant> storage = ItemStorage.SIDED.find(context.getWorld(), context.getAbsolutePos(chestPos), Direction.UP);
context.assertTrue(storage != null, "Storage must not be null");
context.assertTrue(storage != null, Text.literal("Storage must not be null"));
// Insert one item
try (Transaction tx = Transaction.openOuter()) {
context.assertTrue(storage.insert(ItemVariant.of(Items.DIAMOND), 1, tx) == 1, "Diamond should have been inserted");
context.assertTrue(storage.insert(ItemVariant.of(Items.DIAMOND), 1, tx) == 1, Text.literal("Diamond should have been inserted"));
tx.commit();
}
// Check that the inventory and slotted storages match
Inventory inventory = HopperBlockEntity.getInventoryAt(context.getWorld(), context.getAbsolutePos(chestPos));
context.assertTrue(inventory != null, "Inventory must not be null");
context.assertTrue(inventory != null, Text.literal("Inventory must not be null"));
if (!(storage instanceof SlottedStorage<ItemVariant> slottedStorage)) {
throw new GameTestException("Double chest storage must be a SlottedStorage");
throw context.createError("Double chest storage must be a SlottedStorage");
}
for (int i = 0; i < inventory.size(); ++i) {
ItemStack stack = inventory.getStack(i);
ItemVariant variant = ItemVariant.of(stack.getItem());
context.assertTrue(variant.matches(stack), "Item variant in slot " + i + " must match stack");
context.assertTrue(variant.matches(stack), Text.literal("Item variant in slot " + i + " must match stack"));
long expectedCount = stack.getCount();
long actualCount = slottedStorage.getSlot(i).getAmount();
context.assertTrue(expectedCount == actualCount, "Slot " + i + " should have " + expectedCount + " items, but has " + actualCount);
context.assertTrue(expectedCount == actualCount, Text.literal("Slot " + i + " should have " + expectedCount + " items, but has " + actualCount));
}
// Check that an update is queued for every single comparator
@ -318,22 +328,19 @@ public class VanillaStorageTests {
comparatorCount.increment();
if (!context.getWorld().getBlockTickScheduler().isQueued(context.getAbsolutePos(relativePos), Blocks.COMPARATOR)) {
throw new GameTestException("Comparator at " + relativePos + " should have an update scheduled");
throw context.createError("Comparator at " + relativePos + " should have an update scheduled");
}
});
context.assertTrue(comparatorCount.intValue() == 6, "Expected exactly 6 comparators");
context.assertTrue(comparatorCount.intValue() == 6, Text.literal("Expected exactly 6 comparators"));
context.complete();
}
*/
/**
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/3017">composters not always incrementing their level on the first insert</a>.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testComposterFirstInsert(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);
@ -345,25 +352,22 @@ public class VanillaStorageTests {
try (Transaction tx = Transaction.openOuter()) {
if (storage.insert(carrot, 1, tx) != 1) {
context.throwPositionedException("Carrot should have been inserted", pos);
context.throwPositionedException(Text.literal("Carrot should have been inserted"), pos);
}
tx.commit();
}
context.checkBlockState(pos, state -> state.get(ComposterBlock.LEVEL) == 1, () -> "Composter should have level 1");
context.checkBlockState(pos, state -> state.get(ComposterBlock.LEVEL) == 1, (s) -> Text.literal("Composter should have level 1"));
}
context.complete();
}
*/
/**
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/3485">jukeboxes having their state changed mid-transaction</a>.
*/
/*
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
@GameTest
public void testJukeboxState(TestContext context) {
BlockPos pos = new BlockPos(2, 2, 2);
context.setBlockState(pos, Blocks.JUKEBOX.getDefaultState());
@ -371,13 +375,11 @@ public class VanillaStorageTests {
try (Transaction tx = Transaction.openOuter()) {
storage.insert(ItemVariant.of(Items.MUSIC_DISC_11), 1, tx);
context.checkBlockState(pos, state -> !state.get(JukeboxBlock.HAS_RECORD), () -> "Jukebox should not have its state changed mid-transaction");
context.checkBlockState(pos, state -> !state.get(JukeboxBlock.HAS_RECORD), (b) -> Text.literal("Jukebox should not have its state changed mid-transaction"));
tx.commit();
}
context.checkBlockState(pos, state -> state.get(JukeboxBlock.HAS_RECORD), () -> "Jukebox should have its state changed");
context.checkBlockState(pos, state -> state.get(JukeboxBlock.HAS_RECORD), (b) -> Text.literal("Jukebox should have its state changed"));
context.complete();
}
*/
}

View file

@ -22,12 +22,13 @@ import net.minecraft.fluid.Fluids;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.test.TestContext;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidConstants;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariant;
import net.fabricmc.fabric.api.transfer.v1.fluid.FluidVariantAttributes;
public class WorldDependentAttributesTest {
//@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE) TODO 1.21.5 tests
@GameTest
public void testViscosity(TestContext context) {
ServerWorld overworld = context.getWorld();
ServerWorld nether = overworld.getServer().getWorld(ServerWorld.NETHER);

View file

@ -3,7 +3,7 @@ org.gradle.parallel=true
version=0.114.4
minecraft_version=25w03a
yarn_version=+build.1
yarn_version=+build.3
loader_version=0.16.10
installer_version=1.0.1

View file

@ -42,7 +42,7 @@ include 'fabric-dimensions-v1'
include 'fabric-entity-events-v1'
include 'fabric-events-interaction-v0'
include 'fabric-game-rule-api-v1'
//include 'fabric-gametest-api-v1'
include 'fabric-gametest-api-v1'
include 'fabric-item-api-v1'
include 'fabric-item-group-api-v1'
include 'fabric-key-binding-api-v1'