This commit is contained in:
Brian Neumann-Fopiano committed 2026-08-15 21:32:00 -04:00
1 parent ef2e1b8f58
commit fc0fdf4ce4
81 files changed
+5109 -511

No files matched your search

@@ -255,4 +255,164 @@ public class IrisWorldStorageTest {
assertFalse(IrisWorldStorage.isExistingManagedDimensionRoot(levelRoot, file));
assertTrue(IrisWorldStorage.isExistingManagedDimensionRoot(levelRoot, directory));
}
@Test
public void persistentDimensionRootAllowsOnlyManagedAndExactVanillaSlots() throws Exception {
File levelRoot = temporaryFolder.newFolder("persistent-root");
assertEquals(
IrisWorldStorage.dimensionRoot(levelRoot, new NamespacedKey("iris", "moon")).getCanonicalFile(),
IrisWorldStorage.requireSafePersistentDimensionRoot(
levelRoot,
new NamespacedKey("iris", "moon")
)
);
assertEquals(
IrisWorldStorage.dimensionRoot(levelRoot, NamespacedKey.minecraft("overworld")).getCanonicalFile(),
IrisWorldStorage.requireSafePersistentDimensionRoot(
levelRoot,
NamespacedKey.minecraft("overworld")
)
);
assertEquals(
IrisWorldStorage.dimensionRoot(levelRoot, NamespacedKey.minecraft("the_nether")).getCanonicalFile(),
IrisWorldStorage.requireSafePersistentDimensionRoot(
levelRoot,
NamespacedKey.minecraft("the_nether")
)
);
assertEquals(
IrisWorldStorage.dimensionRoot(levelRoot, NamespacedKey.minecraft("the_end")).getCanonicalFile(),
IrisWorldStorage.requireSafePersistentDimensionRoot(
levelRoot,
NamespacedKey.minecraft("the_end")
)
);
assertThrows(
ExactWorldSlotPathPolicy.Rejection.class,
() -> IrisWorldStorage.requireSafePersistentDimensionRoot(
levelRoot,
NamespacedKey.minecraft("custom")
)
);
assertThrows(
ExactWorldSlotPathPolicy.Rejection.class,
() -> IrisWorldStorage.requireSafePersistentDimensionRoot(
levelRoot,
new NamespacedKey("foreign", "moon")
)
);
}
@Test
public void frozenDimensionRootUsesCanonicalLevelStorageWhenPresent() throws Exception {
File worldContainer = temporaryFolder.newFolder("server-direct");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
NamespacedKey worldKey = new NamespacedKey("iris", "moon");
File dimensionRoot = IrisWorldStorage.dimensionRoot(levelRoot, worldKey);
Files.createDirectories(dimensionRoot.toPath());
assertEquals(
dimensionRoot,
IrisWorldStorage.requireFrozenDimensionRoot(
worldContainer,
levelRoot,
"world_iris_moon",
worldKey
)
);
}
@Test
public void frozenDimensionRootUsesCurrentCraftBukkitConfiguredStorage() throws Exception {
File worldContainer = temporaryFolder.newFolder("server-configured");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
NamespacedKey worldKey = new NamespacedKey("iris", "moon");
File configuredLevelRoot = new File(worldContainer, "world_iris_moon");
File configuredDimensionRoot = IrisWorldStorage.dimensionRoot(configuredLevelRoot, worldKey);
Files.createDirectories(configuredDimensionRoot.toPath());
assertEquals(
configuredDimensionRoot,
IrisWorldStorage.requireFrozenDimensionRoot(
worldContainer,
levelRoot,
"world_iris_moon",
worldKey
)
);
}
@Test
public void frozenDimensionRootRejectsMissingAndAmbiguousStorage() throws Exception {
File worldContainer = temporaryFolder.newFolder("server-ambiguous");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
NamespacedKey worldKey = new NamespacedKey("iris", "moon");
assertThrows(
IllegalStateException.class,
() -> IrisWorldStorage.requireFrozenDimensionRoot(
worldContainer,
levelRoot,
"world_iris_moon",
worldKey
)
);
Files.createDirectories(IrisWorldStorage.dimensionRoot(levelRoot, worldKey).toPath());
File configuredLevelRoot = new File(worldContainer, "world_iris_moon");
Files.createDirectories(IrisWorldStorage.dimensionRoot(configuredLevelRoot, worldKey).toPath());
assertThrows(
IllegalStateException.class,
() -> IrisWorldStorage.requireFrozenDimensionRoot(
worldContainer,
levelRoot,
"world_iris_moon",
worldKey
)
);
}
@Test
public void frozenDimensionRootRejectsSymlinkedStorage() throws Exception {
File worldContainer = temporaryFolder.newFolder("server-symlink");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
Path namespaceRoot = Files.createDirectories(levelRoot.toPath().resolve("dimensions"));
Path outside = temporaryFolder.newFolder("frozen-outside").toPath();
Files.createSymbolicLink(namespaceRoot.resolve("iris"), outside);
assertThrows(
IllegalStateException.class,
() -> IrisWorldStorage.requireFrozenDimensionRoot(
worldContainer,
levelRoot,
"world_iris_moon",
new NamespacedKey("iris", "moon")
)
);
}
@Test
public void frozenPackRootRequiresRealWorldLocalSnapshot() throws Exception {
File dimensionRoot = temporaryFolder.newFolder("frozen-pack-world");
assertThrows(
IllegalStateException.class,
() -> IrisWorldStorage.requireFrozenPackRoot(dimensionRoot)
);
Path irisRoot = Files.createDirectory(dimensionRoot.toPath().resolve("iris"));
Path externalPack = temporaryFolder.newFolder("external-pack").toPath();
Files.createSymbolicLink(irisRoot.resolve("pack"), externalPack);
assertThrows(
IllegalStateException.class,
() -> IrisWorldStorage.requireFrozenPackRoot(dimensionRoot)
);
Files.delete(irisRoot.resolve("pack"));
Path packRoot = Files.createDirectory(irisRoot.resolve("pack"));
assertEquals(packRoot.toFile(), IrisWorldStorage.requireFrozenPackRoot(dimensionRoot));
}
}
@@ -52,4 +52,20 @@ public class IrisWorldsTest {
assertEquals(Set.of("world", "world_iris_moon"), selected.keySet());
assertEquals(Set.of("archive_iris_foreign"), other.keySet());
}
@Test
public void bukkitWorldFilteringRecognizesCurrentCraftBukkitConfiguredStorage() throws Exception {
Path worldContainer = temporaryFolder.newFolder("configured-server").toPath();
Path levelRoot = Files.createDirectory(worldContainer.resolve("world"));
Files.createDirectories(
worldContainer.resolve("world_iris_moon/dimensions/iris/moon")
);
Map<String, String> selected = IrisWorlds.filterBukkitWorldsByStorage(
levelRoot,
Map.of("world_iris_moon", "overworld")
);
assertEquals(Map.of("world_iris_moon", "overworld"), selected);
}
}
@@ -21,11 +21,23 @@ public class WorldCreatorCompatTest {
assertEquals("world_the_end", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("the_end"), "world"));
}
@Test
public void persistentFallbackUsesExactConfiguredStartupName() {
assertEquals(
"world_iris_compat_world",
WorldCreatorCompat.fallbackPersistentName(new NamespacedKey("iris", "compat_world"), "world")
);
}
@Test
public void fallbackKeyRoundTripsCreatorName() {
assertEquals(new NamespacedKey("iris", "compat_world"), WorldCreatorCompat.fallbackKey("compat_world", "world"));
assertEquals(NamespacedKey.minecraft("overworld"), WorldCreatorCompat.fallbackKey("world", "world"));
assertEquals(NamespacedKey.minecraft("the_nether"), WorldCreatorCompat.fallbackKey("world_nether", "world"));
assertEquals(
new NamespacedKey("iris", "compat_world"),
WorldCreatorCompat.fallbackKey("world_iris_compat_world", "world")
);
}
@Test
@@ -112,6 +112,23 @@ public class BukkitWorldConfigurationTest {
)), bindings);
}
@Test
public void admitsCurrentCraftBukkitConfiguredWorldStorage() throws Exception {
Path worldContainer = temporaryFolder.newFolder("configured-binding-server").toPath();
File configuration = worldContainer.resolve("bukkit.yml").toFile();
Path levelRoot = Files.createDirectory(worldContainer.resolve("world"));
Files.createDirectories(worldContainer.resolve("world_iris_moon/dimensions/iris/moon"));
YamlConfiguration yaml = new YamlConfiguration();
yaml.set("worlds.world_iris_moon.generator", "Iris:overworld");
yaml.save(configuration);
assertEquals(List.of(new BukkitWorldConfiguration.IrisGeneratorBinding(
"world_iris_moon",
new WorldSlotKey("iris", "moon"),
"overworld"
)), BukkitWorldConfiguration.readIrisGeneratorBindings(configuration, "world", levelRoot));
}
@Test
public void excludesSymlinkedCustomWorldTarget() throws Exception {
File configuration = temporaryFolder.newFile("symlink-admission-bukkit.yml");
@@ -30,6 +30,18 @@ public class WorldLifecycleStagingTest {
assertSame(generator, WorldLifecycleStaging.consumeStemGenerator("world"));
}
@Test
public void stagedStemGeneratorCanBeInspectedWithoutConsumption() {
ChunkGenerator generator = mock(ChunkGenerator.class);
WorldLifecycleStaging.stageStemGenerator("world", generator);
assertSame(generator, WorldLifecycleStaging.peekStemGenerator("world"));
assertSame(generator, WorldLifecycleStaging.peekStemGenerator("world"));
assertSame(generator, WorldLifecycleStaging.consumeStemGenerator("world"));
assertNull(WorldLifecycleStaging.peekStemGenerator("world"));
}
@Test
public void stagedStemGeneratorCannotBeConsumedByDifferentWorldName() {
ChunkGenerator generator = mock(ChunkGenerator.class);
@@ -28,6 +28,28 @@ public class WorldRemovalPathPolicyTest {
levelRoot.resolve("dimensions/iris/iris_world").toAbsolutePath().normalize(),
target.worldDirectory()
);
assertEquals(target.worldDirectory(), target.storageDirectory());
}
@Test
public void resolvesCurrentCraftBukkitConfiguredDimensionDirectory() throws Exception {
Path worldContainer = temporaryFolder.newFolder("configured-removal-server").toPath();
Path levelRoot = Files.createDirectory(worldContainer.resolve("world"));
Path dimensionRoot = Files.createDirectories(
worldContainer.resolve("world_iris_moon/dimensions/iris/moon")
);
WorldRemovalPathPolicy.Target target = WorldRemovalPathPolicy.resolve("moon", "world", levelRoot);
assertEquals(dimensionRoot.toAbsolutePath().normalize(), target.worldDirectory());
assertEquals(worldContainer.resolve("world_iris_moon").toAbsolutePath().normalize(),
target.storageDirectory());
WorldRemovalPathPolicy.validateStoragePath(levelRoot, target.worldKey(), dimensionRoot);
WorldRemovalPathPolicy.validateStorageRoot(
levelRoot,
target.worldKey(),
worldContainer.resolve("world_iris_moon")
);
}
@Test
@@ -320,6 +320,7 @@ public class WorldReplacementBootstrapTest {
Path overworldDimension = overworldPaths.stage().resolve("iris/pack/dimensions/overworld.json");
Files.createDirectories(overworldDimension.getParent());
Files.writeString(overworldDimension, "overworld-replacement");
WorldReplacementEntryGuard.stage(levelRoot, overworldPaths.stage(), overworldId);
String overworldFingerprint = WorldReplacementFilesystem.fingerprintPack(
overworldPaths.stage().resolve("iris/pack")
);
@@ -0,0 +1,109 @@
package art.arcane.iris.core.lifecycle;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class WorldReplacementEntryGuardTest {
private static final UUID TRANSACTION_ID = UUID.fromString("00000000-0000-0000-0000-000000000001");
private static final UUID OTHER_TRANSACTION_ID = UUID.fromString("00000000-0000-0000-0000-000000000002");
private static final UUID FIRST_PLAYER = UUID.fromString("10000000-0000-0000-0000-000000000001");
private static final UUID SECOND_PLAYER = UUID.fromString("20000000-0000-0000-0000-000000000002");
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void stagesAndRefreshesCurrentPlayerReceipts() throws Exception {
Path levelRoot = temporaryFolder.newFolder("level-refresh").toPath();
Path stagedWorld = temporaryFolder.newFolder("stage-refresh").toPath();
writePlayer(levelRoot, FIRST_PLAYER, ".dat");
writePlayer(levelRoot, SECOND_PLAYER, ".dat_old");
Files.writeString(levelRoot.resolve("players/data/.DS_Store"), "ignored");
WorldReplacementEntryGuard.Entry staged = WorldReplacementEntryGuard.stage(
levelRoot,
stagedWorld,
TRANSACTION_ID
);
assertEquals(Set.of(FIRST_PLAYER), staged.pendingPlayers());
writePlayer(levelRoot, SECOND_PLAYER, ".dat");
WorldReplacementEntryGuard.Entry refreshed = WorldReplacementEntryGuard.refreshPlayers(
levelRoot,
stagedWorld,
TRANSACTION_ID
);
assertEquals(Set.of(FIRST_PLAYER, SECOND_PLAYER), refreshed.pendingPlayers());
assertEquals(refreshed, WorldReplacementEntryGuard.load(stagedWorld).orElseThrow());
}
@Test
public void keepsFinalReceiptUntilSafeSpawnAllowsMarkerRetirement() throws Exception {
Path levelRoot = temporaryFolder.newFolder("level-retire").toPath();
Path stagedWorld = temporaryFolder.newFolder("stage-retire").toPath();
writePlayer(levelRoot, FIRST_PLAYER, ".dat");
WorldReplacementEntryGuard.stage(levelRoot, stagedWorld, TRANSACTION_ID);
assertFalse(WorldReplacementEntryGuard.retireIfEmpty(stagedWorld, TRANSACTION_ID));
Optional<WorldReplacementEntryGuard.Entry> completed = WorldReplacementEntryGuard.completePlayer(
stagedWorld,
TRANSACTION_ID,
FIRST_PLAYER
);
assertTrue(completed.isPresent());
assertTrue(completed.orElseThrow().pendingPlayers().isEmpty());
assertTrue(Files.isRegularFile(marker(stagedWorld)));
assertTrue(WorldReplacementEntryGuard.retireIfEmpty(stagedWorld, TRANSACTION_ID));
assertFalse(Files.exists(marker(stagedWorld)));
}
@Test
public void rejectsAReceiptFromAnotherTransactionWithoutChangingTheMarker() throws Exception {
Path levelRoot = temporaryFolder.newFolder("level-mismatch").toPath();
Path stagedWorld = temporaryFolder.newFolder("stage-mismatch").toPath();
writePlayer(levelRoot, FIRST_PLAYER, ".dat");
WorldReplacementEntryGuard.stage(levelRoot, stagedWorld, TRANSACTION_ID);
IOException failure = assertThrows(
IOException.class,
() -> WorldReplacementEntryGuard.completePlayer(
stagedWorld,
OTHER_TRANSACTION_ID,
FIRST_PLAYER
)
);
assertTrue(failure.getMessage().contains("another transaction"));
assertEquals(
Set.of(FIRST_PLAYER),
WorldReplacementEntryGuard.load(stagedWorld).orElseThrow().pendingPlayers()
);
}
private static void writePlayer(Path levelRoot, UUID playerId, String suffix) throws IOException {
Path playerData = levelRoot.resolve("players/data");
Files.createDirectories(playerData);
Files.writeString(playerData.resolve(playerId + suffix), "player");
}
private static Path marker(Path worldDirectory) {
return worldDirectory.resolve("iris").resolve(WorldReplacementEntryGuard.MARKER_NAME);
}
}
@@ -0,0 +1,254 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.core.pregenerator;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class MantleHeapPressureTest {
private static final MantleHeapPressure.PanicGcPolicy POLICY =
new MantleHeapPressure.PanicGcPolicy(10_000L, 60_000L, 60_000L, 240_000L);
@Test
public void normalPressureNeverRequestsEitherGcPath() {
ReclaimerFixture fixture = new ReclaimerFixture(0);
fixture.reclaimer.request(0.91D);
fixture.clock.addAndGet(120_000L);
fixture.reclaimer.request(0.91D);
assertEquals(0, fixture.explicitCalls.get());
assertEquals(0, fixture.diagnosticCalls.get());
}
@Test
public void sustainedPanicInvokesDiagnosticOnlyAfterNormalReclaimGrace() {
ReclaimerFixture fixture = new ReclaimerFixture(0);
fixture.reclaimer.request(0.97D);
fixture.clock.set(9_999L);
fixture.reclaimer.request(0.97D);
fixture.clock.set(10_000L);
fixture.reclaimer.request(0.97D);
fixture.clock.set(69_999L);
fixture.reclaimer.request(0.99D);
assertEquals(1, fixture.explicitCalls.get());
assertEquals(1, fixture.diagnosticCalls.get());
assertEquals(List.of(0.97D), fixture.diagnosticFractions);
}
@Test
public void recoveredEpisodeResetsButDiagnosticCooldownStillApplies() {
ReclaimerFixture fixture = new ReclaimerFixture(0);
fixture.reclaimer.request(0.97D);
fixture.clock.set(10_000L);
fixture.reclaimer.request(0.97D);
fixture.reclaimer.resetEpisode();
fixture.clock.set(20_000L);
fixture.reclaimer.request(0.97D);
fixture.clock.set(30_000L);
fixture.reclaimer.request(0.97D);
fixture.clock.set(70_000L);
fixture.reclaimer.request(0.97D);
assertEquals(2, fixture.explicitCalls.get());
assertEquals(2, fixture.diagnosticCalls.get());
}
@Test
public void highWaterEpisodeDiagnosesAfterNormalReclaimLeavesHeapBelowHighWater() {
ReclaimerFixture fixture = new ReclaimerFixture(0);
fixture.reclaimer.request(0.93D);
fixture.clock.set(9_999L);
fixture.reclaimer.request(0.91D);
fixture.clock.set(10_000L);
fixture.reclaimer.request(0.91D);
assertEquals(1, fixture.explicitCalls.get());
assertEquals(1, fixture.diagnosticCalls.get());
assertEquals(List.of(0.91D), fixture.diagnosticFractions);
}
@Test
public void successfulDiagnosticRetriesAfterCooldownWhenPressureRemainsHigh() {
ReclaimerFixture fixture = new ReclaimerFixture(0);
fixture.reclaimer.request(0.97D);
fixture.clock.set(10_000L);
fixture.reclaimer.request(0.91D);
fixture.clock.set(69_999L);
fixture.reclaimer.request(0.91D);
fixture.clock.set(70_000L);
fixture.reclaimer.request(0.91D);
assertEquals(1, fixture.explicitCalls.get());
assertEquals(2, fixture.diagnosticCalls.get());
assertEquals(List.of(0.91D, 0.91D), fixture.diagnosticFractions);
}
@Test
public void failedDiagnosticRetriesAfterBackoffWithinSameEpisode() {
ReclaimerFixture fixture = new ReclaimerFixture(1);
fixture.reclaimer.request(0.97D);
fixture.clock.set(10_000L);
fixture.reclaimer.request(0.91D);
fixture.clock.set(69_999L);
fixture.reclaimer.request(0.91D);
fixture.clock.set(70_000L);
fixture.reclaimer.request(0.91D);
assertEquals(1, fixture.explicitCalls.get());
assertEquals(2, fixture.diagnosticCalls.get());
assertEquals(1, fixture.failures.size());
}
@Test
public void sustainedSubHighPressureReleasesAfterBoundedHysteresis() {
AtomicLong clock = new AtomicLong(1_000L);
AtomicInteger releases = new AtomicInteger();
MantleHeapPressure.HeapPressureGate gate = new MantleHeapPressure.HeapPressureGate(
0.92D,
0.82D,
60_000L,
clock::get,
releases::incrementAndGet);
assertEquals(true, gate.update(0.93D));
assertEquals(true, gate.update(0.89D));
clock.set(60_999L);
assertEquals(true, gate.update(0.89D));
clock.set(61_000L);
assertEquals(false, gate.update(0.89D));
assertEquals(1, releases.get());
assertEquals(false, gate.update(0.89D));
}
@Test
public void renewedHighPressureRestartsHysteresisAndCanReengageAfterRelease() {
AtomicLong clock = new AtomicLong(5_000L);
AtomicInteger releases = new AtomicInteger();
MantleHeapPressure.HeapPressureGate gate = new MantleHeapPressure.HeapPressureGate(
0.92D,
0.82D,
60_000L,
clock::get,
releases::incrementAndGet);
assertEquals(true, gate.update(0.93D));
assertEquals(true, gate.update(0.88D));
clock.set(64_999L);
assertEquals(true, gate.update(0.93D));
assertEquals(true, gate.update(0.88D));
clock.set(124_998L);
assertEquals(true, gate.update(0.88D));
clock.set(124_999L);
assertEquals(false, gate.update(0.88D));
assertEquals(1, releases.get());
assertEquals(true, gate.update(0.92D));
}
@Test
public void lowWaterReleasesImmediately() {
AtomicLong clock = new AtomicLong();
AtomicInteger releases = new AtomicInteger();
MantleHeapPressure.HeapPressureGate gate = new MantleHeapPressure.HeapPressureGate(
0.92D,
0.82D,
60_000L,
clock::get,
releases::incrementAndGet);
assertEquals(true, gate.update(0.95D));
assertEquals(false, gate.update(0.82D));
assertEquals(1, releases.get());
}
@Test
public void diagnosticInvocationUsesCurrentJvmCommandMBean() throws Exception {
MBeanServer server = mock(MBeanServer.class);
when(server.isRegistered(any(ObjectName.class))).thenReturn(true);
MantleHeapPressure.invokeHotSpotDiagnosticGc(server);
ObjectName name = new ObjectName("com.sun.management:type=DiagnosticCommand");
ArgumentCaptor<Object[]> parameters = ArgumentCaptor.forClass(Object[].class);
ArgumentCaptor<String[]> signature = ArgumentCaptor.forClass(String[].class);
verify(server).invoke(
eq(name),
eq("gcRun"),
parameters.capture(),
signature.capture());
assertArrayEquals(new Object[0], parameters.getValue());
assertArrayEquals(new String[0], signature.getValue());
}
@Test
public void unsupportedJvmFailsBeforeInvokingDiagnosticCommand() throws Exception {
MBeanServer server = mock(MBeanServer.class);
when(server.isRegistered(any(ObjectName.class))).thenReturn(false);
assertThrows(UnsupportedOperationException.class, () -> MantleHeapPressure.invokeHotSpotDiagnosticGc(server));
verify(server, never()).invoke(any(ObjectName.class), any(), any(), any());
}
private static final class ReclaimerFixture {
private final AtomicLong clock = new AtomicLong();
private final AtomicInteger explicitCalls = new AtomicInteger();
private final AtomicInteger diagnosticCalls = new AtomicInteger();
private final List<Double> diagnosticFractions = new ArrayList<>();
private final List<Throwable> failures = new ArrayList<>();
private final MantleHeapPressure.PanicGcReclaimer reclaimer;
private ReclaimerFixture(int failuresBeforeSuccess) {
MantleHeapPressure.PanicGcActions actions = new MantleHeapPressure.PanicGcActions(
clock::get,
explicitCalls::incrementAndGet,
() -> {
int call = diagnosticCalls.incrementAndGet();
if (call <= failuresBeforeSuccess) {
throw new IllegalStateException("unsupported");
}
},
diagnosticFractions::add,
(String context, Throwable failure) -> failures.add(failure));
this.reclaimer = new MantleHeapPressure.PanicGcReclaimer(POLICY, actions);
}
}
}
@@ -9,31 +9,27 @@ import static org.junit.Assert.assertTrue;
public class PregenTaskBoundsOverflowTest {
@Test
public void farPositiveCenterKeepsRegionBoundsOrdered() {
PregenTask task = PregenTask.builder()
.center(new Position2(Integer.MAX_VALUE - 16, Integer.MAX_VALUE - 16))
.radiusX(4096)
.radiusZ(4096)
.build();
public void farPositiveCenterIsRejectedBeforeTraversal() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> PregenTask.builder()
.center(new Position2(Integer.MAX_VALUE - 16, Integer.MAX_VALUE - 16))
.radiusX(4096)
.radiusZ(4096)
.build());
int[] bounds = task.regionBounds();
assertTrue("minX must not exceed maxX", bounds[0] <= bounds[2]);
assertTrue("minZ must not exceed maxZ", bounds[1] <= bounds[3]);
assertTrue(failure.getMessage().contains("coordinate limit"));
}
@Test
public void farNegativeCenterKeepsRegionBoundsOrdered() {
PregenTask task = PregenTask.builder()
.center(new Position2(Integer.MIN_VALUE + 16, Integer.MIN_VALUE + 16))
.radiusX(4096)
.radiusZ(4096)
.build();
public void farNegativeCenterIsRejectedBeforeTraversal() {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> PregenTask.builder()
.center(new Position2(Integer.MIN_VALUE + 16, Integer.MIN_VALUE + 16))
.radiusX(4096)
.radiusZ(4096)
.build());
int[] bounds = task.regionBounds();
assertTrue("minX must not exceed maxX", bounds[0] <= bounds[2]);
assertTrue("minZ must not exceed maxZ", bounds[1] <= bounds[3]);
assertTrue(failure.getMessage().contains("coordinate limit"));
}
@Test
@@ -49,11 +45,11 @@ public class PregenTaskBoundsOverflowTest {
}
@Test
public void worldLimitRadiusIsAccepted() {
public void exactWorldLimitRadiusIsAccepted() {
PregenTask task = PregenTask.builder()
.center(new Position2(0, 0))
.radiusX(30_000_000)
.radiusZ(30_000_000)
.radiusX(PregenTask.MAX_WORLD_BLOCK)
.radiusZ(PregenTask.MAX_WORLD_BLOCK)
.build();
int[] bounds = task.regionBounds();
@@ -62,6 +58,30 @@ public class PregenTaskBoundsOverflowTest {
assertEquals(58594, bounds[2]);
}
@Test
public void oneBlockPastWorldLimitIsRejectedOnEveryEdge() {
int limit = PregenTask.MAX_WORLD_BLOCK;
assertWorldLimitFailure(limit, 0, 1, 1);
assertWorldLimitFailure(-limit, 0, 1, 1);
assertWorldLimitFailure(0, limit, 1, 1);
assertWorldLimitFailure(0, -limit, 1, 1);
}
@Test
public void offsetAreaEndingExactlyAtWorldLimitIsAccepted() {
int radius = 1000;
PregenTask task = PregenTask.builder()
.center(new Position2(PregenTask.MAX_WORLD_BLOCK - radius, -PregenTask.MAX_WORLD_BLOCK + radius))
.radiusX(radius)
.radiusZ(radius)
.build();
int[] bounds = task.regionBounds();
assertTrue(bounds[0] <= bounds[2]);
assertTrue(bounds[1] <= bounds[3]);
}
@Test
public void ordinaryBoundsAreUnchanged() {
PregenTask task = PregenTask.builder()
@@ -87,4 +107,14 @@ public class PregenTaskBoundsOverflowTest {
assertEquals("bounds[" + index + "]", expected[index], actual[index]);
}
}
private static void assertWorldLimitFailure(int centerX, int centerZ, int radiusX, int radiusZ) {
IllegalArgumentException failure = assertThrows(IllegalArgumentException.class,
() -> PregenTask.builder()
.center(new Position2(centerX, centerZ))
.radiusX(radiusX)
.radiusZ(radiusZ)
.build());
assertTrue(failure.getMessage().contains("coordinate limit"));
}
}
@@ -4,18 +4,31 @@ import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import org.bukkit.HeightMap;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.VoxelShape;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
public class WorldRuntimeControlServiceSafeEntryTest {
private static final BoundingBox FULL_BLOCK = new BoundingBox(0D, 0D, 0D, 1D, 1D, 1D);
@Test
public void resolvesStudioEntryAnchorFromGeneratorInsteadOfMutableWorldSpawn() {
World world = mock(World.class);
@@ -47,21 +60,180 @@ public class WorldRuntimeControlServiceSafeEntryTest {
}
@Test
public void resolvesSafeEntryImmediatelyWhenColumnIsAllWater() {
World world = mock(World.class);
Block stub = mock(Block.class, Mockito.RETURNS_DEEP_STUBS);
doReturn(-64).when(world).getMinHeight();
doReturn(320).when(world).getMaxHeight();
doReturn(true).when(world).isChunkLoaded(0, 0);
doReturn(62).when(world).getHighestBlockYAt(0, 0);
doReturn(62).when(world).getHighestBlockYAt(0, 0, HeightMap.MOTION_BLOCKING_NO_LEAVES);
doReturn(stub).when(world).getBlockAt(anyInt(), anyInt(), anyInt());
public void resolvesEntryAboveDryCollisionSupportingFloor() {
World world = loadedWorld(0, 0);
Block stone = block(Material.STONE, false, false, FULL_BLOCK);
Block air = block(Material.AIR, false, true);
doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES));
doAnswer(invocation -> {
int y = invocation.getArgument(1);
return y == 62 ? stone : air;
}).when(world).getBlockAt(anyInt(), anyInt(), anyInt());
Location source = new Location(world, 0.5D, 62D, 0.5D);
Location result = WorldRuntimeControlService.findTopSafeLocation(world, source);
assertNotNull("Safe entry must resolve to a non-null location even for water-only columns", result);
assertNotNull(result);
assertEquals(63, result.getBlockY());
}
@Test
public void searchesOnlyTheOwnedChunkForNearbySolidGround() {
World world = loadedWorld(0, 0);
Block water = block(Material.WATER, true, true);
Block stone = block(Material.STONE, false, false, FULL_BLOCK);
Block air = block(Material.AIR, false, true);
doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES));
doAnswer(invocation -> {
int x = invocation.getArgument(0);
int y = invocation.getArgument(1);
int z = invocation.getArgument(2);
if (x < 0 || x > 15 || z < 0 || z > 15) {
throw new AssertionError("Safe-entry search crossed its Folia-owned source chunk");
}
if (x == 1 && z == 0) {
return y == 62 ? stone : air;
}
return water;
}).when(world).getBlockAt(anyInt(), anyInt(), anyInt());
Location source = new Location(world, 0.5D, 63D, 0.5D);
Location result = WorldRuntimeControlService.findTopSafeLocation(world, source);
assertNotNull(result);
assertEquals(1, result.getBlockX());
assertEquals(63, result.getBlockY());
assertEquals(0, result.getBlockZ());
}
@Test
public void rejectsFluidHazardousAndCollisionBlockedCandidates() {
World world = loadedWorld(0, 0);
Block water = block(Material.WATER, true, true);
Block air = block(Material.AIR, false, true);
Block stone = block(Material.STONE, false, false, FULL_BLOCK);
Block leaves = block(Material.OAK_LEAVES, false, false, FULL_BLOCK);
Block powderSnow = block(Material.POWDER_SNOW, false, true);
Block magma = block(Material.MAGMA_BLOCK, false, false, FULL_BLOCK);
Block cactus = block(Material.CACTUS, false, false, FULL_BLOCK);
Block cobweb = block(Material.COBWEB, false, true, FULL_BLOCK);
Block fence = block(Material.OAK_FENCE, false, false,
new BoundingBox(0.375D, 0D, 0.375D, 0.625D, 1.5D, 0.625D));
Block waterloggedSlab = waterloggedBlock(
Material.OAK_SLAB,
new BoundingBox(0D, 0D, 0D, 1D, 0.5D, 1D)
);
doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES));
doAnswer(invocation -> {
int x = invocation.getArgument(0);
int y = invocation.getArgument(1);
int z = invocation.getArgument(2);
if (y == 62) {
if (x == 7 && z == 7) {
return leaves;
}
if (x == 7 && z == 8) {
return powderSnow;
}
if (x == 7 && z == 9) {
return magma;
}
if (x == 8 && z == 9) {
return stone;
}
if (x == 9 && z == 7) {
return waterloggedSlab;
}
if (x == 9 && z == 8) {
return cactus;
}
if (x == 9 && z == 9) {
return stone;
}
if (x == 10 && z == 10) {
return stone;
}
}
if (x == 8 && z == 9 && y == 63) {
return cobweb;
}
if (x == 9 && z == 9 && y == 63) {
return air;
}
if (x == 9 && z == 9 && y == 64) {
return fence;
}
if (x == 10 && z == 10 && (y == 63 || y == 64)) {
return air;
}
return water;
}).when(world).getBlockAt(anyInt(), anyInt(), anyInt());
Location source = new Location(world, 8.5D, 63D, 8.5D);
Location result = WorldRuntimeControlService.findTopSafeLocation(world, source);
assertNotNull(result);
assertEquals(10, result.getBlockX());
assertEquals(63, result.getBlockY());
assertEquals(10, result.getBlockZ());
}
@Test
public void returnsNullForWaterOnlyChunkAndBoundsVerticalSearch() {
World world = loadedWorld(0, 0);
Block water = block(Material.WATER, true, true);
AtomicInteger lowestReadY = new AtomicInteger(Integer.MAX_VALUE);
doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES));
doAnswer(invocation -> {
int y = invocation.getArgument(1);
lowestReadY.accumulateAndGet(y, Math::min);
return water;
}).when(world).getBlockAt(anyInt(), anyInt(), anyInt());
Location source = new Location(world, 0.5D, 63D, 0.5D);
Location result = WorldRuntimeControlService.findTopSafeLocation(world, source);
assertNull(result);
assertEquals(-1, lowestReadY.get());
}
@Test
public void returnsNullWithoutReadingAnUnloadedChunk() {
World world = loadedWorld(0, 0);
doReturn(false).when(world).isChunkLoaded(0, 0);
Location source = new Location(world, 0.5D, 63D, 0.5D);
Location result = WorldRuntimeControlService.findTopSafeLocation(world, source);
assertNull(result);
verify(world, never()).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES));
verify(world, never()).getBlockAt(anyInt(), anyInt(), anyInt());
}
private static World loadedWorld(int chunkX, int chunkZ) {
World world = mock(World.class);
doReturn(-64).when(world).getMinHeight();
doReturn(320).when(world).getMaxHeight();
doReturn(true).when(world).isChunkLoaded(chunkX, chunkZ);
return world;
}
private static Block block(Material material, boolean liquid, boolean passable, BoundingBox... boundingBoxes) {
Block block = mock(Block.class);
VoxelShape collisionShape = mock(VoxelShape.class);
doReturn(material).when(block).getType();
doReturn(liquid).when(block).isLiquid();
doReturn(passable).when(block).isPassable();
doReturn(collisionShape).when(block).getCollisionShape();
doReturn(List.of(boundingBoxes)).when(collisionShape).getBoundingBoxes();
return block;
}
private static Block waterloggedBlock(Material material, BoundingBox... boundingBoxes) {
Block block = block(material, false, false, boundingBoxes);
Waterlogged blockData = mock(Waterlogged.class);
doReturn(true).when(blockData).isWaterlogged();
doReturn(blockData).when(block).getBlockData();
return block;
}
}
@@ -0,0 +1,64 @@
package art.arcane.iris.core.service;
import art.arcane.iris.core.IrisWorldStorage;
import org.bukkit.NamespacedKey;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.nio.file.Files;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
public class GlobalCacheSVCTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void cacheUsesFrozenCurrentCraftBukkitDimensionRoot() throws Exception {
File worldContainer = temporaryFolder.newFolder("server");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
NamespacedKey worldKey = new NamespacedKey("iris", "underworld");
File configuredLevelRoot = Files.createDirectory(
worldContainer.toPath().resolve("world_iris_underworld")
).toFile();
File configuredDimensionRoot = IrisWorldStorage.dimensionRoot(configuredLevelRoot, worldKey);
Files.createDirectories(configuredDimensionRoot.toPath().resolve("iris/pack"));
assertEquals(
configuredDimensionRoot,
GlobalCacheSVC.requireCacheDimensionRoot(worldContainer, levelRoot, worldKey)
);
}
@Test
public void cacheFailsClosedWhenFrozenStorageIsAmbiguous() throws Exception {
File worldContainer = temporaryFolder.newFolder("ambiguous-server");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
NamespacedKey worldKey = new NamespacedKey("iris", "overworld");
Files.createDirectories(IrisWorldStorage.dimensionRoot(levelRoot, worldKey).toPath());
File configuredLevelRoot = Files.createDirectory(
worldContainer.toPath().resolve("world_iris_overworld")
).toFile();
Files.createDirectories(IrisWorldStorage.dimensionRoot(configuredLevelRoot, worldKey).toPath());
assertThrows(
IllegalStateException.class,
() -> GlobalCacheSVC.requireCacheDimensionRoot(worldContainer, levelRoot, worldKey)
);
}
@Test
public void cacheFailsClosedWhenFrozenStorageIsMissing() throws Exception {
File worldContainer = temporaryFolder.newFolder("missing-server");
File levelRoot = Files.createDirectory(worldContainer.toPath().resolve("world")).toFile();
NamespacedKey worldKey = new NamespacedKey("iris", "overworld");
assertThrows(
IllegalStateException.class,
() -> GlobalCacheSVC.requireCacheDimensionRoot(worldContainer, levelRoot, worldKey)
);
}
}
@@ -0,0 +1,72 @@
package art.arcane.iris.core.tools;
import org.junit.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisPersistentWorldCreationContractTest {
@Test
public void productionCreateFreezesIntoCurrentPlatformStorageBeforeWorldCreation() throws Exception {
String source = Files.readString(Path.of("src/main/java/art/arcane/iris/core/tools/IrisCreator.java"));
int createStart = source.indexOf("private World createReserved(");
int createEnd = source.indexOf("static Player createTeleportTarget(", createStart);
String create = source.substring(createStart, createEnd);
int dimensionRoot = create.indexOf("WorldCreatorCompat.persistentDimensionRoot(worldKey)");
int levelRoot = create.indexOf("WorldCreatorCompat.persistentLevelRoot(worldKey)");
int existingStorage = create.indexOf("Files.exists(storageRoot.toPath())");
int freezePack = create.indexOf(".installIntoWorld(sender, resolvedDimension, dimensionRoot)");
int persistentCreator = create.indexOf(".persistent(!studio && !benchmark)");
int bukkitCreate = create.indexOf("INMS.get().createWorldAsync(wc, request)");
int rollbackStorage = create.indexOf(
"rollbackWorldCreation(worldKey, world, stagedGenerator, storageRoot, bukkitRegistered, failure)"
);
assertTrue(dimensionRoot >= 0);
assertTrue(levelRoot > dimensionRoot);
assertTrue(existingStorage > levelRoot);
assertTrue(freezePack > existingStorage);
assertTrue(persistentCreator > freezePack);
assertTrue(bukkitCreate > persistentCreator);
assertTrue(rollbackStorage > bukkitCreate);
assertFalse(create.contains("copySeed"));
assertFalse(create.contains("level.dat"));
}
@Test
public void persistentCreatorPreservesConfiguredNameAndCanonicalIdentity() throws Exception {
String source = Files.readString(Path.of("src/main/java/art/arcane/iris/core/tools/IrisWorldCreator.java"));
int createStart = source.indexOf("public WorldCreator create()");
int createEnd = source.indexOf("private World.Environment findEnvironment()", createStart);
String create = source.substring(createStart, createEnd);
int persistentCreator = create.indexOf("WorldCreatorCompat.ofPersistentKey(worldKey)");
int persistentStorage = create.indexOf("WorldCreatorCompat.persistentDimensionRoot(worldKey)");
int canonicalIdentity = create.indexOf(".platformIdentity(worldKey.toString())");
int configuredName = create.indexOf(".name(creator.name())");
int exactPack = create.indexOf("new File(w.worldFolder(), \"iris/pack\")");
assertTrue(persistentCreator >= 0);
assertTrue(persistentStorage > persistentCreator);
assertTrue(canonicalIdentity > persistentStorage);
assertTrue(configuredName > canonicalIdentity);
assertTrue(exactPack > configuredName);
}
@Test
public void publicBukkitBackendRebuildKeepsExactFallbackWorldName() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleRequest.java"
));
int rebuildStart = source.indexOf("public WorldCreator toWorldCreator()");
int rebuildEnd = source.indexOf("return creator;", rebuildStart);
String rebuild = source.substring(rebuildStart, rebuildEnd);
assertTrue(rebuild.contains("WorldCreatorCompat.ofKey(worldKey, worldName)"));
assertFalse(rebuild.contains("WorldCreatorCompat.ofKey(worldKey)"));
}
}
@@ -0,0 +1,47 @@
package art.arcane.iris.engine;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertTrue;
public class EngineRuntimePublicationContractTest {
@Test
public void worldManagerStartsOnlyAfterTheRuntimeSessionIsReady() throws IOException {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/engine/EngineRuntimeBuilder.java"));
int publishStart = source.indexOf("void publishRuntime(");
int publishEnd = source.indexOf("private void scheduleRuntimeTasks", publishStart);
String publish = source.substring(publishStart, publishEnd);
assertBefore(publish, "engine.runtime = next", "next.worldManager().start()");
assertBefore(publish, "activateNextSession()", "next.worldManager().start()");
assertBefore(publish, "engine.lifecycleState = LifecycleState.RUNNING", "next.worldManager().start()");
assertBefore(publish, "engine.getClosing().set(false)", "next.worldManager().start()");
assertBefore(publish, "openBackgroundTaskAdmission()", "next.worldManager().start()");
}
@Test
public void failedManagerStartClosesAdmissionBeforeRuntimeCleanup() throws IOException {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/engine/EngineRuntimeBuilder.java"));
int publishStart = source.indexOf("void publishRuntime(");
int publishEnd = source.indexOf("private void scheduleRuntimeTasks", publishStart);
String publish = source.substring(publishStart, publishEnd);
assertBefore(publish, "engine.getClosing().set(true)", "closeRuntime(next, e)");
assertBefore(publish, "closeBackgroundTaskAdmission()", "closeRuntime(next, e)");
assertBefore(publish, "sealAndAwait(", "closeRuntime(next, e)");
}
private static void assertBefore(String source, String first, String second) {
int firstIndex = source.indexOf(first);
int secondIndex = source.indexOf(second);
assertTrue("Missing source contract token: " + first, firstIndex >= 0);
assertTrue("Missing source contract token: " + second, secondIndex >= 0);
assertTrue(first + " must occur before " + second, firstIndex < secondIndex);
}
}
@@ -14,13 +14,25 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
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.AtomicReference;
import java.util.logging.Logger;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Answers.CALLS_REAL_METHODS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -136,6 +148,173 @@ public class IrisDimensionCarvingResolverParityTest {
}
}
@Test
public void sharedResolverStateNeverCarriesDimensionEntriesAcrossEngines() {
Fixture first = createFixture();
Fixture second = createMixedDepthFixture();
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
IrisDimensionCarvingEntry firstExpected = legacyResolveRootEntry(first.engine, 80);
IrisDimensionCarvingEntry secondExpected = legacyResolveRootEntry(second.engine, 80);
assertSame(firstExpected, IrisDimensionCarvingResolver.resolveRootEntry(first.engine, 80, state));
assertSame(secondExpected, IrisDimensionCarvingResolver.resolveRootEntry(second.engine, 80, state));
assertSame(firstExpected, IrisDimensionCarvingResolver.resolveRootEntry(first.engine, 80, state));
}
@Test
public void threadLocalResolverNeverCarriesDimensionEntriesAcrossEngines() {
Fixture first = createFixture();
Fixture second = createMixedDepthFixture();
int worldY = 83;
IrisDimensionCarvingEntry firstExpected = legacyResolveRootEntry(first.engine, worldY);
IrisDimensionCarvingEntry secondExpected = legacyResolveRootEntry(second.engine, worldY);
assertSame(firstExpected, IrisDimensionCarvingResolver.resolveRootEntry(first.engine, worldY));
assertSame(secondExpected, IrisDimensionCarvingResolver.resolveRootEntry(second.engine, worldY));
}
@Test
public void threadLocalResolverInvalidatesWhenTheSameEnginePublishesReplacementData() {
Fixture first = createFixture();
Fixture replacement = createMixedDepthFixture();
AtomicReference<IrisDimension> dimension = new AtomicReference<>(first.engine.getDimension());
AtomicReference<IrisData> data = new AtomicReference<>(first.engine.getData());
Engine engine = mock(Engine.class, CALLS_REAL_METHODS);
doAnswer((InvocationOnMock invocation) -> dimension.get()).when(engine).getDimension();
doAnswer((InvocationOnMock invocation) -> data.get()).when(engine).getData();
int worldY = 83;
IrisDimensionCarvingEntry firstExpected = legacyResolveRootEntry(first.engine, worldY);
IrisDimensionCarvingEntry replacementExpected = legacyResolveRootEntry(replacement.engine, worldY);
assertSame(firstExpected, IrisDimensionCarvingResolver.resolveRootEntry(engine, worldY));
dimension.set(replacement.engine.getDimension());
data.set(replacement.engine.getData());
assertSame(replacementExpected, IrisDimensionCarvingResolver.resolveRootEntry(engine, worldY));
}
@Test
public void resolverStateUsesWeakRuntimeIdentityBindings() throws NoSuchFieldException {
Field engineIdentity = IrisDimensionCarvingResolver.State.class.getDeclaredField("engineIdentity");
Field dimensionIdentity = IrisDimensionCarvingResolver.State.class.getDeclaredField("dimensionIdentity");
Field dataIdentity = IrisDimensionCarvingResolver.State.class.getDeclaredField("dataIdentity");
assertSame(WeakReference.class, engineIdentity.getType());
assertSame(WeakReference.class, dimensionIdentity.getType());
assertSame(WeakReference.class, dataIdentity.getType());
}
@Test(timeout = 20_000L)
public void longLivedWorkerDoesNotRetainPopulatedThreadLocalStateOrEngine() throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
CountDownLatch blockerStarted = new CountDownLatch(1);
CountDownLatch releaseBlocker = new CountDownLatch(1);
try {
LifetimeReferences references = executor.submit(this::populateThreadLocalLifetimeReferences).get();
Future<?> blocker = executor.submit(() -> {
blockerStarted.countDown();
releaseBlocker.await();
return null;
});
assertTrue(blockerStarted.await(5L, TimeUnit.SECONDS));
awaitCollection(references);
releaseBlocker.countDown();
blocker.get(5L, TimeUnit.SECONDS);
} finally {
releaseBlocker.countDown();
executor.shutdownNow();
assertTrue(executor.awaitTermination(5L, TimeUnit.SECONDS));
}
}
private LifetimeReferences populateThreadLocalLifetimeReferences() throws Exception {
RetainedFixture fixture = createRetainedFixture();
assertNotNull(IrisDimensionCarvingResolver.resolveRootEntry(fixture.engine(), 80));
Field threadStateField = IrisDimensionCarvingResolver.class.getDeclaredField("THREAD_STATE");
threadStateField.setAccessible(true);
ThreadLocal<?> threadState = (ThreadLocal<?>) threadStateField.get(null);
Object value = threadState.get();
assertTrue(value instanceof WeakReference<?>);
@SuppressWarnings("unchecked")
WeakReference<IrisDimensionCarvingResolver.State> state =
(WeakReference<IrisDimensionCarvingResolver.State>) value;
IrisDimensionCarvingResolver.State populatedState = state.get();
assertNotNull(populatedState);
Field biomeCacheField = IrisDimensionCarvingResolver.State.class.getDeclaredField("biomeCache");
biomeCacheField.setAccessible(true);
assertFalse(((Map<?, ?>) biomeCacheField.get(populatedState)).isEmpty());
return new LifetimeReferences(
state,
new WeakReference<>(fixture.engine()));
}
private static void awaitCollection(LifetimeReferences references) throws InterruptedException {
for (int attempt = 0; attempt < 100; attempt++) {
System.gc();
if (references.state().get() == null
&& references.engine().get() == null) {
return;
}
byte[] pressure = new byte[1_048_576];
pressure[0] = (byte) attempt;
Thread.sleep(10L);
}
assertTrue("Thread-local State was retained", references.state().get() == null);
assertTrue("Engine was retained", references.engine().get() == null);
}
@Test
public void explicitStateRemainsStronglyCallerOwned() throws Exception {
RetainedFixture fixture = createRetainedFixture();
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
assertSame(fixture.entry(), IrisDimensionCarvingResolver.resolveRootEntry(
fixture.engine(), 80, state));
Field rootEntriesField = IrisDimensionCarvingResolver.State.class
.getDeclaredField("rootEntriesByWorldY");
rootEntriesField.setAccessible(true);
System.gc();
assertSame(fixture.entry(), ((Map<?, ?>) rootEntriesField.get(state)).get(80));
}
private RetainedFixture createRetainedFixture() {
IrisData data = mock(IrisData.class);
IrisBiome biome = new IrisBiome();
biome.setLoader(data);
@SuppressWarnings("unchecked")
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
doReturn(biome).when(biomeLoader).load("retained");
doReturn(biomeLoader).when(data).getBiomeLoader();
IrisDimensionCarvingEntry entry = buildEntry(
"retained", "retained", new IrisRange(-64, 320), 0, List.of());
KList<IrisDimensionCarvingEntry> carvingEntries = new KList<>();
carvingEntries.add(entry);
IrisDimension dimension = new IrisDimension();
dimension.setCarving(carvingEntries);
Engine engine = (Engine) Proxy.newProxyInstance(
Engine.class.getClassLoader(),
new Class<?>[]{Engine.class},
(proxy, method, arguments) -> switch (method.getName()) {
case "getDimension" -> dimension;
case "getData" -> data;
case "hashCode" -> System.identityHashCode(proxy);
case "equals" -> proxy == arguments[0];
case "toString" -> "carving-retention-test-engine";
default -> throw new UnsupportedOperationException(method.toString());
});
return new RetainedFixture(engine, entry);
}
private Fixture createFixture() {
IrisBiome rootLowBiome = mock(IrisBiome.class);
IrisBiome rootHighBiome = mock(IrisBiome.class);
@@ -454,6 +633,15 @@ public class IrisDimensionCarvingResolverParityTest {
private record Fixture(Engine engine) {
}
private record RetainedFixture(Engine engine, IrisDimensionCarvingEntry entry) {
}
private record LifetimeReferences(
WeakReference<IrisDimensionCarvingResolver.State> state,
WeakReference<Engine> engine
) {
}
private static final class LegacyCarvingChoice implements IRare {
private final IrisDimensionCarvingEntry entry;
private final int rarity;