mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
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:
1 parent
1c91fbb4c9
commit
7152b5e394
4 files changed
+302
No files matched your search
+114
@@ -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);
|
||||
}
|
||||
}
|
||||
+121
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user