Fix cross-thread report assertion

- Assert the spawn failure report through the bound platform instead of a thread-confined static mock, so the report is seen whichever thread wins the reporting race
- Guard WorldEntitySpawnerLifecycleTest against a leaked platform binding
- Unbind the stub platform after the probe module's platform state test
This commit is contained in:
Brian Neumann-Fopiano
2026-09-08 04:28:17 -04:00
parent 42798c6340
commit bdb13d1647
2 changed files with 32 additions and 5 deletions
@@ -12,11 +12,16 @@ import art.arcane.iris.engine.framework.GenerationSessionManager;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisPlatform;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.testsupport.Await;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import art.arcane.iris.util.common.scheduling.J;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.volmlib.util.scheduling.ChronoLatch;
import org.bukkit.World;
import org.bukkit.entity.LivingEntity;
import org.junit.ClassRule;
import org.junit.Test;
import org.mockito.MockedStatic;
@@ -41,8 +46,9 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
@@ -50,6 +56,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class WorldEntitySpawnerLifecycleTest {
@ClassRule
public static final PlatformLeakGuard PLATFORM_GUARD = PlatformLeakGuard.clean();
@Test
public void queuedSpawnIsDiscardedWhenTheManagerCloses() throws Exception {
try (Fixture fixture = new Fixture()) {
@@ -162,14 +171,26 @@ public class WorldEntitySpawnerLifecycleTest {
when(fixture.world.isChunkLoaded(7, -4)).thenThrow(failure);
Future<Boolean> waiting = fixture.spawn(false, true);
Runnable task = fixture.nextTask();
AtomicReference<String> reportedContext = new AtomicReference<>();
AtomicReference<Throwable> reportedCause = new AtomicReference<>();
IrisPlatform platform = mock(IrisPlatform.class);
doAnswer(invocation -> {
reportedContext.set(invocation.getArgument(0));
reportedCause.set(invocation.getArgument(1));
return null;
}).when(platform).reportError(anyString(), any(Throwable.class));
IrisPlatforms.bind(platform);
try (MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class)) {
try {
task.run();
logging.verify(() -> IrisLogging.reportError(
eq("Failed to spawn Iris entities in chunk 7,-4."), same(failure)));
assertFalse(waiting.get(1L, TimeUnit.SECONDS));
Await.until("the spawn failure report", () -> reportedCause.get() != null);
} finally {
IrisPlatforms.unbind();
}
assertFalse(waiting.get(1L, TimeUnit.SECONDS));
assertEquals("Failed to spawn Iris entities in chunk 7,-4.", reportedContext.get());
assertSame(failure, reportedCause.get());
assertEquals(0, fixture.sessions.activeLeases());
}
}
@@ -5,6 +5,7 @@ import art.arcane.iris.engine.object.IrisObjectRotation;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.hunk.Hunk;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -21,6 +22,11 @@ public final class StubPlatformStateTest {
StubPlatform.bindGenerationStateHandlers();
}
@AfterClass
public static void unbindPlatform() {
IrisPlatforms.unbind();
}
@Test
public void rotatesFacingAxisAndConnectedFaces() {
IrisObjectRotation rotation = IrisObjectRotation.of(0, 90, 0);