diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index bc85103..41d956d 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -47,3 +47,14 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the referenc (SHA-256 `da0a35a968905203187edc5a1f26018305cf146114426ed7b468080b40c95771`). Class-file SHA-256 is `0ce8ddd700df8ec859325133a1a683de1aceaabd67cb4c0d1867e6cb907e5790`, stored as `src/test/resources/compat/ReferenceScalaNormalOcclusion.class.b64`. + +### `ReferenceScalaFacePart` + +`scala/codechicken/multipart/compat/ReferenceScalaFacePart.scala` mixes in `TFacePart` without overriding either +member it supplies, so its compiled forwarders call all three `TFacePart$class` statics: `solid`, +`redstoneConductionMap` and `$init$`. OpenComputers, ProjRed and ProjectBlue depend on those descriptors. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the reference dev jar built at `a1bf3c8` +(SHA-256 `1cb57d8728f06b3d283b0bca8ce9f92a58f4c2b8cc835fa4f7ee3de6338c27ec`). Class-file SHA-256 is +`a95dfa131f57e6e6efdba25e989154899d9b1f9a5cbff7a1e6b4374ec9c2b0b2`, stored as +`src/test/resources/compat/ReferenceScalaFacePart.class.b64`. diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaFacePart.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaFacePart.scala new file mode 100644 index 0000000..f34a625 --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaFacePart.scala @@ -0,0 +1,12 @@ +package codechicken.multipart.compat + +import codechicken.multipart.{TFacePart, TMultiPart} + +/** Mixes in TFacePart without overriding either supplied member, so the compiled forwarders call + * TFacePart$class.solid, TFacePart$class.redstoneConductionMap and TFacePart$class.$init$. + */ +class ReferenceScalaFacePart extends TMultiPart with TFacePart { + def getType = "test:reference-face" + + def getSlotMask = 0x3f +} diff --git a/src/test/java/codechicken/multipart/FacePartCharacterizationTest.java b/src/test/java/codechicken/multipart/FacePartCharacterizationTest.java new file mode 100644 index 0000000..1d06b83 --- /dev/null +++ b/src/test/java/codechicken/multipart/FacePartCharacterizationTest.java @@ -0,0 +1,88 @@ +package codechicken.multipart; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class FacePartCharacterizationTest { + + @Test + void defaultsAreSolidOnEverySideAndConductNothing() { + ForwardingPart part = new ForwardingPart(); + + for (int side = 0; side < 6; side++) { + assertTrue(part.solid(side)); + } + assertTrue(part.solid(-1)); + assertTrue(part.solid(99)); + assertEquals(0, part.redstoneConductionMap()); + } + + @Test + void deprecatedBridgeMatchesTheDefaults() { + ForwardingPart part = new ForwardingPart(); + + for (int side = 0; side < 6; side++) { + assertTrue(TFacePart$class.solid(part, side)); + } + assertEquals(0, TFacePart$class.redstoneConductionMap(part)); + } + + /** The bridge holds the trait defaults rather than dispatching, so an override does not change what it returns. */ + @Test + void deprecatedBridgeReturnsTheTraitDefaultsEvenWhenOverridden() { + OverridingPart part = new OverridingPart(); + + assertFalse(part.solid(0)); + assertEquals(0x10, part.redstoneConductionMap()); + assertTrue(TFacePart$class.solid(part, 0)); + assertEquals(0, TFacePart$class.redstoneConductionMap(part)); + } + + @Test + void facePartIsASlottedPart() { + ForwardingPart part = new ForwardingPart(); + + assertTrue(part instanceof TSlottedPart); + assertEquals(0x3f, part.getSlotMask()); + } + + /** Mirrors the forwarders Scala emits for a class mixing in the trait without overriding either member. */ + private static class ForwardingPart extends TMultiPart implements TFacePart { + + @Override + public String getType() { + return "test:face"; + } + + @Override + public int getSlotMask() { + return 0x3f; + } + + @Override + public boolean solid(int side) { + return TFacePart$class.solid(this, side); + } + + @Override + public int redstoneConductionMap() { + return TFacePart$class.redstoneConductionMap(this); + } + } + + private static final class OverridingPart extends ForwardingPart { + + @Override + public boolean solid(int side) { + return false; + } + + @Override + public int redstoneConductionMap() { + return 0x10; + } + } +} diff --git a/src/test/java/codechicken/multipart/TFacePartBinaryCompatibilityTest.java b/src/test/java/codechicken/multipart/TFacePartBinaryCompatibilityTest.java new file mode 100644 index 0000000..6a99073 --- /dev/null +++ b/src/test/java/codechicken/multipart/TFacePartBinaryCompatibilityTest.java @@ -0,0 +1,55 @@ +package codechicken.multipart; + +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 org.junit.jupiter.api.Test; + +/** + * Loads a Scala class compiled against the reference dev jar, whose forwarders call all three TFacePart$class statics. + * Recompiling the fixture against the port would hide the linkage failure this exists to catch. + */ +class TFacePartBinaryCompatibilityTest { + + @Test + void referenceScalaImplementorStillLinksThroughTheBridge() throws Exception { + Class fixtureClass = new FixtureClassLoader(TFacePart.class.getClassLoader()).define(loadFixture()); + Object fixture = fixtureClass.getDeclaredConstructor().newInstance(); + + assertTrue(fixture instanceof TFacePart); + assertTrue(fixture instanceof TSlottedPart); + assertTrue(fixture instanceof TMultiPart); + TFacePart part = (TFacePart) fixture; + + for (int side = 0; side < 6; side++) { + assertTrue(part.solid(side)); + } + assertEquals(0, part.redstoneConductionMap()); + assertEquals(0x3f, part.getSlotMask()); + } + + private static byte[] loadFixture() { + InputStream input = Objects.requireNonNull( + TFacePartBinaryCompatibilityTest.class.getResourceAsStream("/compat/ReferenceScalaFacePart.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/ReferenceScalaFacePart.class.b64 b/src/test/resources/compat/ReferenceScalaFacePart.class.b64 new file mode 100644 index 0000000..598030c --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaFacePart.class.b64 @@ -0,0 +1,25 @@ +yv66vgAAADQALgEAM2NvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFG +YWNlUGFydAcAAQEAIGNvZGVjaGlja2VuL211bHRpcGFydC9UTXVsdGlQYXJ0BwADAQAfY29kZWNo +aWNrZW4vbXVsdGlwYXJ0L1RGYWNlUGFydAcABQEAHFJlZmVyZW5jZVNjYWxhRmFjZVBhcnQuc2Nh +bGEBAB5Mc2NhbGEvcmVmbGVjdC9TY2FsYVNpZ25hdHVyZTsBAAVieXRlcwEBJQYBKTJBIQECARMJ +MSIrGjRmZRZ0Ny1aKmRDMgxnKVkyZiEGFEhPAwIECQUxMW1cN3FDUlQhIQIEAhM1LEgOXjVxQ0ok +KCJBBAIXDXxHLVoyaVMOXFdNXAIBJw0BIUIECQMXMWkRAUIFAxsRESECVid2WVJMBysZOnUhCVlx +IgMCEQkJSUFLUjFkS0ILJw8eBQYlASEJYUUBB3kldBcOHiAVA1EBIiEGAQ4DCUFRYQYBBQJhCXFh +WjN1KWYEWC1GARohCVFyJEQBHBUJYVIkAQNtQzo8JyIBEAIJKQxnL1kFA0FtEWFhFTtzUzo8ByIC +EgEJAxkTYUM0ZmlOY3cOXidiZy4sEgEKCQNLIWoRQQoGAk8FKTFvWTFtQyYREUYKAgQTOiQIAQAF +c29saWQBAAQoSSlaAQAlY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RGYWNlUGFydCRjbGFzcwcADQEA +JShMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RGYWNlUGFydDtJKVoMAAsADwoADgAQAQAEdGhpcwEA +NUxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhRmFjZVBhcnQ7AQAE +c2lkZQEAAUkBABVyZWRzdG9uZUNvbmR1Y3Rpb25NYXABAAMoKUkBACQoTGNvZGVjaGlja2VuL211 +bHRpcGFydC9URmFjZVBhcnQ7KUkMABYAGAoADgAZAQAHZ2V0VHlwZQEAFCgpTGphdmEvbGFuZy9T +dHJpbmc7AQATdGVzdDpyZWZlcmVuY2UtZmFjZQgAHQEAC2dldFNsb3RNYXNrAQAGPGluaXQ+AQAD +KClWDAAgACEKAAQAIgEABiRpbml0JAEAJChMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RGYWNlUGFy +dDspVgwAJAAlCgAOACYBAARDb2RlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAPTGluZU51bWJlclRh +YmxlAQAKU291cmNlRmlsZQEAGVJ1bnRpbWVWaXNpYmxlQW5ub3RhdGlvbnMBAAhTY2FsYVNpZwAh +AAIABAABAAYAAAAFAAEACwAMAAEAKAAAADoAAgACAAAABiobuAARrAAAAAIAKQAAABYAAgAAAAYA +EgATAAAAAAAGABQAFQABACoAAAAGAAEAAAAIAAEAFgAXAAEAKAAAAC8AAQABAAAABSq4ABqsAAAA +AgApAAAADAABAAAABQASABMAAAAqAAAABgABAAAACAABABsAHAABACgAAAAtAAEAAQAAAAMSHrAA +AAACACkAAAAMAAEAAAADABIAEwAAACoAAAAGAAEAAAAJAAEAHwAXAAEAKAAAAC0AAQABAAAAAxA/ +rAAAAAIAKQAAAAwAAQAAAAMAEgATAAAAKgAAAAYAAQAAAAsAAQAgACEAAQAoAAAAMwABAAEAAAAJ +KrcAIyq4ACexAAAAAgApAAAADAABAAAACQASABMAAAAqAAAABgABAAAACAADACsAAAACAAcALAAA +AAsAAQAIAAEACXMACgAtAAAAAwUAAA==