test: characterize Saw

Freezes the one supplied member before the port: getMaxCuttingStrength
asks the saw about a stack of itself, so the stack handed to
getCuttingStrength wraps the saw and has size 1.

Unlike the constant-returning bridges ported so far, this one dispatches
getCuttingStrength through the interface, so a subclass override is
honoured even when the bridge is called directly. That is asserted
explicitly.

Adds a frozen Scala 2.11.5 consumer compiled against the reference dev jar
at 0de7283. It reports its strength only when handed a stack that actually
wraps itself, so the test proves the bridge built the stack rather than
only that it linked. ProjRed is the only consumer.

First characterization outside codechicken.multipart; these tests live in
codechicken.microblock alongside the type under test.

65 plain-JVM tests, all passing against the untouched Scala.
This commit is contained in:
Algent committed 2026-08-14 21:42:03 +02:00
1 parent 0de728345a
commit 844efac5a2
5 files changed
+188

No files matched your search

@@ -0,0 +1,53 @@
package codechicken.microblock;
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 net.minecraft.item.Item;
import org.junit.jupiter.api.Test;
/**
* Loads a Scala class compiled against the reference dev jar, whose forwarder calls Saw$class.getMaxCuttingStrength and
* whose constructor calls Saw$class.$init$. ProjRed links against both.
*/
class SawBinaryCompatibilityTest {
@Test
void referenceScalaImplementorStillLinksThroughTheBridge() throws Exception {
Class<?> fixtureClass = new FixtureClassLoader(Saw.class.getClassLoader()).define(loadFixture());
Object fixture = fixtureClass.getDeclaredConstructor().newInstance();
assertTrue(fixture instanceof Saw);
assertTrue(fixture instanceof Item);
Saw saw = (Saw) fixture;
// The fixture returns 7 only when handed a stack that actually wraps itself.
assertEquals(7, saw.getMaxCuttingStrength());
}
private static byte[] loadFixture() {
InputStream input = Objects.requireNonNull(
SawBinaryCompatibilityTest.class.getResourceAsStream("/compat/ReferenceScalaSaw.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);
}
}
}
@@ -0,0 +1,82 @@
package codechicken.microblock;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import org.junit.jupiter.api.Test;
class SawCharacterizationTest {
@Test
void maxCuttingStrengthAsksTheSawAboutAStackOfItself() {
SpySaw saw = new SpySaw(5);
assertEquals(5, saw.getMaxCuttingStrength());
assertEquals(1, saw.queried.size());
ItemStack queried = saw.queried.get(0);
assertNotNull(queried);
assertSame(saw, queried.getItem());
assertEquals(1, queried.stackSize);
}
@Test
void deprecatedBridgeBuildsTheSameStack() {
SpySaw saw = new SpySaw(3);
assertEquals(3, Saw$class.getMaxCuttingStrength(saw));
assertEquals(1, saw.queried.size());
assertSame(saw, saw.queried.get(0).getItem());
}
/**
* Unlike the constant-returning bridges, this one dispatches getCuttingStrength through the interface, so a
* subclass override is honoured even when the bridge is called directly.
*/
@Test
void deprecatedBridgeHonoursACuttingStrengthOverride() {
SpySaw saw = new StrongerSaw();
assertEquals(9, Saw$class.getMaxCuttingStrength(saw));
}
private static class SpySaw extends Item implements Saw {
private final List<ItemStack> queried = new ArrayList<>();
private final int strength;
private SpySaw(int strength) {
this.strength = strength;
}
@Override
public int getCuttingStrength(ItemStack item) {
queried.add(item);
return strength;
}
@Override
public int getMaxCuttingStrength() {
return Saw$class.getMaxCuttingStrength(this);
}
}
private static final class StrongerSaw extends SpySaw {
private StrongerSaw() {
super(1);
}
@Override
public int getCuttingStrength(ItemStack item) {
super.getCuttingStrength(item);
return 9;
}
}
}