diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisProbeChunkGenerator.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisProbeChunkGenerator.java new file mode 100644 index 000000000..c38ea4c9e --- /dev/null +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisProbeChunkGenerator.java @@ -0,0 +1,101 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.core; + +import org.bukkit.HeightMap; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; +import java.util.Random; + +/** + * Answer to a generator-plugin discovery probe. + *

+ * Multiverse-Core calls {@code getDefaultWorldGenerator} with an empty dimension id and the name of + * an already loaded world purely to find out whether a plugin is a generator plugin, then throws the + * returned instance away. A non-null answer keeps Iris in {@code /mv generators} and in Multiverse's + * generator tab-completion without Multiverse logging a warning block on every boot. + *

+ * Nothing here can build terrain: every generation entry point refuses, so an instance that escapes + * the probe fails loudly instead of silently producing vanilla chunks. + */ +final class IrisProbeChunkGenerator extends ChunkGenerator { + private final String worldName; + + IrisProbeChunkGenerator(String worldName) { + this.worldName = Objects.requireNonNull(worldName, "worldName"); + } + + @Override + public void generateNoise(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull ChunkData chunkData) { + throw refusal(); + } + + @Override + public void generateSurface(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull ChunkData chunkData) { + throw refusal(); + } + + @Override + public void generateBedrock(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull ChunkData chunkData) { + throw refusal(); + } + + @Override + public void generateCaves(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull ChunkData chunkData) { + throw refusal(); + } + + @Override + public ChunkData generateChunkData(@NotNull World world, @NotNull Random random, int x, int z, @NotNull BiomeGrid biome) { + throw refusal(); + } + + @Override + public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { + throw refusal(); + } + + @Override + public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) { + throw refusal(); + } + + @Override + public List getDefaultPopulators(@NotNull World world) { + throw refusal(); + } + + @Override + public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { + throw refusal(); + } + + private IllegalStateException refusal() { + return new IllegalStateException("Iris generator-discovery probe for '" + worldName + + "' was asked to generate terrain. Iris worlds are created with /iris create."); + } +} diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java index 445749a8f..222ec94bc 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java @@ -33,15 +33,19 @@ import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisWorld; import art.arcane.iris.engine.platform.BukkitChunkGenerator; import art.arcane.iris.util.common.plugin.VolmitPlugin; +import art.arcane.volmlib.util.bukkit.WorldIdentity; import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; +import org.bukkit.World; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.ChunkGenerator; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -56,6 +60,7 @@ import java.util.function.Supplier; public final class IrisWorldGeneratorResolver { private static final int VALIDATION_STABILITY_ATTEMPTS = 2; private static final Object SNAPSHOT_VALIDATION_LOCK = new Object(); + private static final String IRIS_DIMENSION_NAMESPACE = "iris"; private final VolmitPlugin plugin; @@ -278,6 +283,10 @@ public final class IrisWorldGeneratorResolver { } public ChunkGenerator resolveDefaultWorldGenerator(String worldName, String id) { + if (isGeneratorDiscoveryProbe(worldName, id)) { + Iris.debug("Generator discovery probe for loaded world " + worldName); + return new IrisProbeChunkGenerator(worldName); + } IrisStartupValidation.requireWorldCreationReady(); ChunkGenerator stagedGenerator = WorldLifecycleStaging.consumeGenerator(worldName); if (stagedGenerator != null) { @@ -288,6 +297,11 @@ public final class IrisWorldGeneratorResolver { if (id == null || id.isEmpty()) id = IrisSettings.get().getGenerator().getDefaultWorldType(); Iris.debug("Generator ID: " + id + " requested by bukkit/plugin"); + File levelRoot = IrisWorldStorage.levelRoot(); + NamespacedKey worldKey = configuredWorldKey(worldName, levelRoot.getName()); + requireWorldKeyAvailable(worldName, worldKey); + requireOwnedWorld(worldName, levelRoot, worldKey); + try { return resolveFrozenWorldGenerator(worldName, id); } catch (RuntimeException failure) { @@ -298,6 +312,66 @@ public final class IrisWorldGeneratorResolver { } } + /** + * Multiverse-Core probes every enabled plugin by asking for a generator with an empty dimension + * id and the name of a world that is already loaded. Bukkit never creates a world that is + * already loaded, so that pair only ever means discovery, never generation. + */ + private static boolean isGeneratorDiscoveryProbe(String worldName, String id) { + return id != null && id.isEmpty() && Bukkit.getWorld(worldName) != null; + } + + /** + * Refuses a second generator for a world key that is already live. Multiverse imports accept a + * world name that maps onto a loaded Iris key, which would otherwise start a second engine on + * the same storage with a different seed. + */ + private static void requireWorldKeyAvailable(String worldName, NamespacedKey worldKey) { + World loaded = WorldIdentity.resolve(worldKey).orElse(null); + if (loaded == null) { + return; + } + throw new IllegalStateException("Refusing to generate '" + worldName + "': " + worldKey + + " is already loaded as '" + loaded.getName() + "'."); + } + + /** + * Refuses worlds Iris does not own before the fail-fast path can see them. A world without Iris + * storage is somebody else's create or import, and returning null there would silently hand the + * caller a vanilla world. + *

+ * The {@code iris} dimension namespace is Iris-exclusive, so a directory in it is ownership on + * its own. Every server already has {@code dimensions/minecraft/*}, so a vanilla slot counts as + * Iris' only once it carries a frozen pack snapshot. + */ + private static void requireOwnedWorld(String worldName, File levelRoot, NamespacedKey worldKey) { + File dimensionRoot; + try { + dimensionRoot = IrisWorldStorage.frozenDimensionRoot( + Bukkit.getWorldContainer(), + levelRoot, + worldName, + worldKey + ).orElse(null); + } catch (RuntimeException unusableStorage) { + // Storage exists but cannot be resolved: an owned world, left to the fail-fast path. + return; + } + if (dimensionRoot != null + && (IRIS_DIMENSION_NAMESPACE.equals(worldKey.getNamespace()) || hasFrozenPack(dimensionRoot))) { + return; + } + throw new IllegalStateException("'" + worldName + "' (" + worldKey + + ") has no Iris world storage, so Iris cannot generate it." + + " Create Iris worlds with /iris create " + worldName + " type=;" + + " Iris registers them with Multiverse itself."); + } + + private static boolean hasFrozenPack(File dimensionRoot) { + Path packRoot = dimensionRoot.toPath().toAbsolutePath().normalize().resolve("iris").resolve("pack"); + return Files.isDirectory(packRoot, LinkOption.NOFOLLOW_LINKS); + } + private ChunkGenerator resolveFrozenWorldGenerator(String worldName, String id) { File levelRoot = IrisWorldStorage.levelRoot(); NamespacedKey worldKey = configuredWorldKey(worldName, levelRoot.getName()); diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java index ad40767dc..11c03e553 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java @@ -234,7 +234,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D } private boolean sendHelpIfRequested(CommandSender sender, String[] args) { - Optional request = DirectorMiniMenu.resolveHelp(getDirector(), Arrays.asList(args), 17); + Optional request = DirectorMiniMenu.resolveHelp(getDirector(), Arrays.asList(args)); if (request.isEmpty()) { return false; } diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/IrisWorldGeneratorResolverTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/IrisWorldGeneratorResolverTest.java index f94c19bf2..7496dc732 100644 --- a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/IrisWorldGeneratorResolverTest.java +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/IrisWorldGeneratorResolverTest.java @@ -1,5 +1,6 @@ package art.arcane.iris.core; +import art.arcane.iris.Iris; import art.arcane.iris.core.pack.BrokenPackException; import art.arcane.iris.core.pack.PackValidationRegistry; import art.arcane.iris.core.pack.PackValidationResult; @@ -7,19 +8,30 @@ import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; +import org.mockito.MockedStatic; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; public class IrisWorldGeneratorResolverTest { @Rule @@ -130,22 +142,158 @@ public class IrisWorldGeneratorResolverTest { } @Test - public void configuredWorldSnapshotFailureStopsStartupAndRethrows() throws Exception { + public void ownedWorldSnapshotFailureStopsStartupAndRethrows() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/IrisWorldGeneratorResolver.java")); int resolverStart = source.indexOf("public ChunkGenerator resolveDefaultWorldGenerator("); int resolverEnd = source.indexOf("private ChunkGenerator resolveFrozenWorldGenerator(", resolverStart); String resolver = source.substring(resolverStart, resolverEnd); - int failureCapture = resolver.indexOf("catch (RuntimeException failure)"); + int probe = resolver.indexOf("isGeneratorDiscoveryProbe(worldName, id)"); + int readiness = resolver.indexOf("IrisStartupValidation.requireWorldCreationReady()"); + int duplicateGuard = resolver.indexOf("requireWorldKeyAvailable(worldName, worldKey)"); + int ownership = resolver.indexOf("requireOwnedWorld(worldName, levelRoot, worldKey)"); + int frozen = resolver.indexOf("return resolveFrozenWorldGenerator(", ownership); + int failureCapture = resolver.indexOf("catch (RuntimeException failure)", frozen); int report = resolver.indexOf("Iris.reportError(", failureCapture); int shutdown = resolver.indexOf("Bukkit.shutdown()", report); int rethrow = resolver.indexOf("throw failure", shutdown); - assertTrue(failureCapture >= 0); + assertTrue(probe >= 0); + assertTrue(readiness > probe); + assertTrue(duplicateGuard > readiness); + assertTrue(ownership > duplicateGuard); + assertTrue(frozen > ownership); + assertTrue(failureCapture > frozen); assertTrue(report > failureCapture); assertTrue(shutdown > report); assertTrue(rethrow > shutdown); + + String shutdownScope = "the fail-fast shutdown must stay scoped to the frozen snapshot of an owned world"; + assertEquals(shutdownScope, resolverStart + shutdown, source.indexOf("Bukkit.shutdown()")); + assertEquals(shutdownScope, resolverStart + shutdown, source.lastIndexOf("Bukkit.shutdown()")); + } + + @Test + public void discoveryProbeOfLoadedWorldReturnsInertGeneratorQuietly() { + try (MockedStatic bukkit = mockStatic(Bukkit.class); + MockedStatic iris = mockStatic(Iris.class)) { + bukkit.when(() -> Bukkit.getWorld("world")).thenReturn(mock(World.class)); + + ChunkGenerator probe = new IrisWorldGeneratorResolver(null) + .resolveDefaultWorldGenerator("world", ""); + + assertNotNull("Multiverse drops Iris from /mv generators when the probe returns null", probe); + bukkit.verify(Bukkit::shutdown, never()); + IllegalStateException refusal = assertThrows( + IllegalStateException.class, + () -> probe.generateNoise(mock(WorldInfo.class), new Random(), 0, 0, null)); + assertTrue(refusal.getMessage(), refusal.getMessage().contains("'world'")); + } + } + + @Test + public void externalCreateWithoutIrisStorageThrowsWithoutShutdown() throws Exception { + File worldContainer = temporaryFolder.newFolder("external-create"); + File levelRoot = new File(worldContainer, "world"); + assertTrue(levelRoot.mkdirs()); + + try (MockedStatic bukkit = mockStatic(Bukkit.class); + MockedStatic iris = mockStatic(Iris.class)) { + Server server = mock(Server.class); + when(server.getLevelDirectory()).thenReturn(levelRoot.toPath()); + bukkit.when(Bukkit::getServer).thenReturn(server); + bukkit.when(Bukkit::getWorldContainer).thenReturn(worldContainer); + bukkit.when(Bukkit::getWorlds).thenReturn(List.of()); + + IllegalStateException failure = assertThrows( + IllegalStateException.class, + () -> new IrisWorldGeneratorResolver(null) + .resolveDefaultWorldGenerator("mvtest", "overworld")); + + assertTrue(failure.getMessage(), failure.getMessage().contains("mvtest")); + assertTrue(failure.getMessage(), failure.getMessage().contains("/iris create")); + bukkit.verify(Bukkit::shutdown, never()); + } + } + + @Test + public void ownedIrisWorldWithUnusableSnapshotStillStopsTheServer() throws Exception { + File worldContainer = temporaryFolder.newFolder("owned-broken"); + File levelRoot = new File(worldContainer, "world"); + assertTrue(levelRoot.mkdirs()); + assertTrue(new File(worldContainer, "world_iris_moon/dimensions/iris/moon").mkdirs()); + + try (MockedStatic bukkit = mockStatic(Bukkit.class); + MockedStatic iris = mockStatic(Iris.class)) { + Server server = mock(Server.class); + when(server.getLevelDirectory()).thenReturn(levelRoot.toPath()); + bukkit.when(Bukkit::getServer).thenReturn(server); + bukkit.when(Bukkit::getWorldContainer).thenReturn(worldContainer); + bukkit.when(Bukkit::getWorlds).thenReturn(List.of()); + + assertThrows( + IllegalStateException.class, + () -> new IrisWorldGeneratorResolver(null) + .resolveDefaultWorldGenerator("world_iris_moon", "overworld")); + + bukkit.verify(Bukkit::shutdown); + } + } + + @Test + public void vanillaDimensionSlotWithoutFrozenPackIsNotOwned() throws Exception { + File worldContainer = temporaryFolder.newFolder("vanilla-slot"); + File levelRoot = new File(worldContainer, "world"); + assertTrue(new File(levelRoot, "dimensions/minecraft/the_nether").mkdirs()); + + try (MockedStatic bukkit = mockStatic(Bukkit.class); + MockedStatic iris = mockStatic(Iris.class)) { + Server server = mock(Server.class); + when(server.getLevelDirectory()).thenReturn(levelRoot.toPath()); + bukkit.when(Bukkit::getServer).thenReturn(server); + bukkit.when(Bukkit::getWorldContainer).thenReturn(worldContainer); + bukkit.when(Bukkit::getWorlds).thenReturn(List.of()); + + IllegalStateException failure = assertThrows( + IllegalStateException.class, + () -> new IrisWorldGeneratorResolver(null) + .resolveDefaultWorldGenerator("world_nether", "overworld")); + + assertTrue(failure.getMessage(), failure.getMessage().contains("minecraft:the_nether")); + assertTrue(failure.getMessage(), failure.getMessage().contains("/iris create")); + bukkit.verify(Bukkit::shutdown, never()); + } + } + + @Test + public void alreadyLoadedIrisKeyIsRefusedBeforeAnyEngineCanStart() throws Exception { + File worldContainer = temporaryFolder.newFolder("duplicate-key"); + File levelRoot = new File(worldContainer, "world"); + assertTrue(levelRoot.mkdirs()); + writeValidPack(new File(levelRoot, "dimensions/iris/irisworld/iris/pack").toPath()); + + World loaded = mock(World.class); + when(loaded.getKey()).thenReturn(new NamespacedKey("iris", "irisworld")); + when(loaded.getName()).thenReturn("world_iris_irisworld"); + + try (MockedStatic bukkit = mockStatic(Bukkit.class); + MockedStatic iris = mockStatic(Iris.class)) { + Server server = mock(Server.class); + when(server.getLevelDirectory()).thenReturn(levelRoot.toPath()); + bukkit.when(Bukkit::getServer).thenReturn(server); + bukkit.when(Bukkit::getWorldContainer).thenReturn(worldContainer); + bukkit.when(Bukkit::getWorlds).thenReturn(List.of(loaded)); + + IllegalStateException failure = assertThrows( + IllegalStateException.class, + () -> new IrisWorldGeneratorResolver(null) + .resolveDefaultWorldGenerator("irisworld", "overworld")); + + assertTrue(failure.getMessage(), failure.getMessage().contains("iris:irisworld")); + assertTrue(failure.getMessage(), failure.getMessage().contains("world_iris_irisworld")); + bukkit.verify(Bukkit::shutdown, never()); + } } private static void writeValidPack(Path packRoot) throws Exception { diff --git a/adapters/fabric/logs/latest.log b/adapters/fabric/logs/latest.log index 3174ceafc..24f32e414 100644 --- a/adapters/fabric/logs/latest.log +++ b/adapters/fabric/logs/latest.log @@ -1,3 +1,488 @@ -[15:24:46] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player) -[15:24:46] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered -[15:24:46] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered +[19:35:02] [Test worker/WARN]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0 +[19:35:02] [Test worker/WARN]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes +[19:35:02] [Test worker/INFO]: [STDERR]: [Iris/WARN] Can't find block data for minecraft:definitely_not_a_real_block +[19:35:02] [Test worker/INFO]: [STDERR]: [Iris/WARN] Can't find block data for minecraft:minecraft:definitely_not_a_real_block +[19:35:02] [Test worker/INFO]: [STDERR]: [Iris/WARN] Block 'minecraft:oak_log' rejected state 'not_a_property=x'; using its default state +[19:35:02] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test' +[19:35:02] [Test worker/WARN]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial7029509183426900057/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial7029509183426900057/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"} +[19:35:02] [Test worker/ERROR]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot10459791259189814912/iris-dimensions.json is corrupt; quarantining it and continuing boot +java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot10459791259189814912/iris-dimensions.json could not be read; refusing to discard persistent worlds + at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:150) + at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:56) + at art.arcane.iris.modded.ModdedDimensionRegistryStore.loadForStartup(ModdedDimensionRegistryStore.java:69) + at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.startupLoadQuarantinesACorruptRegistryInsteadOfFailingBoot(ModdedDimensionRegistryStoreTest.java:99) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +Caused by: art.arcane.volmlib.util.json.JSONException: A JSONObject text must end with '}' at 34 [character 35 line 1] + at art.arcane.volmlib.util.json.JSONTokener.syntaxError(JSONTokener.java:414) + at art.arcane.volmlib.util.json.JSONObject.(JSONObject.java:145) + at art.arcane.volmlib.util.json.JSONTokener.nextValue(JSONTokener.java:345) + at art.arcane.volmlib.util.json.JSONArray.(JSONArray.java:111) + at art.arcane.volmlib.util.json.JSONTokener.nextValue(JSONTokener.java:348) + at art.arcane.volmlib.util.json.JSONObject.(JSONObject.java:159) + at art.arcane.volmlib.util.json.JSONObject.(JSONObject.java:260) + at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:117) + ... 45 more +[19:35:02] [Test worker/ERROR]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost +[19:35:02] [Test worker/ERROR]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot10459791259189814912/iris-dimensions.json.broken-1787182502612 +[19:35:02] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +java.lang.RuntimeException: second disable failed + at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService +java.lang.RuntimeException: first disable failed + at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/ERROR]: Iris disabled all services with 2 failure(s) +java.lang.RuntimeException: second disable failed + at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) + Suppressed: java.lang.RuntimeException: first disable failed + at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54) + ... 42 more +[19:35:02] [Test worker/ERROR]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +java.lang.RuntimeException: cleanup failed + at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/ERROR]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +java.lang.RuntimeException: enable failed + at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) + Suppressed: java.lang.RuntimeException: cleanup failed + at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) + ... 42 more +[19:35:02] [Test worker/ERROR]: [worldcheck] server stop request failed +java.lang.IllegalStateException: stop request failed + at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:238) + at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:144) + at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:235) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/ERROR]: [worldcheck] waiting for server shutdown failed +java.lang.IllegalStateException: shutdown wait failed + at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:264) + at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:159) + at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:262) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/ERROR]: [worldcheck] check failed +java.lang.IllegalStateException: check failed + at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:224) + at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:139) + at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:222) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success' +[19:35:02] [Test worker/WARN]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider) +java.lang.RuntimeException: provider init failed + at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) + at java.base/java.lang.reflect.Method.invoke(Method.java:565) + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +[19:35:02] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player) +[19:35:02] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered +[19:35:02] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java index 6778d04ff..0db67d247 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java @@ -278,46 +278,75 @@ public class ModdedLifecycleFailureContractTest { private static String method(String source, String signature) { int start = requiredIndex(source, signature); - int openBrace = requiredIndex(source, "{", start); + int openBrace = openingBrace(source, start); return source.substring(start, matchingBrace(source, openBrace) + 1); } + /** + * Returns the block of the shallowest catch in the method, i.e. the one attached to the + * method's outermost try. Textual first-match would return a nested catch instead, and a + * nested catch handles a different failure than the one these contracts pin. + */ private static String catchBlock(String source) { - int catchIndex = requiredIndex(source, "catch ("); - int openBrace = requiredIndex(source, "{", catchIndex); - return blockAt(source, openBrace); + int bodyStart = openingBrace(source, 0); + int depth = 0; + int bestDepth = Integer.MAX_VALUE; + int catchIndex = -1; + int i = bodyStart; + while (i < source.length()) { + int skipped = skipNonCode(source, i); + if (skipped > i) { + i = skipped; + continue; + } + char current = source.charAt(i); + if (current == '{') { + depth++; + } else if (current == '}') { + depth--; + if (depth == 0) { + break; + } + } else if (current == 'c' && depth < bestDepth && source.startsWith("catch (", i)) { + bestDepth = depth; + catchIndex = i; + } + i++; + } + assertTrue("Missing source contract token: catch (", catchIndex >= 0); + return blockAt(source, openingBrace(source, catchIndex)); } private static String blockAt(String source, int openBrace) { return source.substring(openBrace, matchingBrace(source, openBrace) + 1); } + private static int openingBrace(String source, int start) { + int i = start; + while (i < source.length()) { + int skipped = skipNonCode(source, i); + if (skipped > i) { + i = skipped; + continue; + } + if (source.charAt(i) == '{') { + return i; + } + i++; + } + throw new IllegalArgumentException("No source block after offset " + start); + } + private static int matchingBrace(String source, int openBrace) { int depth = 0; - boolean quoted = false; - boolean character = false; - boolean escaped = false; - for (int i = openBrace; i < source.length(); i++) { + int i = openBrace; + while (i < source.length()) { + int skipped = skipNonCode(source, i); + if (skipped > i) { + i = skipped; + continue; + } char current = source.charAt(i); - if (escaped) { - escaped = false; - continue; - } - if ((quoted || character) && current == '\\') { - escaped = true; - continue; - } - if (!character && current == '"') { - quoted = !quoted; - continue; - } - if (!quoted && current == '\'') { - character = !character; - continue; - } - if (quoted || character) { - continue; - } if (current == '{') { depth++; } else if (current == '}') { @@ -326,10 +355,54 @@ public class ModdedLifecycleFailureContractTest { return i; } } + i++; } throw new IllegalArgumentException("Unclosed source block"); } + /** + * Advances past a comment, string, char or text block starting at the index, otherwise returns + * the index unchanged. Comments must be skipped: an apostrophe in prose ("generator's") would + * otherwise open a char literal and swallow the rest of the file. + */ + private static int skipNonCode(String source, int index) { + char current = source.charAt(index); + if (current == '/' && index + 1 < source.length()) { + char next = source.charAt(index + 1); + if (next == '/') { + int end = source.indexOf('\n', index); + return end < 0 ? source.length() : end + 1; + } + if (next == '*') { + int end = source.indexOf("*/", index + 2); + return end < 0 ? source.length() : end + 2; + } + return index; + } + if (current == '"' && source.startsWith("\"\"\"", index)) { + int end = source.indexOf("\"\"\"", index + 3); + return end < 0 ? source.length() : end + 3; + } + if (current == '"' || current == '\'') { + return endOfLiteral(source, index, current); + } + return index; + } + + private static int endOfLiteral(String source, int index, char terminator) { + for (int i = index + 1; i < source.length(); i++) { + char current = source.charAt(i); + if (current == '\\') { + i++; + continue; + } + if (current == terminator) { + return i + 1; + } + } + return source.length(); + } + private static int occurrences(String source, String needle) { int count = 0; int offset = 0; diff --git a/core/src/main/java/art/arcane/iris/core/IrisWorlds.java b/core/src/main/java/art/arcane/iris/core/IrisWorlds.java index 48504a39e..0b7dcbd4d 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisWorlds.java +++ b/core/src/main/java/art/arcane/iris/core/IrisWorlds.java @@ -35,6 +35,8 @@ import java.util.Optional; import java.util.stream.Stream; public class IrisWorlds { + private static final String IRIS_GENERATOR_NAME = "iris"; + private static final String IRIS_GENERATOR_PREFIX = "iris:"; private static final AtomicCache cache = new AtomicCache<>(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Type TYPE = TypeToken.getParameterized(KMap.class, String.class, String.class).getType(); @@ -262,16 +264,10 @@ public class IrisWorlds { if (worlds == null) return new KMap<>(); KMap result = new KMap<>(); + String defaultWorldType = IrisSettings.get().getGenerator().getDefaultWorldType(); for (String world : worlds.getKeys(false)) { - String gen = worlds.getString(world + ".generator"); - if (gen == null) continue; - - String loadKey; - if (gen.equalsIgnoreCase("iris")) { - loadKey = IrisSettings.get().getGenerator().getDefaultWorldType(); - } else if (gen.startsWith("Iris:")) { - loadKey = gen.substring(5); - } else continue; + String loadKey = generatorLoadKey(worlds.getString(world + ".generator"), defaultWorldType); + if (loadKey == null) continue; result.put(world, loadKey); } @@ -279,6 +275,22 @@ public class IrisWorlds { return filterBukkitWorldsByStorage(levelRoot, result); } + /** + * Maps a bukkit.yml generator string onto the Iris pack it selects, or null when the string is + * not Iris. Bukkit matches generator plugin names case-insensitively, so the prefix does too. + */ + static String generatorLoadKey(String generator, String defaultWorldType) { + if (generator == null) return null; + if (generator.equalsIgnoreCase(IRIS_GENERATOR_NAME)) return defaultWorldType; + + int prefixLength = IRIS_GENERATOR_PREFIX.length(); + if (generator.length() > prefixLength + && generator.regionMatches(true, 0, IRIS_GENERATOR_PREFIX, 0, prefixLength)) { + return generator.substring(prefixLength); + } + return null; + } + private Optional packRoot(String worldIdentity) { NamespacedKey worldKey = WorldIdentity.parse(worldIdentity); Path worldContainer = levelRoot.getParent(); diff --git a/core/src/main/resources/languages/de_DE.json b/core/src/main/resources/languages/de_DE.json index 8f5a91095..e6f32e4a3 100644 --- a/core/src/main/resources/languages/de_DE.json +++ b/core/src/main/resources/languages/de_DE.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Dieser Befehl kann von diesem Ursprung aus nicht ausgeführt werden.", "director.runtime.error.unknown_parameter": "Unbekannter Parameterschlüssel: {key}", "director.runtime.error.unexpected_argument": "Unerwartetes Argument \"{argument}\". Optionale Parameter müssen als Schlüssel angegeben werden, z. B. seed=123", + "director.runtime.error.unclosed_group": "Fehlende schließende \"]\" für Parameter {key}", "director.runtime.error.conversion_failed": "\"{value}\" kann für {parameter} nicht in {type} konvertiert werden", "director.runtime.error.default_parse_failed": "Der Standardwert für Parameter {parameter} kann nicht gelesen werden", "director.runtime.error.missing_argument": "Fehlendes Argument \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/es_ES.json b/core/src/main/resources/languages/es_ES.json index 7983cbaa7..5aead5475 100644 --- a/core/src/main/resources/languages/es_ES.json +++ b/core/src/main/resources/languages/es_ES.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Este comando no se puede ejecutar desde este origen.", "director.runtime.error.unknown_parameter": "Clave de parámetro desconocida: {key}", "director.runtime.error.unexpected_argument": "Argumento inesperado \"{argument}\". Los parámetros opcionales deben especificarse con clave, p. ej. seed=123", + "director.runtime.error.unclosed_group": "Falta el corchete de cierre \"]\" para el parámetro {key}", "director.runtime.error.conversion_failed": "No se puede convertir \"{value}\" a {type} para {parameter}", "director.runtime.error.default_parse_failed": "No se puede analizar el valor predeterminado del parámetro {parameter}", "director.runtime.error.missing_argument": "Falta el argumento \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/fi_FI.json b/core/src/main/resources/languages/fi_FI.json index 4b9577a0a..e009ab8a5 100644 --- a/core/src/main/resources/languages/fi_FI.json +++ b/core/src/main/resources/languages/fi_FI.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Tätä komentoa ei voi suorittaa tästä lähteestä.", "director.runtime.error.unknown_parameter": "Tuntematon parametriavain: {key}", "director.runtime.error.unexpected_argument": "Odottamaton argumentti \"{argument}\". Valinnaiset parametrit on annettava avaimella, esim. seed=123", + "director.runtime.error.unclosed_group": "Parametrilta {key} puuttuu sulkeva \"]\"", "director.runtime.error.conversion_failed": "Arvoa \"{value}\" ei voi muuntaa tyypiksi {type} parametrille {parameter}", "director.runtime.error.default_parse_failed": "Parametrin {parameter} oletusarvoa ei voi jäsentää", "director.runtime.error.missing_argument": "Puuttuva argumentti \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/fr_FR.json b/core/src/main/resources/languages/fr_FR.json index 7dab73106..ebf7508a3 100644 --- a/core/src/main/resources/languages/fr_FR.json +++ b/core/src/main/resources/languages/fr_FR.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Cette commande ne peut pas être exécutée depuis cette origine.", "director.runtime.error.unknown_parameter": "Clé de paramètre inconnue : {key}", "director.runtime.error.unexpected_argument": "Argument inattendu \"{argument}\". Les paramètres facultatifs doivent être nommés, par ex. seed=123", + "director.runtime.error.unclosed_group": "Crochet fermant \"]\" manquant pour le paramètre {key}", "director.runtime.error.conversion_failed": "Impossible de convertir \"{value}\" en {type} pour {parameter}", "director.runtime.error.default_parse_failed": "Impossible d'analyser la valeur par défaut du paramètre {parameter}", "director.runtime.error.missing_argument": "Argument manquant \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/he_IL.json b/core/src/main/resources/languages/he_IL.json index ddde4e232..6c9688837 100644 --- a/core/src/main/resources/languages/he_IL.json +++ b/core/src/main/resources/languages/he_IL.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "לא ניתן להריץ פקודה זו ממקור זה.", "director.runtime.error.unknown_parameter": "מפתח פרמטר לא ידוע: {key}", "director.runtime.error.unexpected_argument": "ארגומנט לא צפוי \"{argument}\". פרמטרים אופציונליים חייבים להינתן עם מפתח, לדוגמה seed=123", + "director.runtime.error.unclosed_group": "חסר סוגר מרובע סוגר \"]\" עבור הפרמטר {key}", "director.runtime.error.conversion_failed": "לא ניתן להמיר את \"{value}\" ל-{type} עבור {parameter}", "director.runtime.error.default_parse_failed": "לא ניתן לנתח את ערך ברירת המחדל של הפרמטר {parameter}", "director.runtime.error.missing_argument": "חסר ארגומנט \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/it_IT.json b/core/src/main/resources/languages/it_IT.json index 07a354a0c..e06719449 100644 --- a/core/src/main/resources/languages/it_IT.json +++ b/core/src/main/resources/languages/it_IT.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Questo comando non può essere eseguito da questa origine.", "director.runtime.error.unknown_parameter": "Chiave di parametro sconosciuta: {key}", "director.runtime.error.unexpected_argument": "Argomento imprevisto \"{argument}\". I parametri opzionali devono essere specificati con una chiave, ad es. seed=123", + "director.runtime.error.unclosed_group": "Manca la parentesi quadra di chiusura \"]\" per il parametro {key}", "director.runtime.error.conversion_failed": "Impossibile convertire \"{value}\" in {type} per {parameter}", "director.runtime.error.default_parse_failed": "Impossibile analizzare il valore predefinito del parametro {parameter}", "director.runtime.error.missing_argument": "Argomento mancante \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/ja-JP.json b/core/src/main/resources/languages/ja-JP.json index e842a357b..57a842e32 100644 --- a/core/src/main/resources/languages/ja-JP.json +++ b/core/src/main/resources/languages/ja-JP.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "この実行元からはコマンドを実行できません。", "director.runtime.error.unknown_parameter": "不明なパラメーターキー: {key}", "director.runtime.error.unexpected_argument": "予期しない引数 \"{argument}\"。オプションのパラメーターはキー付きで指定してください。例: seed=123", + "director.runtime.error.unclosed_group": "パラメーター {key} の閉じ角括弧 \"]\" がありません", "director.runtime.error.conversion_failed": "\"{value}\" を {parameter} 用の {type} に変換できません", "director.runtime.error.default_parse_failed": "パラメーター {parameter} のデフォルト値を解析できません", "director.runtime.error.missing_argument": "引数 \"{parameter}\" ({type}) が必要です", diff --git a/core/src/main/resources/languages/ko_KR.json b/core/src/main/resources/languages/ko_KR.json index 6f2c3ae7d..b7558331c 100644 --- a/core/src/main/resources/languages/ko_KR.json +++ b/core/src/main/resources/languages/ko_KR.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "이 실행 위치에서는 이 명령어를 실행할 수 없습니다.", "director.runtime.error.unknown_parameter": "알 수 없는 매개변수 키: {key}", "director.runtime.error.unexpected_argument": "예상하지 못한 인수 \"{argument}\". 선택적 매개변수는 키와 함께 지정해야 합니다. 예: seed=123", + "director.runtime.error.unclosed_group": "매개변수 {key}에 닫는 대괄호 \"]\"가 없습니다", "director.runtime.error.conversion_failed": "\"{value}\" 값을 {parameter}용 {type} 형식으로 변환할 수 없습니다", "director.runtime.error.default_parse_failed": "매개변수 {parameter}의 기본값을 분석할 수 없습니다", "director.runtime.error.missing_argument": "인수 \"{parameter}\" ({type})가 필요합니다", diff --git a/core/src/main/resources/languages/lt_LT.json b/core/src/main/resources/languages/lt_LT.json index 2796b75d3..44d5798fd 100644 --- a/core/src/main/resources/languages/lt_LT.json +++ b/core/src/main/resources/languages/lt_LT.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Šios komandos negalima vykdyti iš šio šaltinio.", "director.runtime.error.unknown_parameter": "Nežinomas parametro raktas: {key}", "director.runtime.error.unexpected_argument": "Netikėtas argumentas \"{argument}\". Pasirenkami parametrai turi būti nurodyti su raktu, pvz. seed=123", + "director.runtime.error.unclosed_group": "Parametrui {key} trūksta uždaromojo skliausto \"]\"", "director.runtime.error.conversion_failed": "Reikšmės \"{value}\" nepavyko konvertuoti į {type} parametrui {parameter}", "director.runtime.error.default_parse_failed": "Nepavyko nuskaityti parametro {parameter} numatytosios reikšmės", "director.runtime.error.missing_argument": "Trūksta argumento \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/nl_NL.json b/core/src/main/resources/languages/nl_NL.json index ef9146cc3..5a45f9e8b 100644 --- a/core/src/main/resources/languages/nl_NL.json +++ b/core/src/main/resources/languages/nl_NL.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Deze opdracht kan niet vanuit deze bron worden uitgevoerd.", "director.runtime.error.unknown_parameter": "Onbekende parametersleutel: {key}", "director.runtime.error.unexpected_argument": "Onverwacht argument \"{argument}\". Optionele parameters moeten met een sleutel worden opgegeven, bijv. seed=123", + "director.runtime.error.unclosed_group": "Ontbrekend afsluitend haakje \"]\" voor parameter {key}", "director.runtime.error.conversion_failed": "Kan \"{value}\" voor {parameter} niet naar {type} converteren", "director.runtime.error.default_parse_failed": "Kan de standaardwaarde voor parameter {parameter} niet verwerken", "director.runtime.error.missing_argument": "Ontbrekend argument \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/pl_PL.json b/core/src/main/resources/languages/pl_PL.json index 64426952d..d3cd67754 100644 --- a/core/src/main/resources/languages/pl_PL.json +++ b/core/src/main/resources/languages/pl_PL.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Tego polecenia nie można wykonać z tego źródła.", "director.runtime.error.unknown_parameter": "Nieznany klucz parametru: {key}", "director.runtime.error.unexpected_argument": "Nieoczekiwany argument \"{argument}\". Parametry opcjonalne muszą mieć klucz, np. seed=123", + "director.runtime.error.unclosed_group": "Brak zamykającego nawiasu \"]\" dla parametru {key}", "director.runtime.error.conversion_failed": "Nie można przekonwertować \"{value}\" na {type} dla {parameter}", "director.runtime.error.default_parse_failed": "Nie można odczytać wartości domyślnej parametru {parameter}", "director.runtime.error.missing_argument": "Brak argumentu \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/pt_PT.json b/core/src/main/resources/languages/pt_PT.json index 92414fe6f..bfe6b1616 100644 --- a/core/src/main/resources/languages/pt_PT.json +++ b/core/src/main/resources/languages/pt_PT.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Este comando não pode ser executado a partir desta origem.", "director.runtime.error.unknown_parameter": "Chave de parâmetro desconhecida: {key}", "director.runtime.error.unexpected_argument": "Argumento inesperado \"{argument}\". Os parâmetros opcionais devem ser indicados por chave, por ex. seed=123", + "director.runtime.error.unclosed_group": "Falta o parêntese reto de fecho \"]\" para o parâmetro {key}", "director.runtime.error.conversion_failed": "Não foi possível converter \"{value}\" em {type} para {parameter}", "director.runtime.error.default_parse_failed": "Não foi possível interpretar o valor predefinido do parâmetro {parameter}", "director.runtime.error.missing_argument": "Falta o argumento \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/ru_RU.json b/core/src/main/resources/languages/ru_RU.json index 2f680468f..c33605e23 100644 --- a/core/src/main/resources/languages/ru_RU.json +++ b/core/src/main/resources/languages/ru_RU.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Эту команду нельзя выполнить из данного источника.", "director.runtime.error.unknown_parameter": "Неизвестный ключ параметра: {key}", "director.runtime.error.unexpected_argument": "Неожиданный аргумент \"{argument}\". Необязательные параметры должны задаваться с ключом, например seed=123", + "director.runtime.error.unclosed_group": "Отсутствует закрывающая скобка \"]\" для параметра {key}", "director.runtime.error.conversion_failed": "Невозможно преобразовать \"{value}\" в {type} для {parameter}", "director.runtime.error.default_parse_failed": "Невозможно разобрать значение по умолчанию параметра {parameter}", "director.runtime.error.missing_argument": "Отсутствует аргумент \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/tr_TR.json b/core/src/main/resources/languages/tr_TR.json index 2c7fe0b50..723c3677b 100644 --- a/core/src/main/resources/languages/tr_TR.json +++ b/core/src/main/resources/languages/tr_TR.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Bu komut bu kaynaktan çalıştırılamaz.", "director.runtime.error.unknown_parameter": "Bilinmeyen parametre anahtarı: {key}", "director.runtime.error.unexpected_argument": "Beklenmeyen bağımsız değişken \"{argument}\". İsteğe bağlı parametreler anahtarla belirtilmelidir, ör. seed=123", + "director.runtime.error.unclosed_group": "{key} parametresi için kapanış köşeli parantezi \"]\" eksik", "director.runtime.error.conversion_failed": "\"{value}\", {parameter} için {type} türüne dönüştürülemiyor", "director.runtime.error.default_parse_failed": "{parameter} parametresinin varsayılan değeri ayrıştırılamıyor", "director.runtime.error.missing_argument": "Eksik bağımsız değişken \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/vi_VI.json b/core/src/main/resources/languages/vi_VI.json index bcd8cdd30..3d2ffa85d 100644 --- a/core/src/main/resources/languages/vi_VI.json +++ b/core/src/main/resources/languages/vi_VI.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "Không thể chạy lệnh này từ nguồn này.", "director.runtime.error.unknown_parameter": "Khóa tham số không xác định: {key}", "director.runtime.error.unexpected_argument": "Đối số không mong đợi \"{argument}\". Các tham số tùy chọn phải được chỉ định bằng khóa, ví dụ seed=123", + "director.runtime.error.unclosed_group": "Thiếu dấu ngoặc vuông đóng \"]\" cho tham số {key}", "director.runtime.error.conversion_failed": "Không thể chuyển \"{value}\" thành {type} cho {parameter}", "director.runtime.error.default_parse_failed": "Không thể phân tích giá trị mặc định của tham số {parameter}", "director.runtime.error.missing_argument": "Thiếu đối số \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/zh_CN.json b/core/src/main/resources/languages/zh_CN.json index 8df60edaf..47a7b0f87 100644 --- a/core/src/main/resources/languages/zh_CN.json +++ b/core/src/main/resources/languages/zh_CN.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "无法从此来源运行该命令。", "director.runtime.error.unknown_parameter": "未知参数键: {key}", "director.runtime.error.unexpected_argument": "意外参数 \"{argument}\"。可选参数必须使用键指定,例如 seed=123", + "director.runtime.error.unclosed_group": "参数 {key} 缺少右方括号 \"]\"", "director.runtime.error.conversion_failed": "无法将 \"{value}\" 转换为 {parameter} 所需的 {type}", "director.runtime.error.default_parse_failed": "无法解析参数 {parameter} 的默认值", "director.runtime.error.missing_argument": "缺少参数 \"{parameter}\" ({type})", diff --git a/core/src/main/resources/languages/zh_TW.json b/core/src/main/resources/languages/zh_TW.json index 25ec75286..6524911db 100644 --- a/core/src/main/resources/languages/zh_TW.json +++ b/core/src/main/resources/languages/zh_TW.json @@ -19,6 +19,7 @@ "director.runtime.error.invalid_origin": "無法從此來源執行該指令。", "director.runtime.error.unknown_parameter": "未知參數鍵: {key}", "director.runtime.error.unexpected_argument": "意外參數 \"{argument}\"。選用參數必須使用鍵指定,例如 seed=123", + "director.runtime.error.unclosed_group": "參數 {key} 缺少右方括號 \"]\"", "director.runtime.error.conversion_failed": "無法將 \"{value}\" 轉換為 {parameter} 所需的 {type}", "director.runtime.error.default_parse_failed": "無法解析參數 {parameter} 的預設值", "director.runtime.error.missing_argument": "缺少參數 \"{parameter}\" ({type})", diff --git a/core/src/test/java/art/arcane/iris/core/IrisWorldsTest.java b/core/src/test/java/art/arcane/iris/core/IrisWorldsTest.java index f1442bec6..32e88c8c0 100644 --- a/core/src/test/java/art/arcane/iris/core/IrisWorldsTest.java +++ b/core/src/test/java/art/arcane/iris/core/IrisWorldsTest.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; public class IrisWorldsTest { @Rule @@ -53,6 +54,18 @@ public class IrisWorldsTest { assertEquals(Set.of("archive_iris_foreign"), other.keySet()); } + @Test + public void bukkitGeneratorStringsAreMatchedCaseInsensitively() { + assertEquals("pack", IrisWorlds.generatorLoadKey("Iris:pack", "overworld")); + assertEquals("pack", IrisWorlds.generatorLoadKey("iris:pack", "overworld")); + assertEquals("pack", IrisWorlds.generatorLoadKey("IRIS:pack", "overworld")); + assertEquals("overworld", IrisWorlds.generatorLoadKey("Iris", "overworld")); + assertEquals("overworld", IrisWorlds.generatorLoadKey("iris", "overworld")); + assertNull(IrisWorlds.generatorLoadKey("VoidGen", "overworld")); + assertNull(IrisWorlds.generatorLoadKey("Irissy:pack", "overworld")); + assertNull(IrisWorlds.generatorLoadKey(null, "overworld")); + } + @Test public void bukkitWorldFilteringRecognizesCurrentCraftBukkitConfiguredStorage() throws Exception { Path worldContainer = temporaryFolder.newFolder("configured-server").toPath();