diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index 9b93807..d4622e4 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -125,6 +125,17 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the referenc `e66be14e6422277cc9567acf8e96a26e419abf5ec6582085c8c6e32cc60aa599`, stored as `src/test/resources/compat/ReferenceScalaMicroMaterialConsumer.class.b64`. +### `ReferenceScalaItemMicroPartConsumer` + +`scala/codechicken/multipart/compat/ReferenceScalaItemMicroPartConsumer.scala` reads `ItemMicroPart$.MODULE$` and +calls the four companion methods ProjRed links against: both two-argument `create` overloads, `getMaterial`, and +`getMaterialID`. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the reference dev jar built at `fdfd776` +(SHA-256 `c3bb8a18ab961a24989d895ade59dc358581c82eec8ecf925256e164eae0ab03`). Class-file SHA-256 is +`967c6c2041c43896f624b60ce74c424575a303eed79c29c8fe60a6344f97dc71`, stored as +`src/test/resources/compat/ReferenceScalaItemMicroPartConsumer.class.b64`. + ### `ReferenceScalaMultipartSaveLoadConsumer` `scala/codechicken/multipart/compat/ReferenceScalaMultipartSaveLoadConsumer.scala` reads diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaItemMicroPartConsumer.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaItemMicroPartConsumer.scala new file mode 100644 index 0000000..10c9923 --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaItemMicroPartConsumer.scala @@ -0,0 +1,20 @@ +package codechicken.multipart.compat + +import codechicken.microblock.ItemMicroPart +import codechicken.microblock.MicroMaterialRegistry.IMicroMaterial +import net.minecraft.item.ItemStack + +/** Compiled against the reference dev jar. Exercises the ItemMicroPart companion methods used by ProjRed. */ +class ReferenceScalaItemMicroPartConsumer { + def createById(damage: Int, material: Int): ItemStack = + ItemMicroPart.create(damage, material) + + def createByName(damage: Int, material: String): ItemStack = + ItemMicroPart.create(damage, material) + + def material(stack: ItemStack): IMicroMaterial = + ItemMicroPart.getMaterial(stack) + + def materialId(stack: ItemStack): Int = + ItemMicroPart.getMaterialID(stack) +} diff --git a/src/test/java/codechicken/microblock/ItemMicroPartBinaryCompatibilityTest.java b/src/test/java/codechicken/microblock/ItemMicroPartBinaryCompatibilityTest.java new file mode 100644 index 0000000..8a37402 --- /dev/null +++ b/src/test/java/codechicken/microblock/ItemMicroPartBinaryCompatibilityTest.java @@ -0,0 +1,80 @@ +package codechicken.microblock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Objects; +import java.util.Scanner; + +import net.minecraft.item.ItemStack; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import codechicken.microblock.handler.MicroblockProxy; + +/** Loads a Scala consumer compiled against the reference ItemMicroPart$.MODULE$ ABI used by ProjRed. */ +class ItemMicroPartBinaryCompatibilityTest { + + private static final String MATERIAL = "test:item_micro_part_binary"; + + @BeforeAll + static void registerMaterial() { + if (MicroMaterialRegistry.getMaterial(MissingMicroMaterial.key()) == null) { + MicroMaterialRegistry.registerMaterial(MissingMicroMaterial$.MODULE$, MissingMicroMaterial.key()); + } + if (MicroMaterialRegistry.getMaterial(MATERIAL) == null) { + MicroMaterialRegistry.registerMaterial(MissingMicroMaterial$.MODULE$, MATERIAL); + } + MicroMaterialRegistry$.MODULE$.setupIDMap(); + } + + @Test + void referenceScalaConsumerStillLinksAgainstAllFourCompanionMethods() throws Exception { + Class fixtureClass = new FixtureClassLoader(ItemMicroPart.class.getClassLoader()).define(loadFixture()); + Object fixture = fixtureClass.getDeclaredConstructor().newInstance(); + ItemMicroPart previous = MicroblockProxy.itemMicro(); + ItemMicroPart item = new ItemMicroPart(); + MicroblockProxy.itemMicro_$eq(item); + try { + int id = MicroMaterialRegistry.materialID(MATERIAL); + ItemStack numbered = (ItemStack) fixtureClass.getMethod("createById", int.class, int.class) + .invoke(fixture, 0x401, id); + ItemStack named = (ItemStack) fixtureClass.getMethod("createByName", int.class, String.class) + .invoke(fixture, 0x202, MATERIAL); + + assertSame(item, numbered.getItem()); + assertEquals(MATERIAL, numbered.getTagCompound().getString("mat")); + assertEquals(MATERIAL, named.getTagCompound().getString("mat")); + assertSame( + MicroMaterialRegistry.getMaterial(MATERIAL), + fixtureClass.getMethod("material", ItemStack.class).invoke(fixture, named)); + assertEquals(id, fixtureClass.getMethod("materialId", ItemStack.class).invoke(fixture, named)); + } finally { + MicroblockProxy.itemMicro_$eq(previous); + } + } + + private static byte[] loadFixture() { + InputStream input = Objects.requireNonNull( + ItemMicroPartBinaryCompatibilityTest.class + .getResourceAsStream("/compat/ReferenceScalaItemMicroPartConsumer.class.b64")); + try (Scanner scanner = new Scanner(input, StandardCharsets.US_ASCII.name()).useDelimiter("\\A")) { + return Base64.getMimeDecoder().decode(scanner.next()); + } + } + + private static final class FixtureClassLoader extends ClassLoader { + + private FixtureClassLoader(ClassLoader parent) { + super(parent); + } + + private Class define(byte[] bytecode) { + return defineClass(null, bytecode, 0, bytecode.length); + } + } +} diff --git a/src/test/java/codechicken/microblock/ItemMicroPartCharacterizationTest.java b/src/test/java/codechicken/microblock/ItemMicroPartCharacterizationTest.java new file mode 100644 index 0000000..6857ffb --- /dev/null +++ b/src/test/java/codechicken/microblock/ItemMicroPartCharacterizationTest.java @@ -0,0 +1,174 @@ +package codechicken.microblock; + +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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +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 net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.client.IItemRenderer; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.Type; + +import codechicken.microblock.handler.MicroblockProxy; + +class ItemMicroPartCharacterizationTest { + + private static final String MATERIAL = "test:item_micro_part"; + private static final int INVALID_CLASS_DAMAGE = 255 << 8 | 1; + + private static final Set ITEM_METHODS = new TreeSet<>( + Arrays.asList( + "getItemStackDisplayName(Lnet/minecraft/item/ItemStack;)Ljava/lang/String;", + "getSubItems(Lnet/minecraft/item/Item;Lnet/minecraft/creativetab/CreativeTabs;Ljava/util/List;)V", + "onItemUse(Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/world/World;IIIIFFF)Z", + "registerIcons(Lnet/minecraft/client/renderer/texture/IIconRegister;)V")); + private static final Set HELPERS = new TreeSet<>( + Arrays.asList( + "checkTagCompound(Lnet/minecraft/item/ItemStack;)V", + "create(II)Lnet/minecraft/item/ItemStack;", + "create(IILjava/lang/String;)Lnet/minecraft/item/ItemStack;", + "create(ILjava/lang/String;)Lnet/minecraft/item/ItemStack;", + "getMaterial(Lnet/minecraft/item/ItemStack;)Lcodechicken/microblock/MicroMaterialRegistry$IMicroMaterial;", + "getMaterialID(Lnet/minecraft/item/ItemStack;)I")); + private static final Set RENDERER = new TreeSet<>( + Arrays.asList( + "handleRenderType(Lnet/minecraft/item/ItemStack;Lnet/minecraftforge/client/IItemRenderer$ItemRenderType;)Z", + "renderHighlight(Lnet/minecraft/entity/player/EntityPlayer;Lnet/minecraft/item/ItemStack;Lnet/minecraft/util/MovingObjectPosition;)Z", + "renderItem(Lnet/minecraftforge/client/IItemRenderer$ItemRenderType;Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)V", + "renderItem(Lnet/minecraftforge/client/IItemRenderer$ItemRenderType;Lnet/minecraft/item/ItemStack;Lscala/collection/Seq;)V", + "shouldUseRenderHelper(Lnet/minecraftforge/client/IItemRenderer$ItemRenderType;Lnet/minecraft/item/ItemStack;Lnet/minecraftforge/client/IItemRenderer$ItemRendererHelper;)Z")); + + @BeforeAll + static void registerMaterial() { + if (MicroMaterialRegistry.getMaterial(MissingMicroMaterial.key()) == null) { + MicroMaterialRegistry.registerMaterial(MissingMicroMaterial$.MODULE$, MissingMicroMaterial.key()); + } + if (MicroMaterialRegistry.getMaterial(MATERIAL) == null) { + MicroMaterialRegistry.registerMaterial(MissingMicroMaterial$.MODULE$, MATERIAL); + } + MicroMaterialRegistry$.MODULE$.setupIDMap(); + } + + @Test + void keepsTheItemCompanionAndRendererSurfaces() throws Exception { + assertEquals(ITEM_METHODS, publicDeclaredMethods(ItemMicroPart.class, false)); + assertEquals(HELPERS, publicDeclaredMethods(ItemMicroPart.class, true)); + assertEquals(HELPERS, publicDeclaredMethods(ItemMicroPart$.class, false)); + assertEquals(RENDERER, publicDeclaredMethods(ItemMicroPartRenderer$.class, false)); + + Set facade = new TreeSet<>(RENDERER); + facade.remove( + "renderItem(Lnet/minecraftforge/client/IItemRenderer$ItemRenderType;Lnet/minecraft/item/ItemStack;[Ljava/lang/Object;)V"); + assertEquals(facade, publicDeclaredMethods(ItemMicroPartRenderer.class, true)); + assertArrayEquals(new Class[] { IItemRenderer.class }, ItemMicroPartRenderer$.class.getInterfaces()); + + Field itemModule = ItemMicroPart$.class.getField("MODULE$"); + assertTrue(Modifier.isStatic(itemModule.getModifiers())); + assertTrue(Modifier.isFinal(itemModule.getModifiers())); + assertSame(ItemMicroPart$.MODULE$, itemModule.get(null)); + + Field rendererModule = ItemMicroPartRenderer$.class.getField("MODULE$"); + assertTrue(Modifier.isStatic(rendererModule.getModifiers())); + assertTrue(Modifier.isFinal(rendererModule.getModifiers())); + assertSame(ItemMicroPartRenderer$.MODULE$, rendererModule.get(null)); + + ItemMicroPartRenderer$.class + .getDeclaredMethod("renderItem", IItemRenderer.ItemRenderType.class, ItemStack.class, Object[].class); + } + + @Test + void constructorSetsTheStableItemIdentity() { + ItemMicroPart item = new ItemMicroPart(); + + assertEquals("item.microblock", item.getUnlocalizedName()); + assertTrue(item.getHasSubtypes()); + } + + @Test + void tagHelpersCreatePreserveAndResolveMaterialTags() { + ItemMicroPart item = new ItemMicroPart(); + ItemStack blank = new ItemStack(item, 1, INVALID_CLASS_DAMAGE); + assertFalse(blank.hasTagCompound()); + + assertNull(ItemMicroPart.getMaterial(blank)); + assertTrue(blank.hasTagCompound()); + NBTTagCompound original = blank.getTagCompound(); + ItemMicroPart.checkTagCompound(blank); + assertSame(original, blank.getTagCompound()); + assertEquals(MicroMaterialRegistry.getMissingId(), ItemMicroPart.getMaterialID(blank)); + + blank.getTagCompound().setString("mat", "test:no_such_material"); + assertSame(MissingMicroMaterial$.MODULE$, ItemMicroPart.getMaterial(blank)); + + blank.getTagCompound().setString("mat", MATERIAL); + assertSame(MicroMaterialRegistry.getMaterial(MATERIAL), ItemMicroPart.getMaterial(blank)); + assertEquals(MicroMaterialRegistry.materialID(MATERIAL), ItemMicroPart.getMaterialID(blank)); + } + + @Test + void creationOverloadsPreserveItemAmountDamageAndMaterialName() { + ItemMicroPart previous = MicroblockProxy.itemMicro(); + ItemMicroPart item = new ItemMicroPart(); + MicroblockProxy.itemMicro_$eq(item); + try { + ItemStack named = ItemMicroPart.create(3, 0x402, MATERIAL); + assertSame(item, named.getItem()); + assertEquals(3, named.stackSize); + assertEquals(0x402, named.getItemDamage()); + assertEquals(MATERIAL, named.getTagCompound().getString("mat")); + + int material = MicroMaterialRegistry.materialID(MATERIAL); + ItemStack numbered = ItemMicroPart.create(0x201, material); + assertEquals(1, numbered.stackSize); + assertEquals(0x201, numbered.getItemDamage()); + assertEquals(MATERIAL, numbered.getTagCompound().getString("mat")); + } finally { + MicroblockProxy.itemMicro_$eq(previous); + } + } + + @Test + void invalidMicroClassShortCircuitsNamesPlacementAndRendering() { + ItemMicroPart item = new ItemMicroPart(); + ItemStack stack = new ItemStack(item, 1, INVALID_CLASS_DAMAGE); + stack.setTagCompound(new NBTTagCompound()); + stack.getTagCompound().setString("mat", MATERIAL); + + assertEquals("Unnamed", item.getItemStackDisplayName(stack)); + assertFalse(item.onItemUse(stack, null, null, 0, 0, 0, 0, 0, 0, 0)); + assertTrue(ItemMicroPartRenderer.handleRenderType(stack, IItemRenderer.ItemRenderType.INVENTORY)); + assertTrue( + ItemMicroPartRenderer.shouldUseRenderHelper( + IItemRenderer.ItemRenderType.INVENTORY, + stack, + IItemRenderer.ItemRendererHelper.INVENTORY_BLOCK)); + assertFalse(ItemMicroPartRenderer.renderHighlight(null, stack, null)); + assertDoesNotThrow( + () -> ((IItemRenderer) ItemMicroPartRenderer$.MODULE$) + .renderItem(IItemRenderer.ItemRenderType.INVENTORY, stack, new Object[0])); + } + + private static Set publicDeclaredMethods(Class type, boolean staticMethods) { + Set signatures = new TreeSet<>(); + for (Method method : type.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers()) == staticMethods) { + signatures.add(method.getName() + Type.getMethodDescriptor(method)); + } + } + return signatures; + } +} diff --git a/src/test/resources/compat/ReferenceScalaItemMicroPartConsumer.class.b64 b/src/test/resources/compat/ReferenceScalaItemMicroPartConsumer.class.b64 new file mode 100644 index 0000000..6525c76 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaItemMicroPartConsumer.class.b64 @@ -0,0 +1,37 @@ +yv66vgAAADQAOQEAQGNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFJ +dGVtTWljcm9QYXJ0Q29uc3VtZXIHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQApUmVmZXJlbmNl +U2NhbGFJdGVtTWljcm9QYXJ0Q29uc3VtZXIuc2NhbGEBAB5Mc2NhbGEvcmVmbGVjdC9TY2FsYVNp +Z25hdHVyZTsBAAVieXRlcwECRgYBNTNBIQECARMJGSMrGjRmZRZ0Ny1aKmRDMgwXCl4zbhsmHCdv +HCliZVIcdU5cOnZbFhQoQkECBQMZGXcuHDliaSoRUUFCAQpbVmRHLxs5YmVSVBFhQgEMRz4kV20Z +NWpHLixnbgEBFAUBUQFDQQYPGwVhISJBBwILTRwXDVwxCgU9YSFBQiFvc0orZw1DAxIBEQUhIwEE +PVM6TEdPEAsCJ0ERQQNBBwIFISlhAwFDAS8FUTFNXTNiaRYUFTATMxUHYRFzBQUCGkE1CSFEAwIc +OQUhEQ5eM24VCWliJEEFblM6LDdNXTFnaSoJcSRBAm9LUkwhIQkOAxMlI1gtXCp1Qw5cByJCEhYB +BCETQQIzYlsGPFcNBQIMSyURYQUEAgQTOiQIIgIVFgEEIRMBQzdiaRYUGC4ZNwkLKQJBEUEWAhkN +FFgtGTtmBWZ0FS1cMxUHYWFTBkMDJFMBB0EFQwMpUwEHYQYFAjBlORExAk0FA2MxCWEBFTpmSRY0 +F0JBGjUFGRlGT101b08qEREHBAUGUQEhCUEOCwNvHQMiAQ8jDwVlCmVCAR5AHQlZZChEAT0VCWkE +IgEEPWU+fEdPUAUCDyURAUlCAQtbJhwnbxwybV8OXBdCASJEA1VpFW4ZOnAbBiRYTV01YllKK3ct +Wzp1ZWZUIQERBAoFFTMlQUQlTlMOFHguVDF1S0pMFw0cBgMFDkNRAVMbQQJhCVFhHTtiRy5EUUET +AQUCLQshIlwxdUtKTBcNXCVlKQkhQwpDA0kTAgcBBAEACmNyZWF0ZUJ5SWQBACIoSUkpTG5ldC9t +aW5lY3JhZnQvaXRlbS9JdGVtU3RhY2s7AQAlY29kZWNoaWNrZW4vbWljcm9ibG9jay9JdGVtTWlj +cm9QYXJ0JAcACwEAB01PRFVMRSQBACdMY29kZWNoaWNrZW4vbWljcm9ibG9jay9JdGVtTWljcm9Q +YXJ0JDsMAA0ADgkADAAPAQAGY3JlYXRlDAARAAoKAAwAEgEABHRoaXMBAEJMY29kZWNoaWNrZW4v +bXVsdGlwYXJ0L2NvbXBhdC9SZWZlcmVuY2VTY2FsYUl0ZW1NaWNyb1BhcnRDb25zdW1lcjsBAAZk +YW1hZ2UBAAFJAQAIbWF0ZXJpYWwBAAxjcmVhdGVCeU5hbWUBADMoSUxqYXZhL2xhbmcvU3RyaW5n +OylMbmV0L21pbmVjcmFmdC9pdGVtL0l0ZW1TdGFjazsMABEAGgoADAAbAQASTGphdmEvbGFuZy9T +dHJpbmc7AQBdKExuZXQvbWluZWNyYWZ0L2l0ZW0vSXRlbVN0YWNrOylMY29kZWNoaWNrZW4vbWlj +cm9ibG9jay9NaWNyb01hdGVyaWFsUmVnaXN0cnkkSU1pY3JvTWF0ZXJpYWw7AQALZ2V0TWF0ZXJp +YWwMAB8AHgoADAAgAQAFc3RhY2sBAB5MbmV0L21pbmVjcmFmdC9pdGVtL0l0ZW1TdGFjazsBAApt +YXRlcmlhbElkAQAhKExuZXQvbWluZWNyYWZ0L2l0ZW0vSXRlbVN0YWNrOylJAQANZ2V0TWF0ZXJp +YWxJRAwAJgAlCgAMACcBAAY8aW5pdD4BAAMoKVYMACkAKgoABAArAQA7Y29kZWNoaWNrZW4vbWlj +cm9ibG9jay9NaWNyb01hdGVyaWFsUmVnaXN0cnkkSU1pY3JvTWF0ZXJpYWwHAC0BACxjb2RlY2hp +Y2tlbi9taWNyb2Jsb2NrL01pY3JvTWF0ZXJpYWxSZWdpc3RyeQcALwEADklNaWNyb01hdGVyaWFs +AQAEQ29kZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAD0xpbmVOdW1iZXJUYWJsZQEAClNvdXJjZUZp +bGUBAAxJbm5lckNsYXNzZXMBABlSdW50aW1lVmlzaWJsZUFubm90YXRpb25zAQAIU2NhbGFTaWcA +IQACAAQAAAAAAAUAAQAJAAoAAQAyAAAARwADAAMAAAAJsgAQGxy2ABOwAAAAAgAzAAAAIAADAAAA +CQAUABUAAAAAAAkAFgAXAAEAAAAJABgAFwACADQAAAAGAAEAAAAKAAEAGQAaAAEAMgAAAEcAAwAD +AAAACbIAEBsstgAcsAAAAAIAMwAAACAAAwAAAAkAFAAVAAAAAAAJABYAFwABAAAACQAYAB0AAgA0 +AAAABgABAAAADQABABgAHgABADIAAAA8AAIAAgAAAAiyABArtgAhsAAAAAIAMwAAABYAAgAAAAgA +FAAVAAAAAAAIACIAIwABADQAAAAGAAEAAAAQAAEAJAAlAAEAMgAAADwAAgACAAAACLIAECu2ACis +AAAAAgAzAAAAFgACAAAACAAUABUAAAAAAAgAIgAjAAEANAAAAAYAAQAAABMAAQApACoAAQAyAAAA +LwABAAEAAAAFKrcALLEAAAACADMAAAAMAAEAAAAFABQAFQAAADQAAAAGAAEAAAAUAAQANQAAAAIA +BQA2AAAACgABAC4AMAAxBgkANwAAAAsAAQAGAAEAB3MACAA4AAAAAwUAAA== \ No newline at end of file