mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
test: characterize the marker interfaces
TSlottedPart, IRandomDisplayTick, INeighborTileChange, TRandomUpdateTick, ISidedHollowConnect and IMicroMaterialRender carry no implementation, so their characterization is their shape: every member abstract and public, no superinterface, and the exact member names. Two of them extend TMultiPart in Scala source but emit a bare interface, and IMicroMaterialRender is satisfied entirely by TMultiPart's Scala-style world/x/y/z accessors, so the names are load-bearing. TRandomUpdateTick's onWorldJoin auto-registration lives in its $class helper. A part that does not declare onWorldJoin resolves it to TMultiPart's no-op; that is pinned so the port cannot quietly turn it into an interface default, which the superclass would shadow.
This commit is contained in:
1 parent
ca9bd5dd89
commit
3e68e67599
2 files changed
+265
No files matched your search
@@ -0,0 +1,124 @@
|
||||
package codechicken.microblock;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import codechicken.lib.vec.Cuboid6;
|
||||
|
||||
/**
|
||||
* Both interfaces carry no implementation, so their characterization is their shape. The accessor names matter most:
|
||||
* {@code IMicroMaterialRender} is satisfied by {@code TMultiPart}'s Scala-style {@code world/x/y/z}, so renaming any of
|
||||
* them to a bean accessor would silently unimplement it for every part.
|
||||
*/
|
||||
class MarkerInterfaceCharacterizationTest {
|
||||
|
||||
@Test
|
||||
void sidedHollowConnectDeclaresOnlyTheSizeQuery() {
|
||||
assertBareInterface(ISidedHollowConnect.class, "getHollowSize(int)int");
|
||||
}
|
||||
|
||||
@Test
|
||||
void microMaterialRenderKeepsScalaStyleAccessorNames() {
|
||||
assertBareInterface(
|
||||
IMicroMaterialRender.class,
|
||||
"world()net.minecraft.world.World",
|
||||
"x()int",
|
||||
"y()int",
|
||||
"z()int",
|
||||
"getRenderBounds()codechicken.lib.vec.Cuboid6");
|
||||
}
|
||||
|
||||
@Test
|
||||
void markersAreImplementableFromJava() {
|
||||
Cuboid6 bounds = new Cuboid6(0, 0, 0, 0.5, 0.5, 0.5);
|
||||
MarkedRender render = new MarkedRender(bounds);
|
||||
|
||||
assertTrue(render instanceof ISidedHollowConnect);
|
||||
assertTrue(render instanceof IMicroMaterialRender);
|
||||
assertEquals(8, render.getHollowSize(2));
|
||||
assertNull(render.world());
|
||||
assertEquals(1, render.x());
|
||||
assertEquals(2, render.y());
|
||||
assertEquals(3, render.z());
|
||||
assertSame(bounds, render.getRenderBounds());
|
||||
}
|
||||
|
||||
private static void assertBareInterface(Class<?> type, String... expectedMembers) {
|
||||
assertTrue(type.isInterface(), type.getName() + " must stay an interface");
|
||||
assertEquals(
|
||||
0,
|
||||
type.getInterfaces().length,
|
||||
type.getName() + " must carry no superinterface, as the Scala trait emits none");
|
||||
|
||||
Set<String> actual = new TreeSet<>();
|
||||
for (Method method : type.getDeclaredMethods()) {
|
||||
assertTrue(Modifier.isAbstract(method.getModifiers()), method + " must stay abstract");
|
||||
assertTrue(Modifier.isPublic(method.getModifiers()), method + " must stay public");
|
||||
actual.add(signature(method));
|
||||
}
|
||||
assertEquals(new TreeSet<>(Arrays.asList(expectedMembers)), actual);
|
||||
}
|
||||
|
||||
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 final class MarkedRender implements ISidedHollowConnect, IMicroMaterialRender {
|
||||
|
||||
private final Cuboid6 bounds;
|
||||
|
||||
MarkedRender(Cuboid6 bounds) {
|
||||
this.bounds = bounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHollowSize(int side) {
|
||||
return 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public World world() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int x() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int y() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int z() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cuboid6 getRenderBounds() {
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
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 java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* The marker interfaces carry no implementation, so their characterization is their shape: every member abstract, no
|
||||
* superinterface, and the exact names downstream parts implement.
|
||||
*/
|
||||
class MarkerInterfaceCharacterizationTest {
|
||||
|
||||
@Test
|
||||
void slottedPartIsABareInterfaceDespiteExtendingTMultiPartInSource() {
|
||||
assertBareInterface(TSlottedPart.class, "getSlotMask()int");
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomDisplayTickDeclaresOnlyTheTickCallback() {
|
||||
assertBareInterface(IRandomDisplayTick.class, "randomDisplayTick(java.util.Random)void");
|
||||
}
|
||||
|
||||
@Test
|
||||
void neighborTileChangeDeclaresBothCallbacks() {
|
||||
assertBareInterface(
|
||||
INeighborTileChange.class,
|
||||
"weakTileChanges()boolean",
|
||||
"onNeighborTileChanged(int,boolean)void");
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomUpdateTickDeclaresOnWorldJoinAbstract() {
|
||||
assertBareInterface(TRandomUpdateTick.class, "randomUpdate()void", "onWorldJoin()void");
|
||||
}
|
||||
|
||||
/**
|
||||
* The auto-registration lives in the {@code $class} helper, which only Scala implementors reach. A part that does
|
||||
* not declare {@code onWorldJoin} resolves it to {@link TMultiPart}, which does nothing -- notably it does not
|
||||
* reach {@link TickScheduler}, which would need a world.
|
||||
*/
|
||||
@Test
|
||||
void randomUpdateTickImplementorWithoutOwnOnWorldJoinInheritsTheNoOp() throws Exception {
|
||||
SilentPart part = new SilentPart();
|
||||
|
||||
part.onWorldJoin();
|
||||
|
||||
assertEquals(TMultiPart.class, SilentPart.class.getMethod("onWorldJoin").getDeclaringClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
void markersAreImplementableAlongsideTMultiPart() {
|
||||
MarkedPart part = new MarkedPart();
|
||||
|
||||
assertTrue(part instanceof TSlottedPart);
|
||||
assertTrue(part instanceof IRandomDisplayTick);
|
||||
assertTrue(part instanceof INeighborTileChange);
|
||||
assertEquals(1 << 6, part.getSlotMask());
|
||||
assertFalse(part.weakTileChanges());
|
||||
|
||||
part.randomDisplayTick(new Random(0));
|
||||
part.onNeighborTileChanged(3, false);
|
||||
assertEquals(3, part.lastTileChangeSide);
|
||||
}
|
||||
|
||||
private static void assertBareInterface(Class<?> type, String... expectedMembers) {
|
||||
assertTrue(type.isInterface(), type.getName() + " must stay an interface");
|
||||
assertEquals(
|
||||
0,
|
||||
type.getInterfaces().length,
|
||||
type.getName() + " must carry no superinterface, as the Scala trait emits none");
|
||||
|
||||
Set<String> actual = new TreeSet<>();
|
||||
for (Method method : type.getDeclaredMethods()) {
|
||||
assertTrue(Modifier.isAbstract(method.getModifiers()), method + " must stay abstract");
|
||||
assertTrue(Modifier.isPublic(method.getModifiers()), method + " must stay public");
|
||||
actual.add(signature(method));
|
||||
}
|
||||
assertEquals(new TreeSet<>(Arrays.asList(expectedMembers)), actual);
|
||||
}
|
||||
|
||||
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 class SilentPart extends TMultiPart implements TRandomUpdateTick {
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "test:silent";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomUpdate() {}
|
||||
}
|
||||
|
||||
private static final class MarkedPart extends TMultiPart
|
||||
implements TSlottedPart, IRandomDisplayTick, INeighborTileChange {
|
||||
|
||||
int lastTileChangeSide = -1;
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "test:marked";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlotMask() {
|
||||
return 1 << 6;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void randomDisplayTick(Random random) {}
|
||||
|
||||
@Override
|
||||
public boolean weakTileChanges() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNeighborTileChanged(int side, boolean weak) {
|
||||
lastTileChangeSide = side;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user