test: characterize microblock packet handlers

Pin the packet-handler ABI, client registry handling, and server handshake payload before the Java port.
This commit is contained in:
Algent committed 2026-08-31 13:38:00 +02:00
1 parent 1c91fbb4c9
commit 7152b5e394
4 files changed
+302

No files matched your search

@@ -0,0 +1,114 @@
package codechicken.microblock.handler;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.concurrent.atomic.AtomicReference;
import net.minecraft.client.Minecraft;
import net.minecraft.network.play.INetHandlerPlayClient;
import net.minecraft.network.play.server.S40PacketDisconnect;
import net.minecraft.util.ChatComponentTranslation;
import org.junit.jupiter.api.Test;
import codechicken.lib.packet.PacketCustom;
import codechicken.microblock.MicroMaterialRegistry;
import codechicken.microblock.MissingMicroMaterial;
import codechicken.microblock.MissingMicroMaterial$;
import io.netty.buffer.Unpooled;
import scala.MatchError;
class MicroblockPacketHandlerBehaviorTest {
@Test
void skipsTheSharedRegistryPacketInSinglePlayer() throws Exception {
PacketCustom packetWithoutPayload = new PacketCustom(Unpooled.buffer().writeByte(1));
assertDoesNotThrow(
() -> MicroblockCPH.handlePacket(packetWithoutPayload, minecraft(true), clientHandler(null)));
}
@Test
void reportsEveryMissingServerMaterialInOrder() throws Exception {
if (MicroMaterialRegistry.getMaterial(MissingMicroMaterial.key()) == null) {
MicroMaterialRegistry.registerMaterial(MissingMicroMaterial$.MODULE$, MissingMicroMaterial.key());
}
AtomicReference<S40PacketDisconnect> disconnect = new AtomicReference<>();
PacketCustom packet = incomingPacket(1, "server:missing-one", "server:missing-two");
try {
MicroblockCPH.handleMaterialRegistration(packet, clientHandler(disconnect));
} finally {
MicroMaterialRegistry.setupIDMap();
}
S40PacketDisconnect disconnectPacket = disconnect.get();
assertNotNull(disconnectPacket);
ChatComponentTranslation reason = (ChatComponentTranslation) disconnectPacket.func_149165_c();
assertEquals("microblock.missing", reason.getKey());
assertArrayEquals(new Object[] { "server:missing-one, server:missing-two" }, reason.getFormatArgs());
}
@Test
void rejectsUnknownClientPacketTypesLikeTheScalaMatch() throws Exception {
PacketCustom packet = new PacketCustom(Unpooled.buffer().writeByte(2));
assertThrows(MatchError.class, () -> MicroblockCPH.handlePacket(packet, null, clientHandler(null)));
}
@Test
void leavesTheServerPacketCallbackAsANoOp() {
assertDoesNotThrow(() -> MicroblockSPH.handlePacket(null, null, null));
assertDoesNotThrow(() -> MicroblockSPH$.MODULE$.handlePacket(null, null, null));
}
private static PacketCustom incomingPacket(int type, String... materialNames) {
PacketCustom packet = new PacketCustom("test", type).writeInt(materialNames.length);
for (String materialName : materialNames) {
packet.writeString(materialName);
}
return new PacketCustom(packet.getByteBuf().copy());
}
private static INetHandlerPlayClient clientHandler(AtomicReference<S40PacketDisconnect> disconnect) {
return (INetHandlerPlayClient) Proxy.newProxyInstance(
INetHandlerPlayClient.class.getClassLoader(),
new Class<?>[] { INetHandlerPlayClient.class },
(proxy, method, arguments) -> {
if ("handleDisconnect".equals(method.getName())) {
if (disconnect != null) {
disconnect.set((S40PacketDisconnect) arguments[0]);
}
return null;
}
throw new AssertionError("Unexpected client callback: " + method.getName());
});
}
private static Minecraft minecraft(boolean singlePlayer) throws Exception {
Minecraft minecraft = (Minecraft) allocateInstance(Minecraft.class);
if (singlePlayer) {
Field running = Minecraft.class.getDeclaredField("integratedServerIsRunning");
running.setAccessible(true);
running.setBoolean(minecraft, true);
Field server = Minecraft.class.getDeclaredField("theIntegratedServer");
server.setAccessible(true);
server.set(minecraft, allocateInstance(server.getType()));
}
return minecraft;
}
private static Object allocateInstance(Class<?> type) throws Exception {
Class<?> unsafe = Class.forName("sun.misc.Unsafe");
Field field = unsafe.getDeclaredField("theUnsafe");
field.setAccessible(true);
return unsafe.getMethod("allocateInstance", Class.class).invoke(field.get(null), type);
}
}
@@ -0,0 +1,121 @@
package codechicken.microblock.handler;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.Type;
import codechicken.lib.packet.PacketCustom.IClientPacketHandler;
import codechicken.lib.packet.PacketCustom.IHandshakeHandler;
import codechicken.lib.packet.PacketCustom.IServerPacketHandler;
class MicroblockPacketHandlerCharacterizationTest {
private static final Set<String> CLIENT_METHODS = new TreeSet<>(
Arrays.asList(
"handleMaterialRegistration(Lcodechicken/lib/packet/PacketCustom;Lnet/minecraft/network/play/INetHandlerPlayClient;)V",
"handlePacket(Lcodechicken/lib/packet/PacketCustom;Lnet/minecraft/client/Minecraft;Lnet/minecraft/network/play/INetHandlerPlayClient;)V"));
private static final Set<String> SERVER_METHODS = new TreeSet<>(
Arrays.asList(
"handlePacket(Lcodechicken/lib/packet/PacketCustom;Lnet/minecraft/entity/player/EntityPlayerMP;Lnet/minecraft/network/play/INetHandlerPlayServer;)V",
"handshakeRecieved(Lnet/minecraft/network/NetHandlerPlayServer;)V"));
@Test
void keepsTheSharedPacketHandlerBase() throws Exception {
assertTrue(Modifier.isPublic(MicroblockPH.class.getModifiers()));
assertFalse(Modifier.isFinal(MicroblockPH.class.getModifiers()));
assertArrayEquals(new Class<?>[0], MicroblockPH.class.getInterfaces());
assertEquals(
new TreeSet<>(Arrays.asList("registryChannel()Ljava/lang/String;")),
publicMethodSignatures(MicroblockPH.class));
Field channel = MicroblockPH.class.getDeclaredField("registryChannel");
assertSame(String.class, channel.getType());
assertTrue(Modifier.isPrivate(channel.getModifiers()));
assertTrue(Modifier.isFinal(channel.getModifiers()));
assertEquals("ForgeMicroblock", new MicroblockPH().registryChannel());
}
@Test
void keepsTheClientFacadeAndCompanion() throws Exception {
assertFacade(MicroblockCPH.class, withRegistryChannel(CLIENT_METHODS));
assertCompanion(MicroblockCPH$.class, IClientPacketHandler.class, CLIENT_METHODS);
assertSame(MicroblockCPH$.class, MicroblockCPH$.class.getField("MODULE$").getType());
assertSame(MicroblockCPH$.MODULE$, MicroblockCPH$.class.getField("MODULE$").get(null));
assertEquals("ForgeMicroblock", MicroblockCPH.registryChannel());
}
@Test
void keepsTheServerFacadeAndCompanion() throws Exception {
assertFacade(MicroblockSPH.class, withRegistryChannel(SERVER_METHODS));
assertCompanion(
MicroblockSPH$.class,
new Class<?>[] { IServerPacketHandler.class, IHandshakeHandler.class },
SERVER_METHODS);
assertSame(MicroblockSPH$.class, MicroblockSPH$.class.getField("MODULE$").getType());
assertSame(MicroblockSPH$.MODULE$, MicroblockSPH$.class.getField("MODULE$").get(null));
assertEquals("ForgeMicroblock", MicroblockSPH.registryChannel());
}
private static void assertFacade(Class<?> type, Set<String> methods) {
assertTrue(Modifier.isPublic(type.getModifiers()));
assertTrue(Modifier.isFinal(type.getModifiers()));
assertArrayEquals(new Class<?>[0], type.getInterfaces());
assertEquals(methods, publicMethodSignatures(type));
for (Method method : type.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
assertTrue(Modifier.isStatic(method.getModifiers()), method.toString());
}
}
}
private static void assertCompanion(Class<?> type, Class<?> packetInterface, Set<String> methods) throws Exception {
assertCompanion(type, new Class<?>[] { packetInterface }, methods);
}
private static void assertCompanion(Class<?> type, Class<?>[] packetInterfaces, Set<String> methods)
throws Exception {
assertTrue(Modifier.isPublic(type.getModifiers()));
assertTrue(Modifier.isFinal(type.getModifiers()));
assertSame(MicroblockPH.class, type.getSuperclass());
assertArrayEquals(packetInterfaces, type.getInterfaces());
assertEquals(methods, publicMethodSignatures(type));
Field module = type.getField("MODULE$");
assertTrue(Modifier.isPublic(module.getModifiers()));
assertTrue(Modifier.isStatic(module.getModifiers()));
assertTrue(Modifier.isFinal(module.getModifiers()));
for (Method method : type.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
assertFalse(Modifier.isStatic(method.getModifiers()), method.toString());
}
}
}
private static Set<String> withRegistryChannel(Set<String> methods) {
Set<String> result = new TreeSet<>(methods);
result.add("registryChannel()Ljava/lang/String;");
return result;
}
private static Set<String> publicMethodSignatures(Class<?> type) {
Set<String> signatures = new TreeSet<>();
for (Method method : type.getDeclaredMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
signatures.add(method.getName() + Type.getMethodDescriptor(method));
}
}
return signatures;
}
}