package net.glowstone.net; import com.flowpowered.network.Codec; import com.flowpowered.network.Message; import com.flowpowered.network.service.CodecLookupService; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.glowstone.net.message.play.inv.HeldItemMessage; import net.glowstone.net.protocol.PlayProtocol; import net.glowstone.net.protocol.GlowProtocol; import net.glowstone.testutils.ServerShim; import org.junit.Test; import java.io.IOException; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Base tests for each {@link GlowProtocol}. */ public abstract class BaseProtocolTest { private final GlowProtocol protocol; private final Message[] testMessages; private final CodecLookupService inboundCodecs; private final CodecLookupService outboundCodecs; protected BaseProtocolTest(GlowProtocol protocol, Message[] testMessages) { this.protocol = protocol; this.testMessages = testMessages; // Retrieve codec lookup service from protocol try { inboundCodecs = getField(protocol, GlowProtocol.class, "inboundCodecs"); outboundCodecs = getField(protocol, GlowProtocol.class, "outboundCodecs"); } catch (ReflectiveOperationException e) { throw new AssertionError(e); } ServerShim.install(); } @Test public void testProtocol() throws Exception { Map<Class<? extends Message>, Codec.CodecRegistration> inboundMap = getField(inboundCodecs, CodecLookupService.class, "messages"); Map<Class<? extends Message>, Codec.CodecRegistration> outboundMap = getField(outboundCodecs, CodecLookupService.class, "messages"); Set<Class<? extends Message>> inboundSet = new HashSet<>(inboundMap.keySet()); Set<Class<? extends Message>> outboundSet = new HashSet<>(outboundMap.keySet()); for (Message message : testMessages) { boolean any = false; Class<? extends Message> clazz = message.getClass(); // test inbound Codec.CodecRegistration registration = inboundCodecs.find(clazz); if (registration != null) { inboundSet.remove(clazz); checkCodec(registration, message); any = true; } // test outbound registration = outboundCodecs.find(clazz); if (registration != null) { outboundSet.remove(clazz); checkCodec(registration, message); any = true; } assertTrue("Codec missing for: " + message, any); } // special case: HeldItemMessage is excluded from tests inboundSet.remove(HeldItemMessage.class); outboundSet.remove(HeldItemMessage.class); assertTrue("Did not test inbound classes: " + inboundSet, inboundSet.isEmpty()); // todo: enable the outbound check for PlayProtocol if (!(protocol instanceof PlayProtocol)) { assertTrue("Did not test outbound classes: " + outboundSet, outboundSet.isEmpty()); } } private void checkCodec(Codec.CodecRegistration reg, Message message) { // check a message with its codec try { Codec<Message> codec = reg.getCodec(); ByteBuf buffer = codec.encode(Unpooled.buffer(), message); Message decoded = codec.decode(buffer); if (buffer.refCnt() > 0) { buffer.release(buffer.refCnt()); } assertEquals("Asymmetry for " + reg.getOpcode() + "/" + message.getClass().getName(), message, decoded); } catch (IOException e) { throw new AssertionError("Error in I/O for " + reg.getOpcode() + "/" + message.getClass().getName(), e); } } @SuppressWarnings("unchecked") private static <T> T getField(Object object, Class<?> clazz, String name) throws ReflectiveOperationException { Field field = clazz.getDeclaredField(name); field.setAccessible(true); return (T) field.get(object); } }