From 2b04a2e52b6e04a2f7340a9445dfe4f72b4ddd2b Mon Sep 17 00:00:00 2001 From: Algent Date: Fri, 28 Aug 2026 11:19:44 +0300 Subject: [PATCH] test: characterize partial occlusion tile trait --- .../test/ForgeEnvironmentSmokeTest.java | 19 ++ ...tialOcclusionTileCharacterizationTest.java | 169 ++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 src/test/java/codechicken/multipart/scalatraits/TPartialOcclusionTileCharacterizationTest.java diff --git a/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java b/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java index 58afe73..8ad24eb 100644 --- a/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java +++ b/src/functionalTest/java/codechicken/multipart/test/ForgeEnvironmentSmokeTest.java @@ -12,8 +12,11 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.lang.reflect.Proxy; +import java.util.Arrays; import java.util.BitSet; import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.server.MinecraftServer; @@ -177,10 +180,26 @@ class ForgeEnvironmentSmokeTest { void generatedJavaTraitOverridesPartialOcclusion() throws Exception { PartialPart existing = new PartialPart(voxel(0)); TileMultipart tile = MultipartHelper.createTileFromParts(Collections.singletonList(existing)); + TileMultipart second = MultipartHelper + .createTileFromParts(Collections.singletonList(new PartialPart(voxel(2)))); Class trait = Class.forName("codechicken.multipart.scalatraits.TPartialOcclusionTile"); assertTrue(trait.isInterface(), "The registered Java mixin class is rewritten to a trait interface"); assertTrue(trait.isInstance(tile)); + assertSame(tile.getClass(), second.getClass(), "The Java trait combination must keep using the class cache"); + assertEquals(0, trait.getDeclaredFields().length); + Set signatures = new TreeSet<>(); + for (java.lang.reflect.Method method : trait.getDeclaredMethods()) { + signatures.add(method.getName() + org.objectweb.asm.Type.getMethodDescriptor(method)); + } + assertEquals( + new TreeSet<>( + Arrays.asList( + "codechicken$multipart$scalatraits$TPartialOcclusionTile$$super$occlusionTest" + + "(Lscala/collection/Seq;Lcodechicken/multipart/TMultiPart;)Z", + "occlusionTest(Lscala/collection/Seq;Lcodechicken/multipart/TMultiPart;)Z", + "partialOcclusionTest(Lscala/collection/Seq;)Z")), + signatures); assertEquals( boolean.class, tile.getClass().getMethod("partialOcclusionTest", scala.collection.Seq.class).getReturnType()); diff --git a/src/test/java/codechicken/multipart/scalatraits/TPartialOcclusionTileCharacterizationTest.java b/src/test/java/codechicken/multipart/scalatraits/TPartialOcclusionTileCharacterizationTest.java new file mode 100644 index 0000000..54426c8 --- /dev/null +++ b/src/test/java/codechicken/multipart/scalatraits/TPartialOcclusionTileCharacterizationTest.java @@ -0,0 +1,169 @@ +package codechicken.multipart.scalatraits; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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 java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + +import org.junit.jupiter.api.Test; + +import codechicken.lib.vec.Cuboid6; +import codechicken.multipart.JPartialOcclusion; +import codechicken.multipart.TMultiPart; +import codechicken.multipart.TileMultipart; +import scala.collection.JavaConversions; +import scala.collection.Seq; + +class TPartialOcclusionTileCharacterizationTest { + + @Test + void keepsTheExactConcreteClassShapeConsumedByTheGenerator() { + Class type = TPartialOcclusionTile.class; + assertEquals(TileMultipart.class, type.getSuperclass()); + assertArrayEquals(new Class[0], type.getInterfaces()); + assertFalse(type.isInterface()); + assertFalse(Modifier.isAbstract(type.getModifiers())); + assertFalse(Modifier.isFinal(type.getModifiers())); + assertEquals(0, type.getDeclaredFields().length); + + Constructor[] constructors = type.getDeclaredConstructors(); + assertEquals(1, constructors.length); + assertEquals(0, constructors[0].getParameterTypes().length); + assertTrue(Modifier.isPublic(constructors[0].getModifiers())); + + assertEquals( + new TreeSet<>( + Arrays.asList( + "occlusionTest(scala.collection.Seq,codechicken.multipart.TMultiPart)boolean", + "partialOcclusionTest(scala.collection.Seq)boolean")), + publicSignatures(type)); + } + + @Test + void partialTestIgnoresNormalPartsAndRequiresExclusiveVoxels() { + TPartialOcclusionTile tile = new TPartialOcclusionTile(); + + assertTrue(tile.partialOcclusionTest(parts(new TestPart(true), new PartialPart(true, voxel(0))))); + assertFalse(tile.partialOcclusionTest(parts(new PartialPart(true)))); + assertTrue(tile.partialOcclusionTest(parts(new PartialPart(true, true)))); + assertTrue(tile.partialOcclusionTest(parts(new PartialPart(true, voxel(0)), new PartialPart(true, voxel(1))))); + assertFalse(tile.partialOcclusionTest(parts(new PartialPart(true, voxel(0)), new PartialPart(true, voxel(0))))); + } + + @Test + void partialCandidateIsCheckedBeforeTheNormalSuperChain() { + TPartialOcclusionTile tile = new TPartialOcclusionTile(); + PartialPart existing = new PartialPart(true, voxel(0)); + PartialPart overlapping = new PartialPart(true, voxel(0)); + + assertFalse(tile.occlusionTest(parts(existing), overlapping)); + assertEquals(0, calls(existing)); + assertEquals(0, calls(overlapping)); + + PartialPart rejecting = new PartialPart(false, voxel(0)); + PartialPart disjoint = new PartialPart(true, voxel(1)); + assertFalse(tile.occlusionTest(parts(rejecting), disjoint)); + assertEquals(1, calls(rejecting)); + assertEquals(0, calls(disjoint)); + } + + @Test + void nonPartialCandidateUsesTheNormalSuperChainDirectly() { + TPartialOcclusionTile tile = new TPartialOcclusionTile(); + TestPart existing = new TestPart(true); + TestPart candidate = new TestPart(false); + + assertFalse(tile.occlusionTest(parts(existing), candidate)); + assertEquals(1, existing.occlusionCalls); + assertEquals(1, candidate.occlusionCalls); + } + + private static Set publicSignatures(Class type) { + Set signatures = new TreeSet<>(); + for (Method method : type.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers())) { + signatures.add(signature(method)); + } + } + return signatures; + } + + private static String signature(Method method) { + StringBuilder out = new StringBuilder(method.getName()).append('('); + Class[] parameters = method.getParameterTypes(); + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + out.append(','); + } + out.append(parameters[i].getName()); + } + return out.append(')').append(method.getReturnType().getName()).toString(); + } + + private static Seq parts(TMultiPart... parts) { + return JavaConversions.asScalaBuffer(Arrays.asList(parts)).toSeq(); + } + + private static int calls(TestPart part) { + return part.occlusionCalls; + } + + private static Cuboid6 voxel(int x) { + return new Cuboid6(x / 8d, 0, 0, (x + 1) / 8d, 1 / 8d, 1 / 8d); + } + + private static class TestPart extends TMultiPart { + + private final boolean occlusionResult; + private int occlusionCalls; + + private TestPart(boolean occlusionResult) { + this.occlusionResult = occlusionResult; + } + + @Override + public String getType() { + return "test:occlusion"; + } + + @Override + public boolean occlusionTest(TMultiPart part) { + occlusionCalls++; + return occlusionResult; + } + } + + private static final class PartialPart extends TestPart implements JPartialOcclusion { + + private final boolean allowCompleteOcclusion; + private final Iterable boxes; + + private PartialPart(boolean occlusionResult, Cuboid6... boxes) { + this(occlusionResult, false, boxes); + } + + private PartialPart(boolean occlusionResult, boolean allowCompleteOcclusion, Cuboid6... boxes) { + super(occlusionResult); + this.allowCompleteOcclusion = allowCompleteOcclusion; + this.boxes = Collections.unmodifiableList(Arrays.asList(boxes)); + } + + @Override + public Iterable getPartialOcclusionBoxes() { + return boxes; + } + + @Override + public boolean allowCompleteOcclusion() { + return allowCompleteOcclusion; + } + } +}