Skip to content

Commit 6837f83

Browse files
committed
Add comprehrensive test to ensure integrity of all StateType mappings and WrappedBlockStates
1 parent 2272315 commit 6837f83

File tree

6 files changed

+227
-7
lines changed

6 files changed

+227
-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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,21 @@ 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+
public static final BlockTags V_1_20_5 = bind("V_1_20_5");
316+
317+
/**
318+
* Unofficial tag for all blocks added in 1.21.2
319+
*/
320+
public static final BlockTags V_1_21_2 = bind("V_1_21_2");
321+
322+
/**
323+
* Unofficial tag for all blocks added in 1.21.4
324+
*/
325+
public static final BlockTags V_1_21_4 = bind("V_1_21_4");
326+
312327
static {
313328
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);
314329
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 +520,9 @@ public class BlockTags {
505520
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);
506521
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);
507522
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);
523+
BlockTags.V_1_20_5.add(StateTypes.VAULT, StateTypes.HEAVY_CORE);
524+
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);
525+
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);
508526
}
509527

510528
String name;

api/src/test/java/com/github/retrooper/packetevents/test/EnsureValidStateTypesTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package com.github.retrooper.packetevents.test;
2020

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

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)