diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index 41d956d..248f1da 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -58,3 +58,18 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the referenc (SHA-256 `1cb57d8728f06b3d283b0bca8ce9f92a58f4c2b8cc835fa4f7ee3de6338c27ec`). Class-file SHA-256 is `a95dfa131f57e6e6efdba25e989154899d9b1f9a5cbff7a1e6b4374ec9c2b0b2`, stored as `src/test/resources/compat/ReferenceScalaFacePart.class.b64`. + +### `ReferenceScalaIconHitEffects` + +`scala/codechicken/multipart/compat/ReferenceScalaIconHitEffects.scala` mixes in `TIconHitEffects` without overriding +anything it or `JIconHitEffects` supplies, so its compiled forwarders call all five statics across both bridges: +`TIconHitEffects$class.addHitEffects`, `.addDestroyEffects`, `.$init$`, and `JIconHitEffects$class.getBreakingIcon`, +`.$init$`. ForgeRelocationFMP, OpenComputers and ProjRed depend on all of them. + +It records the last side passed to `getBrokenIcon` so the test can prove delegation actually happened rather than only +that the class loaded. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the reference dev jar built at `57130a6` +(SHA-256 `97469dae5152f5040db6106022e8176a1b84814e700c7dfca89f1364e11cf6af`). Class-file SHA-256 is +`80255098990ee61d2c96f70c7020b3bede4f2654786c0c8a3b7618d5060923c7`, stored as +`src/test/resources/compat/ReferenceScalaIconHitEffects.class.b64`. diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaIconHitEffects.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaIconHitEffects.scala new file mode 100644 index 0000000..8c75bff --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaIconHitEffects.scala @@ -0,0 +1,22 @@ +package codechicken.multipart.compat + +import codechicken.lib.vec.Cuboid6 +import codechicken.multipart.{TIconHitEffects, TMultiPart} +import net.minecraft.util.IIcon + +/** Mixes in TIconHitEffects without overriding anything it or JIconHitEffects supplies, so the compiled forwarders + * call TIconHitEffects$class.addHitEffects, TIconHitEffects$class.addDestroyEffects, TIconHitEffects$class.$init$, + * JIconHitEffects$class.getBreakingIcon and JIconHitEffects$class.$init$. + */ +class ReferenceScalaIconHitEffects extends TMultiPart with TIconHitEffects { + var lastSide = -1 + + def getType = "test:reference-icon-hit" + + def getBounds = new Cuboid6(0, 0, 0, 1, 1, 1) + + def getBrokenIcon(side: Int): IIcon = { + lastSide = side + null + } +} diff --git a/src/test/java/codechicken/multipart/IconHitEffectsCharacterizationTest.java b/src/test/java/codechicken/multipart/IconHitEffectsCharacterizationTest.java new file mode 100644 index 0000000..2a411a7 --- /dev/null +++ b/src/test/java/codechicken/multipart/IconHitEffectsCharacterizationTest.java @@ -0,0 +1,100 @@ +package codechicken.multipart; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.util.IIcon; + +import org.junit.jupiter.api.Test; + +import codechicken.lib.vec.Cuboid6; + +/** + * The particle calls themselves need a world and an EffectRenderer, so they cannot run headless. What is asserted here + * is the part of the contract that is reachable: icon selection, the order and range of the icons requested, and + * whether the part's own bounds are consulted. Actual particle appearance stays on the manual checklist. + */ +class IconHitEffectsCharacterizationTest { + + @Test + void breakingIconFallsBackToTheBrokenIconForThatSide() { + SpyPart part = new SpyPart(); + + assertNull(part.getBreakingIcon("ignored-subpart", 3)); + assertEquals(1, part.brokenIconSides.size()); + assertEquals(3, part.brokenIconSides.get(0)); + } + + @Test + void deprecatedBridgeMatchesTheBreakingIconFallback() { + SpyPart part = new SpyPart(); + + assertNull(JIconHitEffects$class.getBreakingIcon(part, null, 5)); + assertEquals(1, part.brokenIconSides.size()); + assertEquals(5, part.brokenIconSides.get(0)); + } + + @Test + void destroyEffectsRequestAllSixSidesInOrderAndScaleToTheBounds() { + SpyPart part = new SpyPart(); + + // The tile is null, so this reaches part.tile after collecting the icons and bounds. + assertThrows(NullPointerException.class, () -> IconHitEffects.addDestroyEffects(part, null)); + + assertEquals(Arrays.asList(0, 1, 2, 3, 4, 5), part.brokenIconSides); + assertEquals(1, part.boundsRequested); + } + + @Test + void destroyEffectsWithoutDensityScalingIgnoreTheBounds() { + SpyPart part = new SpyPart(); + + assertThrows(NullPointerException.class, () -> IconHitEffects.addDestroyEffects(part, null, false)); + + assertEquals(Arrays.asList(0, 1, 2, 3, 4, 5), part.brokenIconSides); + assertEquals(0, part.boundsRequested); + } + + @Test + void iconHitEffectsPartIsAMultiPart() { + SpyPart part = new SpyPart(); + + assertTrue(part instanceof JIconHitEffects); + assertTrue(part instanceof TMultiPart); + } + + /** Mirrors the forwarder Scala emits for a class that does not override getBreakingIcon. */ + private static final class SpyPart extends TMultiPart implements JIconHitEffects { + + private final List brokenIconSides = new ArrayList<>(); + private int boundsRequested; + + @Override + public String getType() { + return "test:icon-hit"; + } + + @Override + public Cuboid6 getBounds() { + boundsRequested++; + return new Cuboid6(0, 0, 0, 1, 1, 1); + } + + @Override + public IIcon getBrokenIcon(int side) { + brokenIconSides.add(side); + return null; + } + + @Override + public IIcon getBreakingIcon(Object subPart, int side) { + return JIconHitEffects$class.getBreakingIcon(this, subPart, side); + } + } +} diff --git a/src/test/java/codechicken/multipart/TIconHitEffectsBinaryCompatibilityTest.java b/src/test/java/codechicken/multipart/TIconHitEffectsBinaryCompatibilityTest.java new file mode 100644 index 0000000..2221535 --- /dev/null +++ b/src/test/java/codechicken/multipart/TIconHitEffectsBinaryCompatibilityTest.java @@ -0,0 +1,68 @@ +package codechicken.multipart; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Objects; +import java.util.Scanner; + +import net.minecraft.client.particle.EffectRenderer; + +import org.junit.jupiter.api.Test; + +/** + * Loads a Scala class compiled against the reference dev jar. Its forwarders call all five statics across both + * JIconHitEffects$class and TIconHitEffects$class. + */ +class TIconHitEffectsBinaryCompatibilityTest { + + @Test + void referenceScalaImplementorStillLinksThroughBothBridges() throws Exception { + Class fixtureClass = new FixtureClassLoader(TIconHitEffects.class.getClassLoader()).define(loadFixture()); + Object fixture = fixtureClass.getDeclaredConstructor().newInstance(); + + assertTrue(fixture instanceof TIconHitEffects); + assertTrue(fixture instanceof JIconHitEffects); + assertTrue(fixture instanceof TMultiPart); + TIconHitEffects part = (TIconHitEffects) fixture; + + // Runs JIconHitEffects$class.getBreakingIcon, which must delegate to the fixture's getBrokenIcon. + assertNull(part.getBreakingIcon(null, 4)); + assertEquals(4, fixtureClass.getMethod("lastSide").invoke(fixture)); + + // Runs TIconHitEffects$class.addDestroyEffects. The fixture's tile is null, so it collects all six icons and + // then fails on the tile; reaching that point proves the static resolved rather than failing to link. + InvocationTargetException thrown = assertThrows( + InvocationTargetException.class, + () -> fixtureClass.getMethod("addDestroyEffects", EffectRenderer.class).invoke(fixture, (Object) null)); + assertInstanceOf(NullPointerException.class, thrown.getCause()); + assertEquals(5, fixtureClass.getMethod("lastSide").invoke(fixture)); + } + + private static byte[] loadFixture() { + InputStream input = Objects.requireNonNull( + TIconHitEffectsBinaryCompatibilityTest.class + .getResourceAsStream("/compat/ReferenceScalaIconHitEffects.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/resources/compat/ReferenceScalaIconHitEffects.class.b64 b/src/test/resources/compat/ReferenceScalaIconHitEffects.class.b64 new file mode 100644 index 0000000..4e6598b --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaIconHitEffects.class.b64 @@ -0,0 +1,55 @@ +yv66vgAAADQAUwEAOWNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFJ +Y29uSGl0RWZmZWN0cwcAAQEAIGNvZGVjaGlja2VuL211bHRpcGFydC9UTXVsdGlQYXJ0BwADAQAl +Y29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RJY29uSGl0RWZmZWN0cwcABQEAIlJlZmVyZW5jZVNjYWxh +SWNvbkhpdEVmZmVjdHMuc2NhbGEBAB5Mc2NhbGEvcmVmbGVjdC9TY2FsYVNpZ25hdHVyZTsBAAVi +eXRlcwECMAYBOTNBIQECARMJYSIrGjRmZRZ0Ny1aKmRDMgwXalk4bxEmJFhJGjRmR1IcKEJBAgUD +GRl3Lhw5YmkqEVFBQgEKW1ZkRy8bOWJlUlQRYUIBDEc+JFdtGTVqRy4sZ24BARQHAVFhAgUCDBk1 +CUEhAwIOCQlRQStUO21pJgQWTR07EQUteRFCAQkFBT0hFmpZOG8RJiRYSRo0ZkdSHAgiAgoBCQMZ +EkECH2pdJiRoCEYBFSEJKQIhRAEDER05AgExQQUCYQkBAlwxdGlOS0ctWgsCM0ERISRIBwI3KQlB +JEEDdEcGZBctAwIfNwkZEUocOwkPAQIBGSFDAUMFYUEuWTp1JyYkV20YE2ZjUhEhJQoJAzUNSiEB +Sg4DCVVzFw4eBQhNfQkJETEBGgMNQUglDQUHUQEBCxVCDQITMQw3D14qakkWBAMiAhYBCQNZE2FC +NGZpUksIL1oLAllBEVFGTQcCXSkRcQZNAQVZBnR3TUMBMgMRURdNXjEKBU1yI0FCKnVlJnR3DUMD +NgERBWEnQQVoS1IUdS4eOGVnVglxBwUCOXs1CRFIAwI7dwUZYS9aMgsFcTIRYQE3akUmEWEoDwII +B1YUdy4bMzcRFQEFASIBQgM1OVcNHiJzXy4sZy5TMnBdUhEhCRQJAwcqaxEBEgYDCxoLQSEeO2pZ +KhFxCVMBClsmdFdtGTpiTVJUESFTAQRdFiQYQkEmRQUVSRVqWThvERVpdQgxARoDERkYDlozAQAI +bGFzdFNpZGUBAAFJAQANYWRkSGl0RWZmZWN0cwEAWihMbmV0L21pbmVjcmFmdC91dGlsL01vdmlu +Z09iamVjdFBvc2l0aW9uO0xuZXQvbWluZWNyYWZ0L2NsaWVudC9wYXJ0aWNsZS9FZmZlY3RSZW5k +ZXJlcjspVgEAIkxjcHcvbW9kcy9mbWwvcmVsYXVuY2hlci9TaWRlT25seTsBAAV2YWx1ZQEAHkxj +cHcvbW9kcy9mbWwvcmVsYXVuY2hlci9TaWRlOwEABkNMSUVOVAEAK2NvZGVjaGlja2VuL211bHRp +cGFydC9USWNvbkhpdEVmZmVjdHMkY2xhc3MHABMBAIEoTGNvZGVjaGlja2VuL211bHRpcGFydC9U +SWNvbkhpdEVmZmVjdHM7TG5ldC9taW5lY3JhZnQvdXRpbC9Nb3ZpbmdPYmplY3RQb3NpdGlvbjtM +bmV0L21pbmVjcmFmdC9jbGllbnQvcGFydGljbGUvRWZmZWN0UmVuZGVyZXI7KVYMAA0AFQoAFAAW +AQAEdGhpcwEAO0xjb2RlY2hpY2tlbi9tdWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhSWNv +bkhpdEVmZmVjdHM7AQADaGl0AQApTG5ldC9taW5lY3JhZnQvdXRpbC9Nb3ZpbmdPYmplY3RQb3Np +dGlvbjsBAA5lZmZlY3RSZW5kZXJlcgEALkxuZXQvbWluZWNyYWZ0L2NsaWVudC9wYXJ0aWNsZS9F +ZmZlY3RSZW5kZXJlcjsBABFhZGREZXN0cm95RWZmZWN0cwEAMShMbmV0L21pbmVjcmFmdC9jbGll +bnQvcGFydGljbGUvRWZmZWN0UmVuZGVyZXI7KVYBAFgoTGNvZGVjaGlja2VuL211bHRpcGFydC9U +SWNvbkhpdEVmZmVjdHM7TG5ldC9taW5lY3JhZnQvY2xpZW50L3BhcnRpY2xlL0VmZmVjdFJlbmRl +cmVyOylWDAAeACAKABQAIQEAD2dldEJyZWFraW5nSWNvbgEALyhMamF2YS9sYW5nL09iamVjdDtJ +KUxuZXQvbWluZWNyYWZ0L3V0aWwvSUljb247AQArY29kZWNoaWNrZW4vbXVsdGlwYXJ0L0pJY29u +SGl0RWZmZWN0cyRjbGFzcwcAJQEAVihMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L0pJY29uSGl0RWZm +ZWN0cztMamF2YS9sYW5nL09iamVjdDtJKUxuZXQvbWluZWNyYWZ0L3V0aWwvSUljb247DAAjACcK +ACYAKAEAB3N1YlBhcnQBABJMamF2YS9sYW5nL09iamVjdDsBAARzaWRlAQADKClJDAALAAwJAAIA +LgEADGxhc3RTaWRlXyRlcQEABChJKVYBAAN4JDEBAAdnZXRUeXBlAQAUKClMamF2YS9sYW5nL1N0 +cmluZzsBABd0ZXN0OnJlZmVyZW5jZS1pY29uLWhpdAgANQEACWdldEJvdW5kcwEAHygpTGNvZGVj +aGlja2VuL2xpYi92ZWMvQ3Vib2lkNjsBABtjb2RlY2hpY2tlbi9saWIvdmVjL0N1Ym9pZDYHADkB +AAY8aW5pdD4BAAkoREREREREKVYMADsAPAoAOgA9AQANZ2V0QnJva2VuSWNvbgEAHShJKUxuZXQv +bWluZWNyYWZ0L3V0aWwvSUljb247DAAwADEKAAIAQQEAAygpVgwAOwBDCgAEAEQBAAYkaW5pdCQB +ACooTGNvZGVjaGlja2VuL211bHRpcGFydC9KSWNvbkhpdEVmZmVjdHM7KVYMAEYARwoAJgBIAQAq +KExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVEljb25IaXRFZmZlY3RzOylWDABGAEoKABQASwEABENv +ZGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAA9MaW5lTnVtYmVyVGFibGUBABlSdW50aW1lVmlzaWJs +ZUFubm90YXRpb25zAQAKU291cmNlRmlsZQEACFNjYWxhU2lnACEAAgAEAAEABgABAAIACwAMAAAA +CQABAA0ADgACAE0AAABFAAMAAwAAAAcqKyy4ABexAAAAAgBOAAAAIAADAAAABwAYABkAAAAAAAcA +GgAbAAEAAAAHABwAHQACAE8AAAAGAAEAAAALAFAAAAANAAEADwABABBlABEAEgABAB4AHwACAE0A +AAA6AAIAAgAAAAYqK7gAIrEAAAACAE4AAAAWAAIAAAAGABgAGQAAAAAABgAcAB0AAQBPAAAABgAB +AAAACwBQAAAADQABAA8AAQAQZQARABIAAQAjACQAAgBNAAAARQADAAMAAAAHKiscuAApsAAAAAIA +TgAAACAAAwAAAAcAGAAZAAAAAAAHACoAKwABAAAABwAsAAwAAgBPAAAABgABAAAACwBQAAAADQAB +AA8AAQAQZQARABIAAQALAC0AAQBNAAAALwABAAEAAAAFKrQAL6wAAAACAE4AAAAMAAEAAAAFABgA +GQAAAE8AAAAGAAEAAAAMAAEAMAAxAAEATQAAADoAAgACAAAABiobtQAvsQAAAAIATgAAABYAAgAA +AAYAGAAZAAAAAAAGADIADAABAE8AAAAGAAEAAAAMAAEAMwA0AAEATQAAAC0AAQABAAAAAxI2sAAA +AAIATgAAAAwAAQAAAAMAGAAZAAAATwAAAAYAAQAAAA4AAQA3ADgAAQBNAAAAOAAOAAEAAAAOuwA6 +WQ4ODg8PD7cAPrAAAAACAE4AAAAMAAEAAAAOABgAGQAAAE8AAAAGAAEAAAAQAAEAPwBAAAEATQAA +AD8AAgACAAAAByobtgBCAbAAAAACAE4AAAAWAAIAAAAHABgAGQAAAAAABwAsAAwAAQBPAAAACgAC +AAAAEwAFABQAAQA7AEMAAQBNAAAAQAACAAEAAAASKrcARSq4AEkquABMKgK1AC+xAAAAAgBOAAAA +DAABAAAAEgAYABkAAABPAAAACgACAAAACwAMAAwAAwBRAAAAAgAHAFAAAAALAAEACAABAAlzAAoA +UgAAAAMFAAA=