diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index d0b1d6c..b32b766 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -98,3 +98,17 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the referenc (SHA-256 `cec40ee4c94f89fadf29e3f4860c68ca7d55caf12634d950a3eb5ecfb272a01b`). Class-file SHA-256 is `76e4d3875ae9246ed2277f0470b22f596885282386ed179a42e5f571711ae5e0`, stored as `src/test/resources/compat/ReferenceScalaEdgePart.class.b64`. + +### `ReferenceScalaSaw` + +`scala/codechicken/multipart/compat/ReferenceScalaSaw.scala` mixes in `Saw` without overriding +`getMaxCuttingStrength`, so its compiled forwarder calls `Saw$class.getMaxCuttingStrength` and its constructor calls +`Saw$class.$init$`. ProjRed links against both. + +Its `getCuttingStrength` reports 7 only when handed a stack that actually wraps the saw itself, so the test proves the +bridge built the stack rather than only that it linked. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the reference dev jar built at `0de7283` +(SHA-256 `e931927e83134f57197aba082345ea96909291469d01a4f146836418a76bb3d1`). Class-file SHA-256 is +`a03e8eb8566ae830829d59a379c67f355519bc82de24265baabd84720f068ce1`, stored as +`src/test/resources/compat/ReferenceScalaSaw.class.b64`. diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaSaw.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaSaw.scala new file mode 100644 index 0000000..fef23ee --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaSaw.scala @@ -0,0 +1,15 @@ +package codechicken.multipart.compat + +import codechicken.microblock.Saw +import net.minecraft.item.{Item, ItemStack} + +/** Mixes in Saw without overriding getMaxCuttingStrength, so the compiled forwarder calls + * Saw$class.getMaxCuttingStrength and the constructor calls Saw$class.$init$. + * + * getCuttingStrength reports whether the stack it was handed actually wraps this item, so the test can prove the + * bridge built the stack rather than only that it linked. + */ +class ReferenceScalaSaw extends Item with Saw { + def getCuttingStrength(item: ItemStack): Int = + if (item != null && (item.getItem eq this)) 7 else -1 +} diff --git a/src/test/java/codechicken/microblock/SawBinaryCompatibilityTest.java b/src/test/java/codechicken/microblock/SawBinaryCompatibilityTest.java new file mode 100644 index 0000000..e701aa9 --- /dev/null +++ b/src/test/java/codechicken/microblock/SawBinaryCompatibilityTest.java @@ -0,0 +1,53 @@ +package codechicken.microblock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +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.Item; + +import org.junit.jupiter.api.Test; + +/** + * Loads a Scala class compiled against the reference dev jar, whose forwarder calls Saw$class.getMaxCuttingStrength and + * whose constructor calls Saw$class.$init$. ProjRed links against both. + */ +class SawBinaryCompatibilityTest { + + @Test + void referenceScalaImplementorStillLinksThroughTheBridge() throws Exception { + Class fixtureClass = new FixtureClassLoader(Saw.class.getClassLoader()).define(loadFixture()); + Object fixture = fixtureClass.getDeclaredConstructor().newInstance(); + + assertTrue(fixture instanceof Saw); + assertTrue(fixture instanceof Item); + Saw saw = (Saw) fixture; + + // The fixture returns 7 only when handed a stack that actually wraps itself. + assertEquals(7, saw.getMaxCuttingStrength()); + } + + private static byte[] loadFixture() { + InputStream input = Objects.requireNonNull( + SawBinaryCompatibilityTest.class.getResourceAsStream("/compat/ReferenceScalaSaw.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/SawCharacterizationTest.java b/src/test/java/codechicken/microblock/SawCharacterizationTest.java new file mode 100644 index 0000000..7277af5 --- /dev/null +++ b/src/test/java/codechicken/microblock/SawCharacterizationTest.java @@ -0,0 +1,82 @@ +package codechicken.microblock; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import org.junit.jupiter.api.Test; + +class SawCharacterizationTest { + + @Test + void maxCuttingStrengthAsksTheSawAboutAStackOfItself() { + SpySaw saw = new SpySaw(5); + + assertEquals(5, saw.getMaxCuttingStrength()); + assertEquals(1, saw.queried.size()); + ItemStack queried = saw.queried.get(0); + assertNotNull(queried); + assertSame(saw, queried.getItem()); + assertEquals(1, queried.stackSize); + } + + @Test + void deprecatedBridgeBuildsTheSameStack() { + SpySaw saw = new SpySaw(3); + + assertEquals(3, Saw$class.getMaxCuttingStrength(saw)); + assertEquals(1, saw.queried.size()); + assertSame(saw, saw.queried.get(0).getItem()); + } + + /** + * Unlike the constant-returning bridges, this one dispatches getCuttingStrength through the interface, so a + * subclass override is honoured even when the bridge is called directly. + */ + @Test + void deprecatedBridgeHonoursACuttingStrengthOverride() { + SpySaw saw = new StrongerSaw(); + + assertEquals(9, Saw$class.getMaxCuttingStrength(saw)); + } + + private static class SpySaw extends Item implements Saw { + + private final List queried = new ArrayList<>(); + private final int strength; + + private SpySaw(int strength) { + this.strength = strength; + } + + @Override + public int getCuttingStrength(ItemStack item) { + queried.add(item); + return strength; + } + + @Override + public int getMaxCuttingStrength() { + return Saw$class.getMaxCuttingStrength(this); + } + } + + private static final class StrongerSaw extends SpySaw { + + private StrongerSaw() { + super(1); + } + + @Override + public int getCuttingStrength(ItemStack item) { + super.getCuttingStrength(item); + return 9; + } + } +} diff --git a/src/test/resources/compat/ReferenceScalaSaw.class.b64 b/src/test/resources/compat/ReferenceScalaSaw.class.b64 new file mode 100644 index 0000000..bd336e6 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaSaw.class.b64 @@ -0,0 +1,24 @@ +yv66vgAAADQALQEALmNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFT +YXcHAAEBABduZXQvbWluZWNyYWZ0L2l0ZW0vSXRlbQcAAwEAGmNvZGVjaGlja2VuL21pY3JvYmxv +Y2svU2F3BwAFAQAXUmVmZXJlbmNlU2NhbGFTYXcuc2NhbGEBAB5Mc2NhbGEvcmVmbGVjdC9TY2Fs +YVNpZ25hdHVyZTsBAAVieXRlcwEBMwYBMTJBIQECARMJCSIrGjRmZRZ0Ny1aKmRDMgw3K1k8CwUN +IRFBQjJwW0IMR08DAgYNBUlRLh43dVNCDCcPHgYCDwVZMW0cM2ZHIkw3bVszbwcBGTIBAQYVIQlZ +ISNEAQ0VCWlhIgEDamkWbCdCQQgRAyVpF05cM2RlBjRHT0MBEgMNcVcNXgUDJzERQSETO2ZbQhFR +A0cHAi0pEXFDQgELWyYcJ28cMm1fDlwXQkENFwUNGRZtHgUGNwEhCQFIAQd5JXQXDh4gFQN1ASJB +CAEOAwlBUQEJAQUCBQohY1ozdQdWJEgvGzhoJ1IUWE1cNHVRUhEhBQsJA0cZahEBCgYCSwUpMW9Z +MW1DJhFxBQoCBBM6JAgiQgcgAQRJA0NBBisTCVlDQkEFSmkWbDcLXjFkVwIBABVnZXRNYXhDdXR0 +aW5nU3RyZW5ndGgBAAMoKUkBACBjb2RlY2hpY2tlbi9taWNyb2Jsb2NrL1NhdyRjbGFzcwcADQEA +HyhMY29kZWNoaWNrZW4vbWljcm9ibG9jay9TYXc7KUkMAAsADwoADgAQAQAEdGhpcwEAMExjb2Rl +Y2hpY2tlbi9tdWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhU2F3OwEAEmdldEN1dHRpbmdT +dHJlbmd0aAEAIShMbmV0L21pbmVjcmFmdC9pdGVtL0l0ZW1TdGFjazspSQEAHG5ldC9taW5lY3Jh +ZnQvaXRlbS9JdGVtU3RhY2sHABYBAAdnZXRJdGVtAQAbKClMbmV0L21pbmVjcmFmdC9pdGVtL0l0 +ZW07DAAYABkKABcAGgEABGl0ZW0BAB5MbmV0L21pbmVjcmFmdC9pdGVtL0l0ZW1TdGFjazsBAAY8 +aW5pdD4BAAMoKVYMAB4AHwoABAAgAQAGJGluaXQkAQAfKExjb2RlY2hpY2tlbi9taWNyb2Jsb2Nr +L1NhdzspVgwAIgAjCgAOACQBAARDb2RlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAPTGluZU51bWJl +clRhYmxlAQANU3RhY2tNYXBUYWJsZQEAClNvdXJjZUZpbGUBABlSdW50aW1lVmlzaWJsZUFubm90 +YXRpb25zAQAIU2NhbGFTaWcAIQACAAQAAQAGAAAAAwABAAsADAABACYAAAAvAAEAAQAAAAUquAAR +rAAAAAIAJwAAAAwAAQAAAAUAEgATAAAAKAAAAAYAAQAAAAwAAQAUABUAAQAmAAAAUgACAAIAAAAT +K8YAECu2ABsqpgAIEAenAAQCrAAAAAMAJwAAABYAAgAAABMAEgATAAAAAAATABwAHQABACgAAAAG +AAEAAAAOACkAAAAFAAIRQAEAAQAeAB8AAQAmAAAAMwABAAEAAAAJKrcAISq4ACWxAAAAAgAnAAAA +DAABAAAACQASABMAAAAoAAAABgABAAAADAADACoAAAACAAcAKwAAAAsAAQAIAAEACXMACgAsAAAA +AwUAAA==