From 75900d060298b9ef679aa28ce9c3ecf846d6fbd9 Mon Sep 17 00:00:00 2001 From: Algent Date: Fri, 14 Aug 2026 20:24:28 +0200 Subject: [PATCH] test: characterize TNormalOcclusion Extends the occlusion characterization to the parts of the area the port will touch: the NormalOcclusionTest$ singleton and its MODULE$ field, the TNormalOcclusion$class bridge, the super accessor the bridge calls back through, NormallyOccludedPart's single-box constructor that AE2 links against, and its null getType. The generated codechicken$multipart$TNormalOcclusion$$super$occlusionTest accessor is ACC_SYNTHETIC on the reference interface, so javac neither sees nor allows implementing it. It is reached by reflection here, and the port has to expose it as an ordinary method for any new implementor to be able to satisfy it. Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar at cd6420f. It mixes in the trait without overriding occlusionTest, so invoking it covers the whole bridge round trip: forwarder, $class, singleton, and the callback through the accessor. ForgeRelocationFMP, OpenComputers and ProjRed all depend on that path. 38 plain-JVM tests, all passing against the untouched Scala. --- src/test/fixtures/README.md | 13 ++++ .../ReferenceScalaNormalOcclusion.scala | 15 +++++ .../NormalOcclusionCharacterizationTest.java | 60 +++++++++++++++++++ ...ormalOcclusionBinaryCompatibilityTest.java | 55 +++++++++++++++++ .../ReferenceScalaNormalOcclusion.class.b64 | 34 +++++++++++ 5 files changed, 177 insertions(+) create mode 100644 src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaNormalOcclusion.scala create mode 100644 src/test/java/codechicken/multipart/TNormalOcclusionBinaryCompatibilityTest.java create mode 100644 src/test/resources/compat/ReferenceScalaNormalOcclusion.class.b64 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==