Skip to content

Commit 2ae5c15

Browse files
committed
Add comprehensive test to ensure integrity of all StateType mappings and WrappedBlockStates
1 parent 2272315 commit 2ae5c15

File tree

5 files changed

+209
-7
lines changed

5 files changed

+209
-7
lines changed

api/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ dependencies {
4040
testImplementation(testlibs.bundles.junit)
4141
testImplementation(libs.netty)
4242
testImplementation(libs.classgraph)
43+
testImplementation(project(":spigot"))
44+
testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.2")
45+
testImplementation("org.junit.jupiter:junit-jupiter-params:5.11.2")
4346
}
4447

4548
mappingCompression {

api/src/main/java/com/github/retrooper/packetevents/protocol/world/states/defaulttags/BlockTags.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,24 @@ public class BlockTags {
309309
*/
310310
public static final BlockTags DEAD_CORAL_PLANTS = bind("dead_coral_plants");
311311

312+
/**
313+
* Unofficial tag for all blocks added in 1.20.5
314+
*/
315+
@VisibleForTesting @ApiStatus.Internal
316+
public static final BlockTags V_1_20_5 = bind("V_1_20_5");
317+
318+
/**
319+
* Unofficial tag for all blocks added in 1.21.2
320+
*/
321+
@VisibleForTesting @ApiStatus.Internal
322+
public static final BlockTags V_1_21_2 = bind("V_1_21_2");
323+
324+
/**
325+
* Unofficial tag for all blocks added in 1.21.4
326+
*/
327+
@VisibleForTesting @ApiStatus.Internal
328+
public static final BlockTags V_1_21_4 = bind("V_1_21_4");
329+
312330
static {
313331
BlockTags.WOOL.add(StateTypes.WHITE_WOOL, StateTypes.ORANGE_WOOL, StateTypes.MAGENTA_WOOL, StateTypes.LIGHT_BLUE_WOOL, StateTypes.YELLOW_WOOL, StateTypes.LIME_WOOL, StateTypes.PINK_WOOL, StateTypes.GRAY_WOOL, StateTypes.LIGHT_GRAY_WOOL, StateTypes.CYAN_WOOL, StateTypes.PURPLE_WOOL, StateTypes.BLUE_WOOL, StateTypes.BROWN_WOOL, StateTypes.GREEN_WOOL, StateTypes.RED_WOOL, StateTypes.BLACK_WOOL);
314332
BlockTags.PLANKS.add(StateTypes.OAK_PLANKS, StateTypes.SPRUCE_PLANKS, StateTypes.BIRCH_PLANKS, StateTypes.JUNGLE_PLANKS, StateTypes.ACACIA_PLANKS, StateTypes.DARK_OAK_PLANKS, StateTypes.PALE_OAK_PLANKS, StateTypes.CRIMSON_PLANKS, StateTypes.WARPED_PLANKS, StateTypes.MANGROVE_PLANKS, StateTypes.BAMBOO_PLANKS, StateTypes.CHERRY_PLANKS);
@@ -505,6 +523,10 @@ public class BlockTags {
505523
BlockTags.GLASS_PANES.add(StateTypes.GLASS_PANE, StateTypes.WHITE_STAINED_GLASS_PANE, StateTypes.ORANGE_STAINED_GLASS_PANE, StateTypes.MAGENTA_STAINED_GLASS_PANE, StateTypes.LIGHT_BLUE_STAINED_GLASS_PANE, StateTypes.YELLOW_STAINED_GLASS_PANE, StateTypes.LIME_STAINED_GLASS_PANE, StateTypes.PINK_STAINED_GLASS_PANE, StateTypes.GRAY_STAINED_GLASS_PANE, StateTypes.LIGHT_GRAY_STAINED_GLASS_PANE, StateTypes.CYAN_STAINED_GLASS_PANE, StateTypes.PURPLE_STAINED_GLASS_PANE, StateTypes.BLUE_STAINED_GLASS_PANE, StateTypes.BROWN_STAINED_GLASS_PANE, StateTypes.GREEN_STAINED_GLASS_PANE, StateTypes.RED_STAINED_GLASS_PANE, StateTypes.BLACK_STAINED_GLASS_PANE);
506524
BlockTags.ALL_CORAL_PLANTS.add(StateTypes.TUBE_CORAL, StateTypes.BRAIN_CORAL, StateTypes.BUBBLE_CORAL, StateTypes.FIRE_CORAL, StateTypes.HORN_CORAL, StateTypes.DEAD_TUBE_CORAL, StateTypes.DEAD_BRAIN_CORAL, StateTypes.DEAD_BUBBLE_CORAL, StateTypes.DEAD_FIRE_CORAL, StateTypes.DEAD_HORN_CORAL);
507525
BlockTags.DEAD_CORAL_PLANTS.add(StateTypes.DEAD_TUBE_CORAL, StateTypes.DEAD_BRAIN_CORAL, StateTypes.DEAD_BUBBLE_CORAL, StateTypes.DEAD_FIRE_CORAL, StateTypes.DEAD_HORN_CORAL);
526+
BlockTags.V_1_20_5.add(StateTypes.VAULT, StateTypes.HEAVY_CORE);
527+
BlockTags.V_1_21_2.add(StateTypes.PALE_OAK_WOOD, StateTypes.PALE_OAK_PLANKS, StateTypes.PALE_OAK_SAPLING, StateTypes.PALE_OAK_LOG, StateTypes.STRIPPED_PALE_OAK_LOG, StateTypes.STRIPPED_PALE_OAK_WOOD, StateTypes.PALE_OAK_LEAVES, StateTypes.CREAKING_HEART, StateTypes.PALE_OAK_SIGN, StateTypes.PALE_OAK_WALL_SIGN, StateTypes.PALE_OAK_HANGING_SIGN, StateTypes.PALE_OAK_WALL_HANGING_SIGN, StateTypes.PALE_OAK_PRESSURE_PLATE, StateTypes.PALE_OAK_TRAPDOOR, StateTypes.POTTED_PALE_OAK_SAPLING, StateTypes.PALE_OAK_BUTTON, StateTypes.PALE_OAK_STAIRS, StateTypes.PALE_OAK_SLAB, StateTypes.PALE_OAK_FENCE_GATE, StateTypes.PALE_OAK_FENCE, StateTypes.PALE_OAK_DOOR, StateTypes.PALE_MOSS_BLOCK, StateTypes.PALE_MOSS_CARPET, StateTypes.PALE_HANGING_MOSS);
528+
BlockTags.V_1_21_4.add(StateTypes.RESIN_CLUMP, StateTypes.RESIN_BLOCK, StateTypes.RESIN_BRICKS, StateTypes.RESIN_BRICK_STAIRS, StateTypes.RESIN_BRICK_SLAB, StateTypes.RESIN_BRICK_WALL, StateTypes.CHISELED_RESIN_BRICKS, StateTypes.OPEN_EYEBLOSSOM, StateTypes.CLOSED_EYEBLOSSOM, StateTypes.POTTED_OPEN_EYEBLOSSOM, StateTypes.POTTED_CLOSED_EYEBLOSSOM);
529+
// TODO update add block tag for new version
508530
}
509531

510532
String name;
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package com.github.retrooper.packetevents.test;
2+
3+
import com.github.retrooper.packetevents.manager.server.ServerVersion;
4+
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
5+
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
6+
import com.github.retrooper.packetevents.protocol.world.states.defaulttags.BlockTags;
7+
import com.github.retrooper.packetevents.protocol.world.states.type.StateType;
8+
import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes;
9+
import com.github.retrooper.packetevents.test.base.BaseDummyAPITest;
10+
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
11+
import org.junit.jupiter.api.DisplayName;
12+
import org.junit.jupiter.api.Test;
13+
14+
import java.util.ArrayList;
15+
import java.util.Collection;
16+
import java.util.List;
17+
import java.util.function.Function;
18+
19+
import com.github.retrooper.packetevents.PacketEvents;
20+
import org.bukkit.Material;
21+
import org.bukkit.material.MaterialData; //Needed for 1.12 and below support.
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.EnumSource;
24+
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
import static org.junit.jupiter.api.Assertions.fail;
27+
28+
public class StateTypeMappingTest extends BaseDummyAPITest {
29+
30+
@ParameterizedTest
31+
@EnumSource(ClientVersion.class)
32+
@DisplayName("Verify StateType mappings for all client versions")
33+
public void testStateTypeMappings(ClientVersion version) {
34+
testStateTypeMappings(version, false);
35+
}
36+
37+
@Test
38+
@DisplayName("Verify StateType mappings (fail fast)")
39+
public void testStateTypeMappingsFailFast() {
40+
// Use the server version.
41+
ServerVersion serverVersion = PacketEvents.getAPI().getServerManager().getVersion();
42+
ClientVersion version = serverVersion.toClientVersion();
43+
testStateTypeMappings(version, true);
44+
}
45+
46+
public void testStateTypeMappings(ClientVersion version, boolean failFast) {
47+
final ServerVersion serverVersion = PacketEvents.getAPI().getServerManager().getVersion();
48+
Function<Material, WrappedBlockState> blockStateFunction = getBlockStateFunction(serverVersion);
49+
50+
StringBuilder errorMessages = new StringBuilder(); // Accumulate error messages for diagnostic mode
51+
52+
Collection<StateType> stateValues = StateTypes.values();
53+
int found = 0;
54+
int idsMatched = 0;
55+
56+
// Get all BlockTags for versions newer than the server version
57+
List<BlockTags> newerBlockTags = getVersionBlockTagsNewerThan(serverVersion);
58+
int expected = stateValues.size();
59+
60+
// Check if the client version is newer than the server version
61+
ClientVersion serverClientVersion = serverVersion.toClientVersion();
62+
boolean isClientNewer = version.isNewerThan(serverClientVersion);
63+
64+
for (StateType value : stateValues) {
65+
String name = value.getName();
66+
int id = value.getMapped().getId(version);
67+
68+
// Case 1: Block is from a newer version than the server (id == -1)
69+
if (id == -1 && isBlockFromNewerVersion(value, newerBlockTags)) {
70+
// Skip validation for blocks from newer versions and mark as found so there is no count error at the end
71+
expected--;
72+
continue;
73+
}
74+
75+
Material material = Material.matchMaterial(name); // This will return null for materials like potted_open_eyeblossom (added in 1.21.4) on 1.21 server
76+
77+
// Case 2: Client is newer, block exists in client (id != -1), but not in server (material == null)
78+
if (isClientNewer && id != -1 && material == null && isBlockFromNewerVersion(value, newerBlockTags)) {
79+
// This is expected behavior: the client knows the block, but the server does not
80+
expected--;
81+
continue;
82+
}
83+
84+
// Case 3: Material is missing unexpectedly (not from a newer version)
85+
if (material == null) {
86+
String errorMessage = String.format("Material not found for statetype %s, id=%d", name, id);
87+
if (failFast) {
88+
fail(errorMessage);
89+
return; // Just to make sure it exits.
90+
} else {
91+
errorMessages.append(errorMessage).append("\n");
92+
}
93+
continue;
94+
}
95+
found++;
96+
97+
WrappedBlockState state = blockStateFunction.apply(material);
98+
if (state == null) {
99+
String errorMessage = String.format("Failed to create BlockState from material %s, id=%d", material.name(), id);
100+
if (failFast) {
101+
fail(errorMessage);
102+
return;
103+
} else {
104+
errorMessages.append(errorMessage).append("\n");
105+
}
106+
continue;
107+
}
108+
109+
if (state.getType() != value) {
110+
String errorMessage = String.format("State type mismatch for material %s, type=%s, value=%s", material.name(), state.getType(), value);
111+
if (failFast) {
112+
fail(errorMessage);
113+
return;
114+
} else {
115+
errorMessages.append(errorMessage).append("\n");
116+
}
117+
continue;
118+
}
119+
idsMatched++;
120+
}
121+
122+
final int missing = expected - found;
123+
124+
// Diagnostic output (non-fail-fast mode)
125+
if (!failFast && errorMessages.length() > 0) {
126+
System.err.println("StateType Mapping Errors:");
127+
System.err.println(errorMessages);
128+
129+
// Output summary
130+
System.err.println(String.format("%d/%d statetypes found", found, expected));
131+
if (missing > 0) {
132+
double percent = ((double) found / expected) * 100;
133+
System.err.println(String.format("%d missing (%.2f%%)", missing, percent));
134+
}
135+
System.err.println(String.format("%d/%d ids matched", idsMatched, found));
136+
}
137+
138+
// Only fail the test if there are unexpected missing StateTypes
139+
assertEquals(expected, found, String.format("Not all StateTypes found for version %s. Missing: %d. See error log for details.", version.getReleaseName(), missing));
140+
assertEquals(found, idsMatched, String.format("Not all StateType IDs matched for version %s. See error log for details.", version.getReleaseName()));
141+
}
142+
143+
private Function<Material, WrappedBlockState> getBlockStateFunction(ServerVersion serverVersion) {
144+
if (serverVersion.isOlderThanOrEquals(ServerVersion.V_1_12)) {
145+
return material -> SpigotConversionUtil.fromBukkitMaterialData(new MaterialData(material));
146+
} else {
147+
return material -> SpigotConversionUtil.fromBukkitBlockData(material.createBlockData());
148+
}
149+
}
150+
151+
/**
152+
* Gets all BlockTags for versions newer than the server version.
153+
* Relies on BlockTags existing with names V_1_20_5, V_1_21_2, V_1_21_4, etc...
154+
* for versions newer than the Mocked server version (currently 1.21.1 from MockBukkit)
155+
*/
156+
private List<BlockTags> getVersionBlockTagsNewerThan(ServerVersion serverVersion) {
157+
List<BlockTags> blockTags = new ArrayList<>();
158+
for (ServerVersion version : ServerVersion.values()) {
159+
if (version.isNewerThan(serverVersion)) { // Use isNewerThan to exclude the server's own version
160+
BlockTags blockTag = BlockTags.getByName(version.name()); // Use name() to match enum naming convention
161+
if (blockTag != null) { // Only add non-null tags
162+
blockTags.add(blockTag);
163+
}
164+
}
165+
}
166+
return blockTags;
167+
}
168+
169+
/**
170+
* Determines if the block is from a version newer than the server's version.
171+
*/
172+
private boolean isBlockFromNewerVersion(StateType stateType, List<BlockTags> newerBlockTags) {
173+
// Check if the StateType is tagged in any of the newer BlockTags
174+
for (BlockTags tag : newerBlockTags) {
175+
if (tag.contains(stateType)) {
176+
return true;
177+
}
178+
}
179+
return false;
180+
}
181+
}

api/src/test/java/com/github/retrooper/packetevents/test/base/TestPacketEventsBuilder.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.github.retrooper.packetevents.impl.netty.NettyManagerImpl;
1616
import io.github.retrooper.packetevents.impl.netty.manager.protocol.ProtocolManagerAbstract;
1717
import io.github.retrooper.packetevents.impl.netty.manager.server.ServerManagerAbstract;
18+
import io.github.retrooper.packetevents.manager.server.ServerManagerImpl;
1819
import net.kyori.adventure.text.format.NamedTextColor;
1920
import org.bukkit.plugin.Plugin;
2021
import org.jetbrains.annotations.Nullable;
@@ -60,12 +61,7 @@ public ProtocolVersion getPlatformVersion() {
6061
return ProtocolVersion.UNKNOWN;
6162
}
6263
};
63-
private final ServerManager serverManager = new ServerManagerAbstract() {
64-
@Override
65-
public ServerVersion getVersion() {
66-
return ServerVersion.getLatest();
67-
}
68-
};
64+
private final ServerManager serverManager = new ServerManagerImpl();
6965

7066
private final NettyManager nettyManager = new NettyManagerImpl();
7167
private final LogManager logManager = new LogManager() {

testlibs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[versions]
2-
mockbukkit = "3.131.0"
2+
mockbukkit = "3.133.2"
33
slf4j = "2.0.16"
44
junit = "5.11.2"
55

0 commit comments

Comments
 (0)