mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
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:
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user