diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index f64e95d..bc85103 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -34,3 +34,16 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the referenc `TCuboidPartBinaryCompatibilityTest` exercises `$init$`, `getSubParts` and `getCollisionBoxes`. The `drawBreaking` forwarder is present in the fixture but cannot be invoked without a client render context, so its bridge descriptor is covered by the ABI baseline rather than by execution. + +### `ReferenceScalaNormalOcclusion` + +`scala/codechicken/multipart/compat/ReferenceScalaNormalOcclusion.scala` mixes in `TNormalOcclusion` without overriding +`occlusionTest`, so the compiled class carries the generated +`codechicken$multipart$TNormalOcclusion$$super$occlusionTest` accessor and forwards `occlusionTest` to +`TNormalOcclusion$class`. Invoking it therefore covers the whole bridge round trip: forwarder, `$class`, the singleton +`NormalOcclusionTest$.MODULE$`, and the callback through the accessor. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the reference dev jar built at `cd6420f` +(SHA-256 `da0a35a968905203187edc5a1f26018305cf146114426ed7b468080b40c95771`). Class-file SHA-256 is +`0ce8ddd700df8ec859325133a1a683de1aceaabd67cb4c0d1867e6cb907e5790`, stored as +`src/test/resources/compat/ReferenceScalaNormalOcclusion.class.b64`. diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaNormalOcclusion.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaNormalOcclusion.scala new file mode 100644 index 0000000..b9c6db4 --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaNormalOcclusion.scala @@ -0,0 +1,15 @@ +package codechicken.multipart.compat + +import codechicken.lib.vec.Cuboid6 +import codechicken.multipart.{TMultiPart, TNormalOcclusion} + +/** Mixes in TNormalOcclusion without overriding occlusionTest, so the compiled class carries the generated + * codechicken$multipart$TNormalOcclusion$$super$occlusionTest accessor and forwards occlusionTest to + * TNormalOcclusion$class. + */ +class ReferenceScalaNormalOcclusion extends TMultiPart with TNormalOcclusion { + def getType = "test:reference-normal-occlusion" + + def getOcclusionBoxes: java.lang.Iterable[Cuboid6] = + java.util.Collections.singletonList(new Cuboid6(0, 0, 0, 0.5, 1, 1)) +} diff --git a/src/test/java/codechicken/multipart/NormalOcclusionCharacterizationTest.java b/src/test/java/codechicken/multipart/NormalOcclusionCharacterizationTest.java index 8e8e94a..300535c 100644 --- a/src/test/java/codechicken/multipart/NormalOcclusionCharacterizationTest.java +++ b/src/test/java/codechicken/multipart/NormalOcclusionCharacterizationTest.java @@ -1,10 +1,16 @@ 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.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.Test; @@ -100,6 +106,60 @@ class NormalOcclusionCharacterizationTest { assertTrue(NormalOcclusionTest.apply(origin, barePart)); } + @Test + void singletonAndStaticForwardersAgree() { + Traversable left = scalaBoxes(box(0, 0, 0, 0.5, 1, 1)); + Traversable fits = scalaBoxes(box(0.5, 0, 0, 1, 1, 1)); + Traversable overlaps = scalaBoxes(box(0.25, 0, 0, 1, 1, 1)); + + assertTrue(NormalOcclusionTest$.MODULE$.apply(left, fits)); + assertFalse(NormalOcclusionTest$.MODULE$.apply(left, overlaps)); + + NormallyOccludedPart part = new NormallyOccludedPart(box(0, 0, 0, 0.5, 1, 1)); + NormallyOccludedPart neighbour = new NormallyOccludedPart(box(0.5, 0, 0, 1, 1, 1)); + assertEquals(NormalOcclusionTest.apply(part, neighbour), NormalOcclusionTest$.MODULE$.apply(part, neighbour)); + } + + @Test + void bridgeCombinesTheBoxTestWithTheSuperChain() { + NormallyOccludedPart part = new NormallyOccludedPart(box(0, 0, 0, 0.5, 1, 1)); + TMultiPart fits = new NormallyOccludedPart(box(0.5, 0, 0, 1, 1, 1)); + TMultiPart overlaps = new NormallyOccludedPart(box(0.25, 0, 0, 1, 1, 1)); + + assertTrue(TNormalOcclusion$class.occlusionTest(part, fits)); + assertFalse(TNormalOcclusion$class.occlusionTest(part, overlaps)); + } + + /** + * The super accessor is ACC_SYNTHETIC in the reference, so javac hides it and only reflection can reach it. The + * bridge calls it through invokeinterface, so its name and descriptor are part of the supported ABI. + */ + @Test + void superAccessorExistsAndChainsToTheTMultiPartDefault() throws Exception { + NormallyOccludedPart part = new NormallyOccludedPart(box(0, 0, 0, 0.5, 1, 1)); + Method accessor = TNormalOcclusion.class + .getMethod("codechicken$multipart$TNormalOcclusion$$super$occlusionTest", TMultiPart.class); + + assertEquals(Boolean.TRUE, accessor.invoke(part, new NormallyOccludedPart(box(0.25, 0, 0, 1, 1, 1)))); + assertNull(part.getType()); + } + + @Test + void singleBoxConstructorMatchesTheIterableConstructor() { + Cuboid6 bound = box(0, 0, 0, 0.5, 1, 1); + NormallyOccludedPart single = new NormallyOccludedPart(bound); + TMultiPart overlaps = new NormallyOccludedPart(box(0.25, 0, 0, 1, 1, 1)); + + List boxes = new ArrayList<>(); + for (Cuboid6 box : single.getOcclusionBoxes()) { + boxes.add(box); + } + + assertEquals(1, boxes.size()); + assertSame(bound, boxes.get(0)); + assertFalse(single.occlusionTest(overlaps)); + } + private static boolean compatible(Cuboid6 first, Cuboid6 second) { return NormalOcclusionTest.apply(scalaBoxes(first), scalaBoxes(second)); } diff --git a/src/test/java/codechicken/multipart/TNormalOcclusionBinaryCompatibilityTest.java b/src/test/java/codechicken/multipart/TNormalOcclusionBinaryCompatibilityTest.java new file mode 100644 index 0000000..78b8c14 --- /dev/null +++ b/src/test/java/codechicken/multipart/TNormalOcclusionBinaryCompatibilityTest.java @@ -0,0 +1,55 @@ +package codechicken.multipart; + +import static org.junit.jupiter.api.Assertions.assertFalse; +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; + +import codechicken.lib.vec.Cuboid6; + +/** + * Loads a Scala class compiled against the reference dev jar. Its occlusionTest forwards to TNormalOcclusion$class, + * which in turn calls back through the generated super accessor, so this covers the whole bridge contract. + */ +class TNormalOcclusionBinaryCompatibilityTest { + + @Test + void referenceScalaImplementorStillLinksThroughTheBridge() throws Exception { + Class fixtureClass = new FixtureClassLoader(TNormalOcclusion.class.getClassLoader()).define(loadFixture()); + Object fixture = fixtureClass.getDeclaredConstructor().newInstance(); + + assertTrue(fixture instanceof TNormalOcclusion); + assertTrue(fixture instanceof JNormalOcclusion); + TNormalOcclusion part = (TNormalOcclusion) fixture; + + // The fixture occupies x 0.0 to 0.5, so a touching neighbour fits and an overlapping one does not. + assertTrue(part.occlusionTest(new NormallyOccludedPart(new Cuboid6(0.5, 0, 0, 1, 1, 1)))); + assertFalse(part.occlusionTest(new NormallyOccludedPart(new Cuboid6(0.25, 0, 0, 1, 1, 1)))); + } + + private static byte[] loadFixture() { + InputStream input = Objects.requireNonNull( + TNormalOcclusionBinaryCompatibilityTest.class + .getResourceAsStream("/compat/ReferenceScalaNormalOcclusion.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/ReferenceScalaNormalOcclusion.class.b64 b/src/test/resources/compat/ReferenceScalaNormalOcclusion.class.b64 new file mode 100644 index 0000000..856cc65 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaNormalOcclusion.class.b64 @@ -0,0 +1,34 @@ +yv66vgAAADQAPAEAOmNvZGVjaGlja2VuL211bHRpcGFydC9jb21wYXQvUmVmZXJlbmNlU2NhbGFO +b3JtYWxPY2NsdXNpb24HAAEBACBjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE11bHRpUGFydAcAAwEA +JmNvZGVjaGlja2VuL211bHRpcGFydC9UTm9ybWFsT2NjbHVzaW9uBwAFAQAjUmVmZXJlbmNlU2Nh +bGFOb3JtYWxPY2NsdXNpb24uc2NhbGEBAB5Mc2NhbGEvcmVmbGVjdC9TY2FsYVNpZ25hdHVyZTsB +AAVieXRlcwEBXwYBPTJBIQECARMJaSIrGjRmZRZ0Ny1aKmRDMgxnahw6bkMyfDVtGTd2ZyZ8Z04D +AgQJBTExbVw3cUNSVCEhAgQCEzUsSA5eNXFDSiQoIkEEAhcNfEctWjJpUw5cV01cAgEnDQEhQgQJ +AxcxaREBQgUDGxERIQJWJ3ZZUkwHKxk6dSEJWXEiAwIRCQkBQktUOHNbBmR3alkybWtOTHdOHAUG +JQEhCWFFAQd5JXQXDh4gFQNRASIhBgEOAwlBUWEGAQUCYQlxYVozdSlmBFgtRgEaIQlRciREARwV +CWFSJAEDbUM6PCciARACCSkMZy9ZBQNBbRFhYRU7c1M6PAciAhIBCQMZEyFFNGZpPhs3DVw7dFM+ +dCdpHD1mZ1YJQQVFAhtLHUohQUoOAxElI1hNXTFjWRYEIgFLFw4DJVIhQUsWAgdZLDdNAwItDQUZ +QS4bMgoFOUojYUIidkU+TEdNDgEAO2NvZGVjaGlja2VuJG11bHRpcGFydCRUTm9ybWFsT2NjbHVz +aW9uJCRzdXBlciRvY2NsdXNpb25UZXN0AQAlKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE11bHRp +UGFydDspWgEADW9jY2x1c2lvblRlc3QMAA0ADAoABAAOAQAEdGhpcwEAPExjb2RlY2hpY2tlbi9t +dWx0aXBhcnQvY29tcGF0L1JlZmVyZW5jZVNjYWxhTm9ybWFsT2NjbHVzaW9uOwEABW5wYXJ0AQAi +TGNvZGVjaGlja2VuL211bHRpcGFydC9UTXVsdGlQYXJ0OwEALGNvZGVjaGlja2VuL211bHRpcGFy +dC9UTm9ybWFsT2NjbHVzaW9uJGNsYXNzBwAUAQBNKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE5v +cm1hbE9jY2x1c2lvbjtMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RNdWx0aVBhcnQ7KVoMAA0AFgoA +FQAXAQAHZ2V0VHlwZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAfdGVzdDpyZWZlcmVuY2Utbm9y +bWFsLW9jY2x1c2lvbggAGwEAEWdldE9jY2x1c2lvbkJveGVzAQAWKClMamF2YS9sYW5nL0l0ZXJh +YmxlOwEAG2NvZGVjaGlja2VuL2xpYi92ZWMvQ3Vib2lkNgcAHwY/4AAAAAAAAAEABjxpbml0PgEA +CShEREREREQpVgwAIwAkCgAgACUBABVqYXZhL3V0aWwvQ29sbGVjdGlvbnMHACcBAA1zaW5nbGV0 +b25MaXN0AQAkKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS91dGlsL0xpc3Q7DAApACoKACgAKwEA +AygpVgwAIwAtCgAEAC4BAAYkaW5pdCQBACsoTGNvZGVjaGlja2VuL211bHRpcGFydC9UTm9ybWFs +T2NjbHVzaW9uOylWDAAwADEKABUAMgEABENvZGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAA9MaW5l +TnVtYmVyVGFibGUBAAlTaWduYXR1cmUBADUoKUxqYXZhL2xhbmcvSXRlcmFibGU8TGNvZGVjaGlj +a2VuL2xpYi92ZWMvQ3Vib2lkNjs+OwEAClNvdXJjZUZpbGUBABlSdW50aW1lVmlzaWJsZUFubm90 +YXRpb25zAQAIU2NhbGFTaWcAIQACAAQAAQAGAAAABRABAAsADAABADQAAAA6AAIAAgAAAAYqK7cA +D6wAAAACADUAAAAWAAIAAAAGABAAEQAAAAAABgASABMAAQA2AAAABgABAAAACgABAA0ADAABADQA +AAA6AAIAAgAAAAYqK7gAGKwAAAACADUAAAAWAAIAAAAGABAAEQAAAAAABgASABMAAQA2AAAABgAB +AAAACgABABkAGgABADQAAAAtAAEAAQAAAAMSHLAAAAACADUAAAAMAAEAAAADABAAEQAAADYAAAAG +AAEAAAALAAEAHQAeAAIANAAAAD0ADgABAAAAE7sAIFkODg4UACEPD7cAJrgALLAAAAACADUAAAAM +AAEAAAATABAAEQAAADYAAAAGAAEAAAAOADcAAAACADgAAQAjAC0AAQA0AAAAMwABAAEAAAAJKrcA +Lyq4ADOxAAAAAgA1AAAADAABAAAACQAQABEAAAA2AAAABgABAAAACgADADkAAAACAAcAOgAAAAsA +AQAIAAEACXMACgA7AAAAAwUAAA==