Share test fixtures across modules

- Move art.arcane.iris.testsupport into core/src/testFixtures and apply java-test-fixtures to core
- Consume core test fixtures from the bukkit plugin, nms, probe and the three loader adapters
- Delete the duplicate ProjectPaths copies in the bukkit-plugin and modded-common test trees
- Point ModdedMessageContributorTest at the shared ProjectPaths
- Guard CommandPackCompatTest, NativeTransitionColumnTest and StubPlatformStateTest with PlatformLeakGuard
- Release the probe runtime state after DimensionStackSuperflatProbeTest opens a workspace
- Keep core main classes ahead of the core jar on the test classpath so the bytecode gates still scan a directory
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-08 05:27:49 -04:00
1 parent fd146de1ad
commit 9dcaed5315
21 files changed
+34 -187

No files matched your search

@@ -1,41 +0,0 @@
package art.arcane.iris.testsupport;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
public final class Await {
private static final long POLL_INTERVAL_NANOS = TimeUnit.MILLISECONDS.toNanos(10L);
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5L);
private Await() {
}
public static void until(String what, BooleanSupplier condition) {
until(what, DEFAULT_TIMEOUT, condition);
}
public static void until(String what, Duration timeout, BooleanSupplier condition) {
if (!reached(what, timeout, condition)) {
throw new AssertionError("Timed out after " + timeout.toMillis() + "ms waiting for " + what);
}
}
public static boolean reached(String what, Duration timeout, BooleanSupplier condition) {
long deadline = System.nanoTime() + timeout.toNanos();
while (true) {
if (condition.getAsBoolean()) {
return true;
}
if (System.nanoTime() - deadline >= 0L) {
return false;
}
LockSupport.parkNanos(POLL_INTERVAL_NANOS);
if (Thread.interrupted()) {
Thread.currentThread().interrupt();
throw new AssertionError("Interrupted while waiting for " + what);
}
}
}
}
@@ -1,125 +0,0 @@
package art.arcane.iris.testsupport;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.block.data.BlockData;
import org.bukkit.plugin.PluginManager;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.invocation.InvocationOnMock;
import java.util.Locale;
import java.util.logging.Logger;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public final class BukkitTestServer {
private static final String DEFAULT_NAME = "IrisTestServer";
private static final String DEFAULT_VERSION = "1.0";
private static final Object LOCK = new Object();
private static volatile Server installed;
private BukkitTestServer() {
}
public static Server install() {
Server current = installed;
if (current != null) {
return current;
}
synchronized (LOCK) {
if (installed != null) {
return installed;
}
Server existing = Bukkit.getServer();
if (existing != null) {
installed = existing;
return existing;
}
Server server = mock(Server.class);
doReturn(Logger.getLogger("IrisTest")).when(server).getLogger();
doReturn(mock(PluginManager.class)).when(server).getPluginManager();
applyBranding(server, DEFAULT_NAME, DEFAULT_VERSION);
doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0, Material.class).name().toLowerCase(Locale.ROOT)))
.when(server).createBlockData(any(Material.class));
doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0, String.class)))
.when(server).createBlockData(anyString());
try {
Bukkit.setServer(server);
} catch (Throwable e) {
if (Bukkit.getServer() != server) {
throw new IllegalStateException("The shared Bukkit test server could not be installed", e);
}
}
installed = server;
return server;
}
}
public static ServerBranding branding() {
return new ServerBranding();
}
private static void applyBranding(Server server, String name, String version) {
doReturn(name).when(server).getName();
doReturn(version).when(server).getVersion();
doReturn(version).when(server).getBukkitVersion();
}
public static final class ServerBranding implements TestRule {
private boolean applied;
private ServerBranding() {
}
public void set(String name, String version) {
applyBranding(install(), name, version);
applied = true;
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} finally {
restore();
}
}
};
}
private void restore() {
if (!applied) {
return;
}
applied = false;
applyBranding(install(), DEFAULT_NAME, DEFAULT_VERSION);
}
}
public static BlockData blockData(String key) {
String canonical = key.indexOf(':') >= 0 ? key : "minecraft:" + key;
BlockData data = mock(BlockData.class);
doReturn(canonical).when(data).getAsString();
doReturn(canonical).when(data).getAsString(anyBoolean());
return data;
}
}
@@ -1,41 +0,0 @@
package art.arcane.iris.testsupport;
import art.arcane.iris.util.common.io.Durability;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public final class DurabilityMode implements TestRule {
private static final String RELAXED = "relaxed";
private final String mode;
private DurabilityMode(String mode) {
this.mode = mode;
}
public static DurabilityMode relaxed() {
return new DurabilityMode(RELAXED);
}
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
String previous = System.getProperty(Durability.MODE_PROPERTY);
System.setProperty(Durability.MODE_PROPERTY, mode);
try {
base.evaluate();
} finally {
if (previous == null) {
System.clearProperty(Durability.MODE_PROPERTY);
} else {
System.setProperty(Durability.MODE_PROPERTY, previous);
}
}
}
};
}
}
@@ -1,52 +0,0 @@
package art.arcane.iris.testsupport;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.IrisServices;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
public final class IrisRuntimeState {
private static final Map<Class<?>, Object> SERVICES = services();
private IrisRuntimeState() {
}
public static boolean platformBound() {
return IrisPlatforms.isBound();
}
public static List<String> registeredServices() {
TreeSet<String> names = new TreeSet<>();
for (Class<?> type : SERVICES.keySet()) {
names.add(type.getName());
}
return Collections.unmodifiableList(new ArrayList<>(names));
}
public static void reset() {
IrisPlatforms.unbind();
IrisServices.clear();
}
private static Map<Class<?>, Object> services() {
try {
Field field = IrisServices.class.getDeclaredField("SERVICES");
field.setAccessible(true);
return castServices(field.get(null));
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("IrisServices no longer exposes its backing map to the test guard", e);
}
}
@SuppressWarnings("unchecked")
private static Map<Class<?>, Object> castServices(Object value) {
return (Map<Class<?>, Object>) value;
}
}
@@ -1,99 +0,0 @@
package art.arcane.iris.testsupport;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.spi.PlatformRegistries;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.function.Supplier;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public final class PlatformBinding implements TestRule {
private final Supplier<IrisPlatform> factory;
private IrisPlatform platform;
private PlatformRegistries registries;
private PlatformBlockState block;
private PlatformBinding(Supplier<IrisPlatform> factory) {
this.factory = factory;
}
public static PlatformBinding mockPlatform() {
return new PlatformBinding(null);
}
public static PlatformBinding of(Supplier<IrisPlatform> factory) {
if (factory == null) {
throw new IllegalArgumentException("A platform factory is required");
}
return new PlatformBinding(factory);
}
public IrisPlatform platform() {
if (platform == null) {
throw new IllegalStateException("The platform binding rule is not active");
}
return platform;
}
public PlatformRegistries registries() {
if (registries == null) {
registries = platform().registries();
}
return registries;
}
public PlatformBlockState block() {
if (block == null) {
block = registries().block("minecraft:stone");
}
return block;
}
@Override
public Statement apply(Statement base, Description description) {
String scope = description.getDisplayName();
return new Statement() {
@Override
public void evaluate() throws Throwable {
PlatformLeakGuard.requireClean(scope, "start");
bind();
try {
base.evaluate();
} finally {
IrisPlatforms.unbind();
platform = null;
registries = null;
block = null;
PlatformLeakGuard.requireClean(scope, "end");
}
}
};
}
private void bind() {
if (factory == null) {
block = mock(PlatformBlockState.class);
registries = mock(PlatformRegistries.class);
when(registries.block(anyString())).thenReturn(block);
platform = mock(IrisPlatform.class);
when(platform.registries()).thenReturn(registries);
} else {
platform = factory.get();
}
IrisPlatforms.bind(platform);
}
}
@@ -1,64 +0,0 @@
package art.arcane.iris.testsupport;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.util.List;
public final class PlatformLeakGuard implements TestRule {
private PlatformLeakGuard() {
}
public static PlatformLeakGuard clean() {
return new PlatformLeakGuard();
}
public static void requireClean(String scope, String phase) {
boolean bound = IrisRuntimeState.platformBound();
List<String> services = IrisRuntimeState.registeredServices();
if (!bound && services.isEmpty()) {
return;
}
IrisRuntimeState.reset();
throw new AssertionError(describe(scope, phase, bound, services));
}
@Override
public Statement apply(Statement base, Description description) {
String scope = description.getDisplayName();
return new Statement() {
@Override
public void evaluate() throws Throwable {
requireClean(scope, "start");
try {
base.evaluate();
} finally {
requireClean(scope, "end");
}
}
};
}
private static String describe(String scope, String phase, boolean bound, List<String> services) {
StringBuilder message = new StringBuilder("Iris runtime state is dirty at ")
.append(phase)
.append(" of ")
.append(scope)
.append(':');
if (bound) {
message.append(" platform still bound;");
}
if (!services.isEmpty()) {
message.append(" services still registered ").append(services).append(';');
}
return message.toString();
}
}
@@ -1,82 +0,0 @@
package art.arcane.iris.testsupport;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.CodeSource;
import java.security.ProtectionDomain;
public final class ProjectPaths {
private static final Path MODULE_ROOT = resolveModuleRoot();
private static final Path REPOSITORY_ROOT = resolveRepositoryRoot(MODULE_ROOT);
private ProjectPaths() {
}
public static Path moduleRoot() {
return MODULE_ROOT;
}
public static Path repositoryRoot() {
return REPOSITORY_ROOT;
}
public static Path moduleFile(String relative) {
return MODULE_ROOT.resolve(relative).normalize();
}
public static Path repositoryFile(String relative) {
return REPOSITORY_ROOT.resolve(relative).normalize();
}
public static File moduleFileAt(String relative) {
return moduleFile(relative).toFile();
}
public static File repositoryFileAt(String relative) {
return repositoryFile(relative).toFile();
}
private static Path resolveModuleRoot() {
ProtectionDomain domain = ProjectPaths.class.getProtectionDomain();
CodeSource source = domain == null ? null : domain.getCodeSource();
URL location = source == null ? null : source.getLocation();
if (location != null) {
try {
Path current = Path.of(location.toURI()).toAbsolutePath().normalize();
while (current != null) {
Path name = current.getFileName();
if (name != null && "build".equals(name.toString()) && current.getParent() != null) {
return current.getParent();
}
current = current.getParent();
}
} catch (URISyntaxException | IllegalArgumentException ignored) {
return Path.of("").toAbsolutePath().normalize();
}
}
return Path.of("").toAbsolutePath().normalize();
}
private static Path resolveRepositoryRoot(Path moduleRoot) {
Path current = moduleRoot;
while (current != null) {
if (Files.isRegularFile(current.resolve("settings.gradle"))
&& Files.isDirectory(current.resolve("core/src/main/java"))) {
return current;
}
current = current.getParent();
}
return moduleRoot;
}
}