mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-23 07:10:51 +00:00
WIP Building for latest.
Still Quantizing, WOrking on that next also removed prebake
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package art.arcane.iris;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisDiagnosticsTest {
|
||||
@Test
|
||||
public void reportErrorWithContextPrintsFullStacktrace() {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
PrintStream originalErr = System.err;
|
||||
System.setErr(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||
try {
|
||||
Iris.reportError("Runtime world creation failed.", new IllegalStateException("outer", new IllegalArgumentException("inner")));
|
||||
} finally {
|
||||
System.setErr(originalErr);
|
||||
}
|
||||
|
||||
String text = output.toString(StandardCharsets.UTF_8);
|
||||
assertTrue(text.contains("Runtime world creation failed."));
|
||||
assertTrue(text.contains("IllegalStateException"));
|
||||
assertTrue(text.contains("IllegalArgumentException"));
|
||||
assertTrue(text.contains("inner"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectSplashPacksSkipsInternalAndInvalidFolders() throws Exception {
|
||||
Path root = Files.createTempDirectory("iris-splash");
|
||||
try {
|
||||
Path validPack = root.resolve("overworld");
|
||||
Files.createDirectories(validPack.resolve("dimensions"));
|
||||
Files.writeString(validPack.resolve("dimensions").resolve("overworld.json"), "{\"version\":\"4000\"}");
|
||||
|
||||
Files.createDirectories(root.resolve("datapack-imports"));
|
||||
|
||||
Path brokenPack = root.resolve("broken");
|
||||
Files.createDirectories(brokenPack.resolve("dimensions"));
|
||||
Files.writeString(brokenPack.resolve("dimensions").resolve("broken.json"), "{");
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
PrintStream originalErr = System.err;
|
||||
System.setErr(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||
List<Iris.SplashPackMetadata> packs;
|
||||
try {
|
||||
packs = Iris.collectSplashPacks(root.toFile());
|
||||
} finally {
|
||||
System.setErr(originalErr);
|
||||
}
|
||||
|
||||
assertEquals(1, packs.size());
|
||||
assertEquals("overworld", packs.get(0).name());
|
||||
assertEquals("4000", packs.get(0).version());
|
||||
|
||||
String text = output.toString(StandardCharsets.UTF_8);
|
||||
assertTrue(text.contains("Failed to read splash metadata for dimension pack \"broken\"."));
|
||||
assertTrue(text.contains("Json"));
|
||||
} finally {
|
||||
Files.walk(root)
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.map(Path::toFile)
|
||||
.forEach(File::delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,16 @@ public class IrisRuntimeSchedulerModeRoutingTest {
|
||||
assertEquals(IrisRuntimeSchedulerMode.FOLIA, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoResolvesToPaperLikeOnCanvasBranding() {
|
||||
installServer("Canvas", "git-Canvas-101 (MC: 1.21.11)");
|
||||
IrisSettings.IrisSettingsPregen pregen = new IrisSettings.IrisSettingsPregen();
|
||||
pregen.runtimeSchedulerMode = IrisRuntimeSchedulerMode.AUTO;
|
||||
|
||||
IrisRuntimeSchedulerMode resolved = IrisRuntimeSchedulerMode.resolve(pregen);
|
||||
assertEquals(IrisRuntimeSchedulerMode.PAPER_LIKE, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitModeBypassesAutoDetection() {
|
||||
installServer("Purpur", "git-Purpur-2562 (MC: 1.21.11)");
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ServerConfiguratorDatapackFolderTest {
|
||||
@Test
|
||||
public void resolvesDimensionWorldFolderBackToRootDatapacks() {
|
||||
File folder = new File("/tmp/server/world/dimensions/minecraft/overworld");
|
||||
File datapacks = ServerConfigurator.resolveDatapacksFolder(folder);
|
||||
assertEquals(new File("/tmp/server/world/datapacks").getAbsolutePath(), datapacks.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepsStandaloneWorldFolderDatapacksUnchanged() {
|
||||
File folder = new File("/tmp/server/custom_world");
|
||||
File datapacks = ServerConfigurator.resolveDatapacksFolder(folder);
|
||||
assertEquals(new File("/tmp/server/custom_world/datapacks").getAbsolutePath(), datapacks.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void installFoldersIncludeExtraStudioWorldDatapackTargets() {
|
||||
File baseFolder = new File("/tmp/server/world/datapacks");
|
||||
File extraFolder = new File("/tmp/server/iris-studio/datapacks");
|
||||
KList<File> baseFolders = new KList<>();
|
||||
baseFolders.add(baseFolder);
|
||||
KList<File> extraFolders = new KList<>();
|
||||
extraFolders.add(extraFolder);
|
||||
KMap<String, KList<File>> extrasByPack = new KMap<>();
|
||||
extrasByPack.put("overworld", extraFolders);
|
||||
|
||||
KList<File> folders = ServerConfigurator.collectInstallDatapackFolders(baseFolders, extrasByPack);
|
||||
|
||||
assertEquals(2, folders.size());
|
||||
assertTrue(folders.contains(baseFolder));
|
||||
assertTrue(folders.contains(extraFolder));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.iris.core.commands.CommandStudio;
|
||||
import art.arcane.iris.core.tools.IrisCreator;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class StudioRuntimeCleanupTest {
|
||||
@Test
|
||||
public void pregenSettingsNoLongerExposeStartupNoisemapPrebake() {
|
||||
boolean found = Arrays.stream(IrisSettings.IrisSettingsPregen.class.getDeclaredFields())
|
||||
.anyMatch(field -> field.getName().equals("startupNoisemapPrebake"));
|
||||
|
||||
assertFalse(found);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioCommandNoLongerExposesProfilecache() {
|
||||
boolean found = Arrays.stream(CommandStudio.class.getDeclaredMethods())
|
||||
.anyMatch(method -> method.getName().equals("profilecache"));
|
||||
|
||||
assertFalse(found);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioCreatorNoLongerContainsPrewarmOrPrebakeHelpers() {
|
||||
boolean found = Arrays.stream(IrisCreator.class.getDeclaredMethods())
|
||||
.map(method -> method.getName().toLowerCase())
|
||||
.anyMatch(name -> name.contains("prewarm") || name.contains("prebake"));
|
||||
|
||||
assertFalse(found);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noisemapPrebakePipelineClassIsRemoved() {
|
||||
try {
|
||||
Class.forName("art.arcane.iris.engine.IrisNoisemapPrebakePipeline");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AssertionError("IrisNoisemapPrebakePipeline should not exist.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CapabilityResolutionTest {
|
||||
@Test
|
||||
public void resolvesExactCreateLevelMethod() throws Exception {
|
||||
Method method = CapabilityResolution.resolveCreateLevelMethod(CurrentCreateLevelOwner.class);
|
||||
|
||||
assertEquals("createLevel", method.getName());
|
||||
assertEquals(3, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesDeclaredLegacyCreateLevelMethod() throws Exception {
|
||||
Method method = CapabilityResolution.resolveCreateLevelMethod(DeclaredLegacyCreateLevelOwner.class);
|
||||
|
||||
assertEquals("createLevel", method.getName());
|
||||
assertEquals(4, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesTwoArgLevelStorageAccessMethod() throws Exception {
|
||||
Method method = CapabilityResolution.resolveLevelStorageAccessMethod(TwoArgLevelStorageSource.class);
|
||||
|
||||
assertEquals("validateAndCreateAccess", method.getName());
|
||||
assertEquals(2, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesOneArgLevelStorageAccessMethod() throws Exception {
|
||||
Method method = CapabilityResolution.resolveLevelStorageAccessMethod(OneArgLevelStorageSource.class);
|
||||
|
||||
assertEquals("validateAndCreateAccess", method.getName());
|
||||
assertEquals(1, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesPublicWorldDataHelper() throws Exception {
|
||||
Method method = CapabilityResolution.resolvePaperWorldDataMethod(PublicWorldLoader.class);
|
||||
|
||||
assertEquals("loadWorldData", method.getName());
|
||||
assertEquals(3, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesDeclaredWorldDataHelper() throws Exception {
|
||||
Method method = CapabilityResolution.resolvePaperWorldDataMethod(DeclaredWorldLoader.class);
|
||||
|
||||
assertEquals("loadWorldData", method.getName());
|
||||
assertEquals(3, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesDeclaredServerRegistryAccessMethod() throws Exception {
|
||||
Method method = CapabilityResolution.resolveServerRegistryAccessMethod(DeclaredRegistryAccessOwner.class);
|
||||
|
||||
assertEquals("registryAccess", method.getName());
|
||||
assertEquals(0, method.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesCurrentWorldLoadingInfoConstructor() throws Exception {
|
||||
Constructor<?> constructor = CapabilityResolution.resolveWorldLoadingInfoConstructor(CurrentWorldLoadingInfo.class);
|
||||
|
||||
assertEquals(4, constructor.getParameterCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesLegacyWorldLoadingInfoConstructor() throws Exception {
|
||||
Constructor<?> constructor = CapabilityResolution.resolveWorldLoadingInfoConstructor(LegacyWorldLoadingInfo.class);
|
||||
|
||||
assertEquals(5, constructor.getParameterCount());
|
||||
}
|
||||
|
||||
public static final class LevelStem {
|
||||
}
|
||||
|
||||
public static final class WorldLoadingInfo {
|
||||
}
|
||||
|
||||
public static final class WorldLoadingInfoAndData {
|
||||
}
|
||||
|
||||
public static final class WorldDataAndGenSettings {
|
||||
}
|
||||
|
||||
public static final class PrimaryLevelData {
|
||||
}
|
||||
|
||||
public static final class LevelStorageAccess {
|
||||
}
|
||||
|
||||
public static final class ResourceKey {
|
||||
}
|
||||
|
||||
public static final class LoadedWorldData {
|
||||
}
|
||||
|
||||
public static final class MinecraftServer {
|
||||
}
|
||||
|
||||
public static final class RegistryAccess {
|
||||
}
|
||||
|
||||
public enum Environment {
|
||||
NORMAL
|
||||
}
|
||||
|
||||
public static final class CurrentCreateLevelOwner {
|
||||
public void createLevel(LevelStem levelStem, WorldLoadingInfoAndData worldLoadingInfoAndData, WorldDataAndGenSettings worldDataAndGenSettings) {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DeclaredLegacyCreateLevelOwner {
|
||||
private void createLevel(LevelStem levelStem, WorldLoadingInfo worldLoadingInfo, LevelStorageAccess levelStorageAccess, PrimaryLevelData primaryLevelData) {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TwoArgLevelStorageSource {
|
||||
public LevelStorageAccess validateAndCreateAccess(String worldName, ResourceKey resourceKey) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class OneArgLevelStorageSource {
|
||||
public LevelStorageAccess validateAndCreateAccess(String worldName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PublicWorldLoader {
|
||||
public static LoadedWorldData loadWorldData(MinecraftServer minecraftServer, ResourceKey dimensionKey, String worldName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DeclaredWorldLoader {
|
||||
private static LoadedWorldData loadWorldData(MinecraftServer minecraftServer, ResourceKey dimensionKey, String worldName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class DeclaredRegistryAccessOwner {
|
||||
private RegistryAccess registryAccess() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CurrentWorldLoadingInfo {
|
||||
public CurrentWorldLoadingInfo(Environment environment, ResourceKey stemKey, ResourceKey dimensionKey, boolean enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class LegacyWorldLoadingInfo {
|
||||
private LegacyWorldLoadingInfo(int index, String worldName, String environment, ResourceKey stemKey, boolean enabled) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class WorldLifecycleDiagnosticsTest {
|
||||
@Test
|
||||
public void studioCreateSelectionFailurePrintsFullStacktrace() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PAPER, false, false, false));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
PrintStream originalErr = System.err;
|
||||
System.setErr(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||
try {
|
||||
try {
|
||||
service.create(request).join();
|
||||
fail("Expected lifecycle create to fail when paper_like_runtime is unavailable.");
|
||||
} catch (CompletionException | IllegalStateException ignored) {
|
||||
}
|
||||
} finally {
|
||||
System.setErr(originalErr);
|
||||
}
|
||||
|
||||
String text = output.toString(StandardCharsets.UTF_8);
|
||||
assertTrue(text.contains("WorldLifecycle create backend selection failed"));
|
||||
assertTrue(text.contains("paper_like_runtime"));
|
||||
assertTrue(text.contains("IllegalStateException"));
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.INMSBinding;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineTarget;
|
||||
import art.arcane.iris.engine.platform.ChunkReplacementListener;
|
||||
import art.arcane.iris.engine.platform.ChunkReplacementOptions;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class WorldLifecycleRuntimeLevelStemTest {
|
||||
@Test
|
||||
public void runtimeStemUsesFullServerRegistryAccessForPlatformGenerators() throws Exception {
|
||||
Object datapackDimensions = new MissingDimensionTypeRegistry();
|
||||
Object serverRegistryAccess = new Object();
|
||||
CapabilitySnapshot capabilities = CapabilitySnapshot.forTestingRuntimeRegistries(ServerFamily.PURPUR, false, datapackDimensions, serverRegistryAccess);
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest(
|
||||
"studio",
|
||||
World.Environment.NORMAL,
|
||||
new TestingPlatformChunkGenerator(),
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
1337L,
|
||||
true,
|
||||
false,
|
||||
WorldLifecycleCaller.STUDIO
|
||||
);
|
||||
AtomicReference<Object> seenRegistryAccess = new AtomicReference<>();
|
||||
INMSBinding binding = createBinding((registryAccess, generator) -> {
|
||||
seenRegistryAccess.set(registryAccess);
|
||||
return "runtime-stem";
|
||||
});
|
||||
|
||||
Object levelStem = WorldLifecycleSupport.resolveRuntimeLevelStem(capabilities, request, binding);
|
||||
|
||||
assertEquals("runtime-stem", levelStem);
|
||||
assertSame(serverRegistryAccess, seenRegistryAccess.get());
|
||||
assertNotSame(datapackDimensions, seenRegistryAccess.get());
|
||||
}
|
||||
|
||||
private static INMSBinding createBinding(RuntimeStemFactory factory) {
|
||||
InvocationHandler handler = (proxy, method, args) -> {
|
||||
if ("createRuntimeLevelStem".equals(method.getName())) {
|
||||
return factory.create(args[0], (ChunkGenerator) args[1]);
|
||||
}
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (boolean.class.equals(returnType)) {
|
||||
return false;
|
||||
}
|
||||
if (int.class.equals(returnType)) {
|
||||
return 0;
|
||||
}
|
||||
if (long.class.equals(returnType)) {
|
||||
return 0L;
|
||||
}
|
||||
if (float.class.equals(returnType)) {
|
||||
return 0F;
|
||||
}
|
||||
if (double.class.equals(returnType)) {
|
||||
return 0D;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return (INMSBinding) Proxy.newProxyInstance(
|
||||
INMSBinding.class.getClassLoader(),
|
||||
new Class[]{INMSBinding.class},
|
||||
handler
|
||||
);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface RuntimeStemFactory {
|
||||
Object create(Object registryAccess, ChunkGenerator generator);
|
||||
}
|
||||
|
||||
private static final class MissingDimensionTypeRegistry {
|
||||
}
|
||||
|
||||
private static final class TestingPlatformChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator {
|
||||
@Override
|
||||
public Engine getEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisData getData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EngineTarget getTarget() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor, ChunkReplacementOptions options, ChunkReplacementListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStudio() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void touch(World world) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Integer> getSpawnChunks() {
|
||||
return CompletableFuture.completedFuture(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getInitialSpawnLocation(World world) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hotload() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class WorldLifecycleSelectionTest {
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnPaper() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PAPER, false, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnPurpur() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnCanvas() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.CANVAS, true, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioSelectsPaperLikeBackendOnFolia() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.FOLIA, true, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("paper_like_runtime", service.selectCreateBackend(request).backendName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioSelectsBukkitBackendOnSpigot() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.SPIGOT, false, false, false));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("studio", World.Environment.NORMAL, null, null, null, true, false, 1337L, true, false, WorldLifecycleCaller.STUDIO);
|
||||
|
||||
assertEquals("bukkit_public", service.selectCreateBackend(request).backendName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void persistentCreatePrefersBukkitBackendOnPaperLikeServers() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
WorldLifecycleRequest request = new WorldLifecycleRequest("persistent", World.Environment.NORMAL, null, null, null, true, false, 1337L, false, false, WorldLifecycleCaller.CREATE);
|
||||
|
||||
assertEquals("bukkit_public", service.selectCreateBackend(request).backendName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unloadUsesRememberedBackendFamily() {
|
||||
WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.PURPUR, false, false, true));
|
||||
|
||||
service.rememberBackend("studio", "paper_like_runtime");
|
||||
assertEquals("paper_like_runtime", service.selectUnloadBackend("studio").backendName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package art.arcane.iris.core.lifecycle;
|
||||
|
||||
import org.bukkit.generator.BiomeProvider;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class WorldLifecycleStagingTest {
|
||||
@Test
|
||||
public void stagedGeneratorIsConsumedExactlyOnce() {
|
||||
ChunkGenerator generator = mock(ChunkGenerator.class);
|
||||
|
||||
WorldLifecycleStaging.stageGenerator("world", generator, null);
|
||||
|
||||
assertSame(generator, WorldLifecycleStaging.consumeGenerator("world"));
|
||||
assertNull(WorldLifecycleStaging.consumeGenerator("world"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stagedStemGeneratorIsIndependentFromGeneratorConsumption() {
|
||||
ChunkGenerator generator = mock(ChunkGenerator.class);
|
||||
|
||||
WorldLifecycleStaging.stageGenerator("world", generator, null);
|
||||
WorldLifecycleStaging.stageStemGenerator("world", generator);
|
||||
|
||||
assertSame(generator, WorldLifecycleStaging.consumeGenerator("world"));
|
||||
assertSame(generator, WorldLifecycleStaging.consumeStemGenerator("world"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAllRemovesGeneratorBiomeAndStemState() {
|
||||
ChunkGenerator generator = mock(ChunkGenerator.class);
|
||||
BiomeProvider biomeProvider = mock(BiomeProvider.class);
|
||||
|
||||
WorldLifecycleStaging.stageGenerator("world", generator, biomeProvider);
|
||||
WorldLifecycleStaging.stageStemGenerator("world", generator);
|
||||
WorldLifecycleStaging.clearAll("world");
|
||||
|
||||
assertNull(WorldLifecycleStaging.consumeGenerator("world"));
|
||||
assertNull(WorldLifecycleStaging.consumeBiomeProvider("world"));
|
||||
assertNull(WorldLifecycleStaging.consumeStemGenerator("world"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package art.arcane.iris.core.nms;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class INMSBindingProbeCodesTest {
|
||||
@Test
|
||||
public void skipsSyntheticBukkitBindingWhenNmsIsEnabled() {
|
||||
List<String> probeCodes = NmsBindingProbeSupport.getBindingProbeCodes("BUKKIT", false, List.of("v1_21_R7"));
|
||||
|
||||
assertEquals(List.of("v1_21_R7"), probeCodes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void leavesBukkitFallbackEmptyWhenNmsIsDisabled() {
|
||||
List<String> probeCodes = NmsBindingProbeSupport.getBindingProbeCodes("BUKKIT", true, List.of("v1_21_R7"));
|
||||
|
||||
assertTrue(probeCodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepsConcreteBindingCodesAsPrimaryProbe() {
|
||||
List<String> probeCodes = NmsBindingProbeSupport.getBindingProbeCodes("v1_21_R7", false, List.of("v1_21_R7"));
|
||||
|
||||
assertEquals(List.of("v1_21_R7"), probeCodes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package art.arcane.iris.core.nms;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class MinecraftVersionTest {
|
||||
private interface PaperLikeServer extends Server {
|
||||
String getMinecraftVersion();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsMinecraftVersionFromPurpurDecoratedVersion() {
|
||||
Server server = mock(Server.class);
|
||||
doReturn("git-Purpur-2570 (MC: 1.21.11)").when(server).getVersion();
|
||||
doReturn("26.1.2.build.2570-experimental").when(server).getBukkitVersion();
|
||||
|
||||
MinecraftVersion version = MinecraftVersion.detect(server);
|
||||
assertEquals("1.21.11", version.value());
|
||||
assertEquals(21, version.major());
|
||||
assertEquals(11, version.minor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefersRuntimeMinecraftVersionMethodWhenPresent() {
|
||||
PaperLikeServer server = mock(PaperLikeServer.class);
|
||||
doReturn("1.21.11").when(server).getMinecraftVersion();
|
||||
doReturn("26.1.2-2570-e64b1b2 (MC: 26.1.2)").when(server).getVersion();
|
||||
doReturn("26.1.2.build.2570-experimental").when(server).getBukkitVersion();
|
||||
|
||||
MinecraftVersion version = MinecraftVersion.detect(server);
|
||||
assertEquals("1.21.11", version.value());
|
||||
assertEquals(21, version.major());
|
||||
assertEquals(11, version.minor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsPurpurApiBuildNumbersAsMinecraftVersion() {
|
||||
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("26.1.2.build.2570-experimental");
|
||||
assertNull(version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsesStandardBukkitSnapshotVersion() {
|
||||
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("1.21.11-R0.1-SNAPSHOT");
|
||||
assertEquals("1.21.11", version.value());
|
||||
assertEquals(21, version.major());
|
||||
assertEquals(11, version.minor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void comparesMajorBeforeMinor() {
|
||||
MinecraftVersion version = MinecraftVersion.fromBukkitVersion("1.20.12-R0.1-SNAPSHOT");
|
||||
assertFalse(version.isAtLeast(21, 11));
|
||||
assertTrue(version.isNewerThan(20, 11));
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package art.arcane.iris.core.nms.datapack.v1217;
|
||||
|
||||
import art.arcane.iris.core.nms.datapack.IDataFixer.Dimension;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DataFixerV1217DimensionTypeTest {
|
||||
private final DataFixerV1217 fixer = new DataFixerV1217();
|
||||
|
||||
@Test
|
||||
public void createsOverworldDimensionWithDragonFightDisabled() {
|
||||
JSONObject json = fixer.createDimension(Dimension.OVERWORLD, -256, 768, 512, null);
|
||||
|
||||
assertTrue(json.has("has_ender_dragon_fight"));
|
||||
assertEquals(false, json.getBoolean("has_ender_dragon_fight"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsEndDimensionWithDragonFightEnabled() {
|
||||
JSONObject json = fixer.createDimension(Dimension.END, 0, 256, 256, null);
|
||||
|
||||
assertTrue(json.has("has_ender_dragon_fight"));
|
||||
assertEquals(true, json.getBoolean("has_ender_dragon_fight"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package art.arcane.iris.core.runtime;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DatapackReadinessResultTest {
|
||||
@Test
|
||||
public void verificationUsesDimensionTypeKeyPath() throws Exception {
|
||||
Path root = Files.createTempDirectory("iris-datapack-readiness");
|
||||
Path datapackRoot = root.resolve("iris");
|
||||
Files.createDirectories(datapackRoot.resolve("data/iris/dimension_type"));
|
||||
Files.writeString(datapackRoot.resolve("pack.mcmeta"), "{}");
|
||||
Files.writeString(datapackRoot.resolve("data/iris/dimension_type/runtime-key.json"), "{}");
|
||||
|
||||
ArrayList<String> verifiedPaths = new ArrayList<>();
|
||||
ArrayList<String> missingPaths = new ArrayList<>();
|
||||
DatapackReadinessResult.collectVerificationPaths(root.toFile(), "runtime-key", verifiedPaths, missingPaths);
|
||||
|
||||
assertTrue(missingPaths.isEmpty());
|
||||
assertEquals(2, verifiedPaths.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verificationMarksMissingDimensionTypePath() throws Exception {
|
||||
Path root = Files.createTempDirectory("iris-datapack-readiness-missing");
|
||||
Path datapackRoot = root.resolve("iris");
|
||||
Files.createDirectories(datapackRoot);
|
||||
Files.writeString(datapackRoot.resolve("pack.mcmeta"), "{}");
|
||||
|
||||
ArrayList<String> verifiedPaths = new ArrayList<>();
|
||||
ArrayList<String> missingPaths = new ArrayList<>();
|
||||
DatapackReadinessResult.collectVerificationPaths(root.toFile(), "runtime-key", verifiedPaths, missingPaths);
|
||||
|
||||
assertEquals(1, verifiedPaths.size());
|
||||
assertEquals(1, missingPaths.size());
|
||||
assertTrue(missingPaths.get(0).endsWith(File.separator + "iris" + File.separator + "data" + File.separator + "iris" + File.separator + "dimension_type" + File.separator + "runtime-key.json"));
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package art.arcane.iris.core.runtime;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SmokeDiagnosticsServiceCloseStateTest {
|
||||
@Test
|
||||
public void closeStateIsPersistedIntoRunSnapshot() {
|
||||
SmokeDiagnosticsService service = SmokeDiagnosticsService.get();
|
||||
SmokeDiagnosticsService.SmokeRunHandle handle = service.beginRun(
|
||||
SmokeDiagnosticsService.SmokeRunMode.STUDIO_CLOSE,
|
||||
"iris-test-world",
|
||||
true,
|
||||
true,
|
||||
null,
|
||||
false
|
||||
);
|
||||
|
||||
handle.setCloseState(true, false, true);
|
||||
|
||||
SmokeDiagnosticsService.SmokeRunReport report = handle.snapshot();
|
||||
assertTrue(report.isCloseUnloadCompletedLive());
|
||||
assertFalse(report.isCloseFolderDeletionCompletedLive());
|
||||
assertTrue(report.isCloseStartupCleanupQueued());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package art.arcane.iris.core.runtime;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TransientWorldCleanupSupportTest {
|
||||
@Test
|
||||
public void identifiesTransientStudioBaseNamesAndSidecars() {
|
||||
String baseName = "iris-123e4567-e89b-12d3-a456-426614174000";
|
||||
|
||||
assertTrue(TransientWorldCleanupSupport.isTransientStudioWorldName(baseName));
|
||||
assertEquals(baseName, TransientWorldCleanupSupport.transientStudioBaseWorldName(baseName));
|
||||
assertEquals(baseName, TransientWorldCleanupSupport.transientStudioBaseWorldName(baseName + "_nether"));
|
||||
assertEquals(baseName, TransientWorldCleanupSupport.transientStudioBaseWorldName(baseName + "_the_end"));
|
||||
assertFalse(TransientWorldCleanupSupport.isTransientStudioWorldName("iris-smoke-studio-deadbeef"));
|
||||
assertNull(TransientWorldCleanupSupport.transientStudioBaseWorldName("overworld"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandsWorldFamilyNamesForDeletion() {
|
||||
String baseName = "iris-123e4567-e89b-12d3-a456-426614174000";
|
||||
|
||||
List<String> names = TransientWorldCleanupSupport.worldFamilyNames(baseName);
|
||||
|
||||
assertEquals(List.of(baseName, baseName + "_nether", baseName + "_the_end"), names);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectsOnlyTransientStudioWorldFamiliesFromContainer() throws IOException {
|
||||
File container = Files.createTempDirectory("transient-world-cleanup-test").toFile();
|
||||
String baseName = "iris-123e4567-e89b-12d3-a456-426614174000";
|
||||
File baseFolder = new File(container, baseName);
|
||||
File netherFolder = new File(container, baseName + "_nether");
|
||||
File smokeFolder = new File(container, "iris-smoke-studio-deadbeef");
|
||||
File regularFolder = new File(container, "overworld");
|
||||
baseFolder.mkdirs();
|
||||
netherFolder.mkdirs();
|
||||
smokeFolder.mkdirs();
|
||||
regularFolder.mkdirs();
|
||||
|
||||
try {
|
||||
LinkedHashSet<String> names = TransientWorldCleanupSupport.collectTransientStudioWorldNames(container);
|
||||
|
||||
assertEquals(new LinkedHashSet<>(List.of(baseName)), names);
|
||||
} finally {
|
||||
Files.deleteIfExists(baseFolder.toPath());
|
||||
Files.deleteIfExists(netherFolder.toPath());
|
||||
Files.deleteIfExists(smokeFolder.toPath());
|
||||
Files.deleteIfExists(regularFolder.toPath());
|
||||
Files.deleteIfExists(container.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package art.arcane.iris.core.runtime;
|
||||
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class WorldRuntimeControlServiceSafeEntryTest {
|
||||
@Test
|
||||
public void resolvesStudioEntryAnchorFromGeneratorInsteadOfMutableWorldSpawn() {
|
||||
World world = mock(World.class);
|
||||
PlatformChunkGenerator provider = mock(PlatformChunkGenerator.class);
|
||||
Location initialSpawn = new Location(world, 0.5D, 96D, 0.5D);
|
||||
Location mutableWorldSpawn = new Location(world, 128.5D, 80D, -64.5D);
|
||||
|
||||
doReturn(true).when(provider).isStudio();
|
||||
doReturn(initialSpawn).when(provider).getInitialSpawnLocation(world);
|
||||
doReturn(mutableWorldSpawn).when(world).getSpawnLocation();
|
||||
|
||||
Location resolved = WorldRuntimeControlService.resolveEntryAnchor(world, provider);
|
||||
|
||||
assertEquals(initialSpawn, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallsBackToWorldSpawnWhenGeneratorIsNotStudio() {
|
||||
World world = mock(World.class);
|
||||
PlatformChunkGenerator provider = mock(PlatformChunkGenerator.class);
|
||||
Location mutableWorldSpawn = new Location(world, 128.5D, 80D, -64.5D);
|
||||
|
||||
doReturn(false).when(provider).isStudio();
|
||||
doReturn(mutableWorldSpawn).when(world).getSpawnLocation();
|
||||
|
||||
Location resolved = WorldRuntimeControlService.resolveEntryAnchor(world, provider);
|
||||
|
||||
assertEquals(mutableWorldSpawn, resolved);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scansFromRuntimeSurfaceBeforeFallingThroughRemainingWorldHeight() {
|
||||
World world = mock(World.class);
|
||||
|
||||
doReturn(0).when(world).getMinHeight();
|
||||
doReturn(256).when(world).getMaxHeight();
|
||||
doReturn(179).when(world).getHighestBlockYAt(0, 0);
|
||||
|
||||
int[] scanOrder = WorldRuntimeControlService.buildSafeLocationScanOrder(world, new Location(world, 0.5D, 96D, 0.5D));
|
||||
|
||||
assertEquals(180, scanOrder[0]);
|
||||
assertEquals(179, scanOrder[1]);
|
||||
assertEquals(1, scanOrder[179]);
|
||||
assertEquals(181, scanOrder[180]);
|
||||
assertEquals(254, scanOrder[scanOrder.length - 1]);
|
||||
}
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
package art.arcane.iris.core.runtime;
|
||||
|
||||
import art.arcane.iris.core.lifecycle.CapabilitySnapshot;
|
||||
import art.arcane.iris.core.lifecycle.ServerFamily;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class WorldRuntimeControlServiceTimeLockTest {
|
||||
@Before
|
||||
public void ensureBukkitServer() {
|
||||
if (Bukkit.getServer() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Server server = mock(Server.class);
|
||||
PluginManager pluginManager = mock(PluginManager.class);
|
||||
doReturn(pluginManager).when(server).getPluginManager();
|
||||
doReturn(Logger.getLogger("WorldRuntimeControlServiceTimeLockTest")).when(server).getLogger();
|
||||
Bukkit.setServer(server);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsTimeLockWhenWorldDoesNotExposeMutableClock() throws Exception {
|
||||
AtomicBoolean setTimeCalled = new AtomicBoolean(false);
|
||||
AtomicLong dayTime = new AtomicLong(0L);
|
||||
World world = createWorldProxy("fixed", true, setTimeCalled, dayTime, false);
|
||||
|
||||
boolean applied = createService().applyNoonTimeLock(world);
|
||||
|
||||
assertFalse(applied);
|
||||
assertFalse(setTimeCalled.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void skipsTimeLockWhenRuntimeSetterRejectsClockMutation() throws Exception {
|
||||
AtomicBoolean setTimeCalled = new AtomicBoolean(false);
|
||||
AtomicLong dayTime = new AtomicLong(0L);
|
||||
World world = createWorldProxy("no-clock", false, setTimeCalled, dayTime, true);
|
||||
|
||||
boolean applied = createService().applyNoonTimeLock(world);
|
||||
|
||||
assertFalse(applied);
|
||||
assertTrue(setTimeCalled.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appliesTimeLockWhenWorldHasMutableClock() throws Exception {
|
||||
AtomicBoolean setTimeCalled = new AtomicBoolean(false);
|
||||
AtomicLong dayTime = new AtomicLong(0L);
|
||||
World world = createWorldProxy("mutable", false, setTimeCalled, dayTime, false);
|
||||
|
||||
boolean applied = createService().applyNoonTimeLock(world);
|
||||
|
||||
assertTrue(applied);
|
||||
assertTrue(setTimeCalled.get());
|
||||
assertTrue(dayTime.get() == 6000L);
|
||||
}
|
||||
|
||||
private WorldRuntimeControlService createService() throws Exception {
|
||||
Constructor<CapabilitySnapshot> snapshotConstructor = CapabilitySnapshot.class.getDeclaredConstructor(
|
||||
ServerFamily.class,
|
||||
boolean.class,
|
||||
Object.class,
|
||||
Class.class,
|
||||
Class.class,
|
||||
String.class,
|
||||
Object.class,
|
||||
Object.class,
|
||||
java.lang.reflect.Method.class,
|
||||
CapabilitySnapshot.PaperLikeFlavor.class,
|
||||
Class.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Constructor.class,
|
||||
java.lang.reflect.Constructor.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Field.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Field.class,
|
||||
java.lang.reflect.Field.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Method.class,
|
||||
java.lang.reflect.Method.class,
|
||||
String.class
|
||||
);
|
||||
snapshotConstructor.setAccessible(true);
|
||||
CapabilitySnapshot snapshot = snapshotConstructor.newInstance(
|
||||
ServerFamily.PAPER,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"test",
|
||||
Bukkit.getServer(),
|
||||
null,
|
||||
null,
|
||||
CapabilitySnapshot.PaperLikeFlavor.UNSUPPORTED,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"test"
|
||||
);
|
||||
|
||||
Constructor<WorldRuntimeControlService> serviceConstructor = WorldRuntimeControlService.class.getDeclaredConstructor(CapabilitySnapshot.class);
|
||||
serviceConstructor.setAccessible(true);
|
||||
return serviceConstructor.newInstance(snapshot);
|
||||
}
|
||||
|
||||
private World createWorldProxy(String name, boolean fixedTime, AtomicBoolean setTimeCalled, AtomicLong dayTime, boolean throwOnSetTime) {
|
||||
Object dimensionType = Proxy.newProxyInstance(
|
||||
World.class.getClassLoader(),
|
||||
new Class[]{DimensionTypeProbe.class},
|
||||
(proxy, method, args) -> {
|
||||
if ("hasFixedTime".equals(method.getName())) {
|
||||
return fixedTime;
|
||||
}
|
||||
if ("fixedTime".equals(method.getName())) {
|
||||
return fixedTime ? OptionalLong.of(6000L) : OptionalLong.empty();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
Object holder = Proxy.newProxyInstance(
|
||||
World.class.getClassLoader(),
|
||||
new Class[]{HolderProbe.class},
|
||||
(proxy, method, args) -> {
|
||||
if ("value".equals(method.getName())) {
|
||||
return dimensionType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
Object handle = Proxy.newProxyInstance(
|
||||
World.class.getClassLoader(),
|
||||
new Class[]{HandleProbe.class},
|
||||
(proxy, method, args) -> {
|
||||
if ("dimensionTypeRegistration".equals(method.getName())) {
|
||||
return holder;
|
||||
}
|
||||
if ("getDayTime".equals(method.getName())) {
|
||||
return dayTime.get();
|
||||
}
|
||||
if ("setDayTime".equals(method.getName())) {
|
||||
setTimeCalled.set(true);
|
||||
if (throwOnSetTime) {
|
||||
throw new IllegalArgumentException("Cannot set time in world without world clock");
|
||||
}
|
||||
dayTime.set(((Long) args[0]).longValue());
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
return (World) Proxy.newProxyInstance(
|
||||
World.class.getClassLoader(),
|
||||
new Class[]{World.class, WorldHandleProbe.class},
|
||||
(proxy, method, args) -> {
|
||||
if ("getName".equals(method.getName())) {
|
||||
return name;
|
||||
}
|
||||
if ("getHandle".equals(method.getName())) {
|
||||
return handle;
|
||||
}
|
||||
if ("getFullTime".equals(method.getName())) {
|
||||
return dayTime.get();
|
||||
}
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (boolean.class.equals(returnType)) {
|
||||
return false;
|
||||
}
|
||||
if (int.class.equals(returnType)) {
|
||||
return 0;
|
||||
}
|
||||
if (long.class.equals(returnType)) {
|
||||
return 0L;
|
||||
}
|
||||
if (float.class.equals(returnType)) {
|
||||
return 0F;
|
||||
}
|
||||
if (double.class.equals(returnType)) {
|
||||
return 0D;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private interface WorldHandleProbe {
|
||||
Object getHandle();
|
||||
}
|
||||
|
||||
private interface HandleProbe {
|
||||
Object dimensionTypeRegistration();
|
||||
|
||||
long getDayTime();
|
||||
|
||||
void setDayTime(long time);
|
||||
}
|
||||
|
||||
private interface HolderProbe {
|
||||
Object value();
|
||||
}
|
||||
|
||||
private interface DimensionTypeProbe {
|
||||
boolean hasFixedTime();
|
||||
|
||||
OptionalLong fixedTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package art.arcane.iris.engine.framework;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GenerationSessionManagerTest {
|
||||
@Test
|
||||
public void teardownSealMarksRejectedWorkAsExpected() throws Exception {
|
||||
GenerationSessionManager manager = new GenerationSessionManager();
|
||||
|
||||
manager.sealAndAwait("close", 1000L, true);
|
||||
|
||||
try {
|
||||
manager.acquire("chunk_generate");
|
||||
} catch (GenerationSessionException e) {
|
||||
assertTrue(e.isExpectedTeardown());
|
||||
assertTrue(e.getMessage().contains("during close"));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new AssertionError("Expected teardown rejection.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sealAndAwaitCompletesWhenOutstandingLeaseReleases() throws Exception {
|
||||
GenerationSessionManager manager = new GenerationSessionManager();
|
||||
GenerationSessionLease lease = manager.acquire("chunk_generate");
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Thread releaser = new Thread(() -> {
|
||||
try {
|
||||
latch.await(200L, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
lease.close();
|
||||
});
|
||||
releaser.start();
|
||||
latch.countDown();
|
||||
|
||||
manager.sealAndAwait("close", 1000L, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisDimensionTypeKeyTest {
|
||||
@Test
|
||||
public void dimensionTypeKeyUsesSanitizedSemanticPackKey() {
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
dimension.setLoadKey("Overworld");
|
||||
|
||||
assertEquals("overworld", dimension.getDimensionTypeKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dimensionTypeKeySanitizesUnsafePackCharacters() {
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
dimension.setLoadKey("Worlds/My Pack");
|
||||
|
||||
assertEquals("worlds_my_pack", dimension.getDimensionTypeKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisObjectBlockDataMergeTest {
|
||||
@Test
|
||||
public void reparsesBlockDataBeforeRetryingMerge() {
|
||||
BlockData base = mock(BlockData.class);
|
||||
BlockData update = mock(BlockData.class);
|
||||
BlockData parsedBase = mock(BlockData.class);
|
||||
BlockData parsedUpdate = mock(BlockData.class);
|
||||
BlockData merged = mock(BlockData.class);
|
||||
Function<String, BlockData> resolver = createResolver(
|
||||
"minecraft:oak_log[axis=x]", parsedBase,
|
||||
"minecraft:oak_log[axis=y]", parsedUpdate
|
||||
);
|
||||
|
||||
doThrow(new IllegalArgumentException("Block data not created via string parsing")).when(base).merge(update);
|
||||
doReturn("minecraft:oak_log[axis=x]").when(base).getAsString(false);
|
||||
doReturn("minecraft:oak_log[axis=y]").when(update).getAsString(false);
|
||||
doReturn(merged).when(parsedBase).merge(parsedUpdate);
|
||||
|
||||
BlockData result = BlockDataMergeSupport.merge(base, update, resolver);
|
||||
|
||||
assertSame(merged, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallsBackToNormalizedUpdateWhenRetryMergeStillFails() {
|
||||
BlockData base = mock(BlockData.class);
|
||||
BlockData update = mock(BlockData.class);
|
||||
BlockData parsedBase = mock(BlockData.class);
|
||||
BlockData parsedUpdate = mock(BlockData.class);
|
||||
Function<String, BlockData> resolver = createResolver(
|
||||
"minecraft:stone", parsedBase,
|
||||
"minecraft:stone[waterlogged=true]", parsedUpdate
|
||||
);
|
||||
|
||||
doThrow(new IllegalArgumentException("Block data not created via string parsing")).when(base).merge(update);
|
||||
doReturn("minecraft:stone").when(base).getAsString(false);
|
||||
doReturn("minecraft:stone[waterlogged=true]").when(update).getAsString(false);
|
||||
doThrow(new IllegalArgumentException("normalized merge failed")).when(parsedBase).merge(parsedUpdate);
|
||||
|
||||
BlockData result = BlockDataMergeSupport.merge(base, update, resolver);
|
||||
|
||||
assertSame(parsedUpdate, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepsDirectMergeWhenBukkitAcceptsIt() {
|
||||
BlockData base = mock(BlockData.class);
|
||||
BlockData update = mock(BlockData.class);
|
||||
BlockData merged = mock(BlockData.class);
|
||||
|
||||
doReturn(Material.STONE).when(base).getMaterial();
|
||||
doReturn(Material.STONE).when(update).getMaterial();
|
||||
doReturn(merged).when(base).merge(update);
|
||||
|
||||
BlockData result = BlockDataMergeSupport.merge(base, update, key -> null);
|
||||
|
||||
assertSame(merged, result);
|
||||
}
|
||||
|
||||
private Function<String, BlockData> createResolver(String firstKey, BlockData firstValue, String secondKey, BlockData secondValue) {
|
||||
Map<String, BlockData> resolved = new HashMap<>();
|
||||
resolved.put(firstKey, firstValue);
|
||||
resolved.put(secondKey, secondValue);
|
||||
return resolved::get;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package art.arcane.iris.util.common.parallel;
|
||||
|
||||
import art.arcane.volmlib.util.parallel.BurstExecutorSupport;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BurstExecutorSupportReentrantTest {
|
||||
@Test
|
||||
public void runsNestedBurstInlineOnSameForkJoinPoolWorker() throws Exception {
|
||||
ForkJoinPool pool = new ForkJoinPool(1);
|
||||
AtomicBoolean nestedExecuted = new AtomicBoolean(false);
|
||||
|
||||
try {
|
||||
Future<?> future = pool.submit(() -> {
|
||||
BurstExecutorSupport burst = new BurstExecutorSupport(pool, 1);
|
||||
burst.queue(() -> {
|
||||
BurstExecutorSupport nested = new BurstExecutorSupport(pool, 1);
|
||||
nested.queue(() -> nestedExecuted.set(true));
|
||||
nested.complete();
|
||||
});
|
||||
burst.complete();
|
||||
});
|
||||
|
||||
future.get(5, TimeUnit.SECONDS);
|
||||
assertTrue(nestedExecuted.get());
|
||||
} finally {
|
||||
pool.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package art.arcane.iris.util.common.plugin;
|
||||
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class VolmitSenderMiniMessageEscapeTest {
|
||||
@Test
|
||||
public void escapesApostrophesForQuotedHoverText() {
|
||||
String escaped = VolmitSender.escapeMiniMessageQuotedText("This world's dimension config");
|
||||
|
||||
assertEquals("This world\\'s dimension config", escaped);
|
||||
MiniMessage.miniMessage().deserialize("<hover:show_text:'" + escaped + "'>ok</hover>");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void escapesBackslashesBeforeQuotedHoverText() {
|
||||
String escaped = VolmitSender.escapeMiniMessageQuotedText("Path \\\\ data");
|
||||
|
||||
assertEquals("Path \\\\\\\\ data", escaped);
|
||||
MiniMessage.miniMessage().deserialize("<hover:show_text:'" + escaped + "'>ok</hover>");
|
||||
}
|
||||
}
|
||||
+40
@@ -1,5 +1,6 @@
|
||||
package art.arcane.iris.util.project.context;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
@@ -7,9 +8,16 @@ import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
@@ -76,6 +84,16 @@ public class ChunkContextPrefillPlanTest {
|
||||
assertEquals(256, caveCalls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paperCommonWorkerThreadsDisableAsyncPrefillWhenPluginLoaded() throws Exception {
|
||||
assertPrefillAsyncDecision("Paper Common Worker #0", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void irisWorkerThreadsKeepAsyncPrefillWhenPluginLoaded() throws Exception {
|
||||
assertPrefillAsyncDecision("Iris 42", true);
|
||||
}
|
||||
|
||||
private ChunkContext createContext(
|
||||
ChunkContext.PrefillPlan prefillPlan,
|
||||
AtomicInteger caveCalls,
|
||||
@@ -145,4 +163,26 @@ public class ChunkContextPrefillPlanTest {
|
||||
|
||||
return new ChunkContext(32, 48, complex, true, prefillPlan, null);
|
||||
}
|
||||
|
||||
private void assertPrefillAsyncDecision(String threadName, boolean expected) throws InterruptedException, ExecutionException, java.util.concurrent.TimeoutException {
|
||||
Iris previous = Iris.instance;
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setName(threadName);
|
||||
return thread;
|
||||
});
|
||||
try {
|
||||
Iris.instance = mock(Iris.class);
|
||||
Future<Boolean> future = executor.submit(() -> ChunkContext.shouldPrefillAsync(2));
|
||||
boolean actual = future.get(10, TimeUnit.SECONDS);
|
||||
if (expected) {
|
||||
assertTrue(actual);
|
||||
} else {
|
||||
assertFalse(actual);
|
||||
}
|
||||
} finally {
|
||||
Iris.instance = previous;
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
Paper 1.21.11
|
||||
/iris create matrix-paper overworld
|
||||
/iris std o overworld
|
||||
close Studio world
|
||||
benchmark create/unload
|
||||
|
||||
Purpur 1.21.11
|
||||
/iris create matrix-purpur overworld
|
||||
/iris std o overworld
|
||||
close Studio world
|
||||
benchmark create/unload
|
||||
|
||||
Canvas 1.21.11
|
||||
/iris create matrix-canvas overworld
|
||||
/iris std o overworld
|
||||
close Studio world
|
||||
benchmark create/unload
|
||||
|
||||
Folia 1.21.11
|
||||
/iris create matrix-folia overworld
|
||||
/iris std o overworld
|
||||
close Studio world
|
||||
benchmark create/unload
|
||||
|
||||
Spigot 1.21.11
|
||||
/iris create matrix-spigot overworld
|
||||
/iris std o overworld
|
||||
close Studio world
|
||||
unload/remove
|
||||
Reference in New Issue
Block a user