From 91c06da68a25f9962c99d429177be237001549b2 Mon Sep 17 00:00:00 2001 From: TheMode Date: Wed, 19 Jan 2022 21:41:25 +0100 Subject: [PATCH] Basic testing framework (#594) --- .../minestom.common-conventions.gradle.kts | 3 + .../minestom/server/instance/AnvilLoader.java | 23 ++-- .../minestom/server/instance/Instance.java | 4 - .../server/instance/InstanceContainer.java | 4 +- .../java/net/minestom/server/api/Env.java | 64 ++++++++++ .../net/minestom/server/api/EnvBefore.java | 11 ++ .../java/net/minestom/server/api/EnvImpl.java | 22 ++++ .../server/api/EnvParameterResolver.java | 15 +++ .../java/net/minestom/server/api/EnvTest.java | 15 +++ .../minestom/server/api/TestConnection.java | 20 +++ .../server/api/TestConnectionImpl.java | 97 +++++++++++++++ .../entity/EntityInstanceIntegrationTest.java | 45 +++++++ .../entity/EntityTeleportIntegrationTest.java | 90 ++++++++++++++ .../entity/EntityViewIntegrationTest.java | 114 ++++++++++++++++++ .../server/inventory/InventoryTest.java | 9 +- 15 files changed, 513 insertions(+), 23 deletions(-) create mode 100644 src/test/java/net/minestom/server/api/Env.java create mode 100644 src/test/java/net/minestom/server/api/EnvBefore.java create mode 100644 src/test/java/net/minestom/server/api/EnvImpl.java create mode 100644 src/test/java/net/minestom/server/api/EnvParameterResolver.java create mode 100644 src/test/java/net/minestom/server/api/EnvTest.java create mode 100644 src/test/java/net/minestom/server/api/TestConnection.java create mode 100644 src/test/java/net/minestom/server/api/TestConnectionImpl.java create mode 100644 src/test/java/net/minestom/server/entity/EntityInstanceIntegrationTest.java create mode 100644 src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java create mode 100644 src/test/java/net/minestom/server/entity/EntityViewIntegrationTest.java diff --git a/build-logic/src/main/kotlin/minestom.common-conventions.gradle.kts b/build-logic/src/main/kotlin/minestom.common-conventions.gradle.kts index 404e5cca2..042539362 100644 --- a/build-logic/src/main/kotlin/minestom.common-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/minestom.common-conventions.gradle.kts @@ -21,5 +21,8 @@ tasks { } withType { useJUnitPlatform() + // Present until tests all succeed without + maxParallelForks = Runtime.getRuntime().availableProcessors() + setForkEvery(1) } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/instance/AnvilLoader.java b/src/main/java/net/minestom/server/instance/AnvilLoader.java index 5d6f1f8a8..3b7f9ce07 100644 --- a/src/main/java/net/minestom/server/instance/AnvilLoader.java +++ b/src/main/java/net/minestom/server/instance/AnvilLoader.java @@ -1,15 +1,12 @@ package net.minestom.server.instance; import net.minestom.server.MinecraftServer; -import net.minestom.server.exception.ExceptionManager; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.instance.block.BlockManager; import net.minestom.server.tag.Tag; import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.world.biomes.Biome; -import net.minestom.server.world.biomes.BiomeManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.mca.*; @@ -30,9 +27,6 @@ import java.util.concurrent.ConcurrentHashMap; public class AnvilLoader implements IChunkLoader { private final static Logger LOGGER = LoggerFactory.getLogger(AnvilLoader.class); - private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); - private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager(); - private static final ExceptionManager EXCEPTION_MANAGER = MinecraftServer.getExceptionManager(); private static final Biome BIOME = Biome.PLAINS; private final Map alreadyLoaded = new ConcurrentHashMap<>(); @@ -74,7 +68,7 @@ public class AnvilLoader implements IChunkLoader { try { return loadMCA(instance, chunkX, chunkZ); } catch (Exception e) { - EXCEPTION_MANAGER.handleException(e); + MinecraftServer.getExceptionManager().handleException(e); } return CompletableFuture.completedFuture(null); } @@ -119,7 +113,8 @@ public class AnvilLoader implements IChunkLoader { int finalZ = fileChunk.getZ() * Chunk.CHUNK_SIZE_Z + z; int finalY = section.getY() * Chunk.CHUNK_SECTION_SIZE + y; String biomeName = section.getBiome(x, y, z); - Biome biome = biomeCache.computeIfAbsent(biomeName, n -> Objects.requireNonNullElse(BIOME_MANAGER.getByName(NamespaceID.from(n)), BIOME)); + Biome biome = biomeCache.computeIfAbsent(biomeName, n -> + Objects.requireNonNullElse(MinecraftServer.getBiomeManager().getByName(NamespaceID.from(n)), BIOME)); chunk.setBiome(finalX, finalY, finalZ, biome); } } @@ -151,7 +146,7 @@ public class AnvilLoader implements IChunkLoader { } return new RegionFile(new RandomAccessFile(regionPath.toFile(), "rw"), regionX, regionZ, instance.getDimensionType().getMinY(), instance.getDimensionType().getMaxY()-1); } catch (IOException | AnvilException e) { - EXCEPTION_MANAGER.handleException(e); + MinecraftServer.getExceptionManager().handleException(e); return null; } }); @@ -178,7 +173,7 @@ public class AnvilLoader implements IChunkLoader { chunk.setBlock(x, y + yOffset, z, block); } catch (Exception e) { - EXCEPTION_MANAGER.handleException(e); + MinecraftServer.getExceptionManager().handleException(e); } } } @@ -199,7 +194,7 @@ public class AnvilLoader implements IChunkLoader { final String tileEntityID = te.getString("id"); if (tileEntityID != null) { - final BlockHandler handler = BLOCK_MANAGER.getHandlerOrDummy(tileEntityID); + final BlockHandler handler = MinecraftServer.getBlockManager().getHandlerOrDummy(tileEntityID); block = block.withHandler(handler); } // Remove anvil tags @@ -254,7 +249,7 @@ public class AnvilLoader implements IChunkLoader { alreadyLoaded.put(n, mcaFile); } catch (AnvilException | IOException e) { LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); - EXCEPTION_MANAGER.handleException(e); + MinecraftServer.getExceptionManager().handleException(e); return AsyncUtils.VOID_FUTURE; } } @@ -264,7 +259,7 @@ public class AnvilLoader implements IChunkLoader { column = mcaFile.getOrCreateChunk(chunkX, chunkZ); } catch (AnvilException | IOException e) { LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); - EXCEPTION_MANAGER.handleException(e); + MinecraftServer.getExceptionManager().handleException(e); return AsyncUtils.VOID_FUTURE; } save(chunk, column); @@ -274,7 +269,7 @@ public class AnvilLoader implements IChunkLoader { mcaFile.forget(column); } catch (IOException e) { LOGGER.error("Failed to save chunk " + chunkX + ", " + chunkZ, e); - EXCEPTION_MANAGER.handleException(e); + MinecraftServer.getExceptionManager().handleException(e); return AsyncUtils.VOID_FUTURE; } return AsyncUtils.VOID_FUTURE; diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 717b71f80..7408ce189 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -3,7 +3,6 @@ package net.minestom.server.instance; import it.unimi.dsi.fastutil.objects.ObjectArraySet; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.pointer.Pointers; -import net.minestom.server.MinecraftServer; import net.minestom.server.Tickable; import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.coordinate.Point; @@ -17,7 +16,6 @@ import net.minestom.server.event.GlobalHandles; import net.minestom.server.event.instance.InstanceTickEvent; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; -import net.minestom.server.instance.block.BlockManager; import net.minestom.server.network.packet.server.play.BlockActionPacket; import net.minestom.server.network.packet.server.play.TimeUpdatePacket; import net.minestom.server.tag.Tag; @@ -54,8 +52,6 @@ import java.util.stream.Collectors; */ public abstract class Instance implements Block.Getter, Block.Setter, Tickable, Schedulable, TagHandler, PacketGroupingAudience { - protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); - private boolean registered; private final DimensionType dimensionType; diff --git a/src/main/java/net/minestom/server/instance/InstanceContainer.java b/src/main/java/net/minestom/server/instance/InstanceContainer.java index 401d6e1d2..0f31eab57 100644 --- a/src/main/java/net/minestom/server/instance/InstanceContainer.java +++ b/src/main/java/net/minestom/server/instance/InstanceContainer.java @@ -124,7 +124,7 @@ public class InstanceContainer extends Instance { final BlockHandler previousHandler = previousBlock.handler(); // Change id based on neighbors - final BlockPlacementRule blockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(block); + final BlockPlacementRule blockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(block); if (blockPlacementRule != null) { block = blockPlacementRule.blockUpdate(this, blockPosition, block); } @@ -501,7 +501,7 @@ public class InstanceContainer extends Instance { if (chunk == null) continue; final Block neighborBlock = chunk.getBlock(neighborX, neighborY, neighborZ); - final BlockPlacementRule neighborBlockPlacementRule = BLOCK_MANAGER.getBlockPlacementRule(neighborBlock); + final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer.getBlockManager().getBlockPlacementRule(neighborBlock); if (neighborBlockPlacementRule == null) continue; final Vec neighborPosition = new Vec(neighborX, neighborY, neighborZ); diff --git a/src/test/java/net/minestom/server/api/Env.java b/src/test/java/net/minestom/server/api/Env.java new file mode 100644 index 000000000..057fdc260 --- /dev/null +++ b/src/test/java/net/minestom/server/api/Env.java @@ -0,0 +1,64 @@ +package net.minestom.server.api; + +import net.minestom.server.ServerProcess; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.Chunk; +import net.minestom.server.instance.ChunkGenerator; +import net.minestom.server.instance.ChunkPopulator; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.batch.ChunkBatch; +import net.minestom.server.instance.block.Block; +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; +import java.util.List; +import java.util.function.BooleanSupplier; + +public interface Env { + @NotNull ServerProcess process(); + + @NotNull TestConnection createConnection(); + + default void tick() { + process().ticker().tick(System.nanoTime()); + } + + default boolean tickWhile(BooleanSupplier condition, Duration timeout) { + var ticker = process().ticker(); + final long start = System.nanoTime(); + while (condition.getAsBoolean()) { + final long tick = System.nanoTime(); + ticker.tick(tick); + if (timeout != null && System.nanoTime() - start > timeout.toNanos()) { + return false; + } + } + return true; + } + + default @NotNull Player createPlayer(@NotNull Instance instance, @NotNull Pos pos) { + return createConnection().connect(instance, pos).join(); + } + + default @NotNull Instance createFlatInstance() { + var instance = process().instance().createInstanceContainer(); + instance.setChunkGenerator(new ChunkGenerator() { + @Override + public void generateChunkData(@NotNull ChunkBatch batch, int chunkX, int chunkZ) { + for (byte x = 0; x < Chunk.CHUNK_SIZE_X; x++) + for (byte z = 0; z < Chunk.CHUNK_SIZE_Z; z++) { + for (byte y = 0; y < 40; y++) { + batch.setBlock(x, y, z, Block.STONE); + } + } + } + + @Override + public List getPopulators() { + return null; + } + }); + return instance; + } +} diff --git a/src/test/java/net/minestom/server/api/EnvBefore.java b/src/test/java/net/minestom/server/api/EnvBefore.java new file mode 100644 index 000000000..cf673fee8 --- /dev/null +++ b/src/test/java/net/minestom/server/api/EnvBefore.java @@ -0,0 +1,11 @@ +package net.minestom.server.api; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +final class EnvBefore implements BeforeEachCallback { + @Override + public void beforeEach(ExtensionContext context) { + System.setProperty("minestom.viewable-packet", "false"); + } +} diff --git a/src/test/java/net/minestom/server/api/EnvImpl.java b/src/test/java/net/minestom/server/api/EnvImpl.java new file mode 100644 index 000000000..313e2c03d --- /dev/null +++ b/src/test/java/net/minestom/server/api/EnvImpl.java @@ -0,0 +1,22 @@ +package net.minestom.server.api; + +import net.minestom.server.ServerProcess; +import org.jetbrains.annotations.NotNull; + +final class EnvImpl implements Env { + private final ServerProcess process; + + public EnvImpl(ServerProcess process) { + this.process = process; + } + + @Override + public @NotNull ServerProcess process() { + return process; + } + + @Override + public @NotNull TestConnection createConnection() { + return new TestConnectionImpl(this); + } +} diff --git a/src/test/java/net/minestom/server/api/EnvParameterResolver.java b/src/test/java/net/minestom/server/api/EnvParameterResolver.java new file mode 100644 index 000000000..b0923e70d --- /dev/null +++ b/src/test/java/net/minestom/server/api/EnvParameterResolver.java @@ -0,0 +1,15 @@ +package net.minestom.server.api; + +import net.minestom.server.MinecraftServer; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.support.TypeBasedParameterResolver; + +final class EnvParameterResolver extends TypeBasedParameterResolver { + @Override + public Env resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return new EnvImpl(MinecraftServer.updateProcess()); + } +} diff --git a/src/test/java/net/minestom/server/api/EnvTest.java b/src/test/java/net/minestom/server/api/EnvTest.java new file mode 100644 index 000000000..13b0f4019 --- /dev/null +++ b/src/test/java/net/minestom/server/api/EnvTest.java @@ -0,0 +1,15 @@ +package net.minestom.server.api; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@ExtendWith(EnvParameterResolver.class) +@ExtendWith(EnvBefore.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EnvTest { +} diff --git a/src/test/java/net/minestom/server/api/TestConnection.java b/src/test/java/net/minestom/server/api/TestConnection.java new file mode 100644 index 000000000..21519039a --- /dev/null +++ b/src/test/java/net/minestom/server/api/TestConnection.java @@ -0,0 +1,20 @@ +package net.minestom.server.api; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.ServerPacket; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public interface TestConnection { + @NotNull CompletableFuture<@NotNull Player> connect(@NotNull Instance instance, @NotNull Pos pos); + + @NotNull PacketTracker trackIncoming(@NotNull Class type); + + interface PacketTracker { + @NotNull List<@NotNull T> collect(); + } +} diff --git a/src/test/java/net/minestom/server/api/TestConnectionImpl.java b/src/test/java/net/minestom/server/api/TestConnectionImpl.java new file mode 100644 index 000000000..0554c6126 --- /dev/null +++ b/src/test/java/net/minestom/server/api/TestConnectionImpl.java @@ -0,0 +1,97 @@ +package net.minestom.server.api; + +import net.minestom.server.ServerProcess; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventListener; +import net.minestom.server.event.player.PlayerLoginEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.SendablePacket; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.player.PlayerConnection; +import org.jetbrains.annotations.NotNull; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; + +final class TestConnectionImpl implements TestConnection { + private final Env env; + private final ServerProcess process; + private final PlayerConnectionImpl playerConnection = new PlayerConnectionImpl(); + + private final List> incomingTrackers = new CopyOnWriteArrayList<>(); + + public TestConnectionImpl(Env env) { + this.env = env; + this.process = env.process(); + } + + @Override + public @NotNull CompletableFuture connect(@NotNull Instance instance, @NotNull Pos pos) { + AtomicReference> listenerRef = new AtomicReference<>(); + var listener = EventListener.builder(PlayerLoginEvent.class) + .handler(event -> { + if (event.getPlayer().getPlayerConnection() == playerConnection) { + event.setSpawningInstance(instance); + event.getPlayer().setRespawnPoint(pos); + process.eventHandler().removeListener(listenerRef.get()); + } + }).build(); + listenerRef.set(listener); + process.eventHandler().addListener(listener); + + var player = new Player(UUID.randomUUID(), "RandName", playerConnection); + process.connection().startPlayState(player, true); + while (player.getInstance() != instance) { // TODO replace with proper future + env.tick(); + } + return CompletableFuture.completedFuture(player); + } + + @Override + public @NotNull PacketTracker trackIncoming(@NotNull Class type) { + var tracker = new TrackerImpl<>(type); + this.incomingTrackers.add(TrackerImpl.class.cast(tracker)); + return tracker; + } + + final class PlayerConnectionImpl extends PlayerConnection { + @Override + public void sendPacket(@NotNull SendablePacket packet) { + for (var tracker : incomingTrackers) { + final var serverPacket = SendablePacket.extractServerPacket(packet); + if (tracker.type.isAssignableFrom(serverPacket.getClass())) tracker.packets.add(serverPacket); + } + } + + @Override + public @NotNull SocketAddress getRemoteAddress() { + return new InetSocketAddress("localhost", 25565); + } + + @Override + public void disconnect() { + + } + } + + final class TrackerImpl implements PacketTracker { + private final Class type; + private final List packets = new CopyOnWriteArrayList<>(); + + public TrackerImpl(Class type) { + this.type = type; + } + + @Override + public @NotNull List collect() { + incomingTrackers.remove(this); + return List.copyOf(packets); + } + } +} diff --git a/src/test/java/net/minestom/server/entity/EntityInstanceIntegrationTest.java b/src/test/java/net/minestom/server/entity/EntityInstanceIntegrationTest.java new file mode 100644 index 000000000..9b15bac01 --- /dev/null +++ b/src/test/java/net/minestom/server/entity/EntityInstanceIntegrationTest.java @@ -0,0 +1,45 @@ +package net.minestom.server.entity; + +import net.minestom.server.api.Env; +import net.minestom.server.api.EnvTest; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.play.JoinGamePacket; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnvTest +public class EntityInstanceIntegrationTest { + + @Test + public void entityJoin(Env env) { + var instance = env.createFlatInstance(); + var entity = new Entity(EntityTypes.ZOMBIE); + entity.setInstance(instance, new Pos(0, 42, 0)).join(); + assertEquals(instance, entity.getInstance()); + } + + @Test + public void playerJoin(Env env) { + var instance = env.createFlatInstance(); + var connection = env.createConnection(); + var player = connection.connect(instance, new Pos(0, 42, 0)).join(); + assertEquals(instance, player.getInstance()); + } + + @Test + public void playerJoinPacket(Env env) { + var instance = env.createFlatInstance(); + var connection = env.createConnection(); + var tracker = connection.trackIncoming(JoinGamePacket.class); + var tracker2 = connection.trackIncoming(ServerPacket.class); + var player = connection.connect(instance, new Pos(0, 40, 0)).join(); + assertEquals(instance, player.getInstance()); + assertEquals(new Pos(0, 40, 0), player.getPosition()); + + assertEquals(1, tracker.collect().size()); + assertTrue(tracker2.collect().size() > 1); + } +} diff --git a/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java b/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java new file mode 100644 index 000000000..5428b6b15 --- /dev/null +++ b/src/test/java/net/minestom/server/entity/EntityTeleportIntegrationTest.java @@ -0,0 +1,90 @@ +package net.minestom.server.entity; + +import net.minestom.server.api.Env; +import net.minestom.server.api.EnvTest; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.play.EntityTeleportPacket; +import net.minestom.server.network.packet.server.play.PlayerPositionAndLookPacket; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@EnvTest +public class EntityTeleportIntegrationTest { + + @Test + public void entityChunkTeleport(Env env) { + var instance = env.createFlatInstance(); + var entity = new Entity(EntityTypes.ZOMBIE); + entity.setInstance(instance, new Pos(0, 42, 0)).join(); + assertEquals(instance, entity.getInstance()); + assertEquals(new Pos(0, 42, 0), entity.getPosition()); + + entity.teleport(new Pos(1, 42, 1)).join(); + assertEquals(new Pos(1, 42, 1), entity.getPosition()); + } + + @Test + public void entityTeleport(Env env) { + var instance = env.createFlatInstance(); + var entity = new Entity(EntityTypes.ZOMBIE); + entity.setInstance(instance, new Pos(0, 42, 0)).join(); + assertEquals(instance, entity.getInstance()); + assertEquals(new Pos(0, 42, 0), entity.getPosition()); + + entity.teleport(new Pos(52, 42, 52)).join(); + assertEquals(new Pos(52, 42, 52), entity.getPosition()); + } + + @Test + public void playerChunkTeleport(Env env) { + var instance = env.createFlatInstance(); + var connection = env.createConnection(); + var player = connection.connect(instance, new Pos(0, 40, 0)).join(); + assertEquals(instance, player.getInstance()); + assertEquals(new Pos(0, 40, 0), player.getPosition()); + + var viewerConnection = env.createConnection(); + viewerConnection.connect(instance, new Pos(0, 40, 0)).join(); + + var tracker = connection.trackIncoming(ServerPacket.class); + var viewerTracker = viewerConnection.trackIncoming(ServerPacket.class); + var teleportPosition = new Pos(1, 42, 1); + player.teleport(teleportPosition).join(); + assertEquals(teleportPosition, player.getPosition()); + + // Verify received packet(s) + { + var packets = tracker.collect(); + assertEquals(1, packets.size()); + var packet = ((PlayerPositionAndLookPacket) packets.get(0)); + assertEquals(teleportPosition, packet.position()); + } + + // Verify broadcast packet(s) + { + var packets = viewerTracker.collect(); + assertEquals(1, packets.size()); + var packet = ((EntityTeleportPacket) packets.get(0)); + assertEquals(player.getEntityId(), packet.entityId()); + assertEquals(teleportPosition, packet.position()); + } + } + + @Test + public void playerTeleport(Env env) { + var instance = env.createFlatInstance(); + var connection = env.createConnection(); + var player = connection.connect(instance, new Pos(0, 40, 0)).join(); + assertEquals(instance, player.getInstance()); + assertEquals(new Pos(0, 40, 0), player.getPosition()); + + var viewerConnection = env.createConnection(); + viewerConnection.connect(instance, new Pos(0, 40, 0)).join(); + + var teleportPosition = new Pos(4999, 42, 4999); + player.teleport(teleportPosition).join(); + assertEquals(teleportPosition, player.getPosition()); + } +} diff --git a/src/test/java/net/minestom/server/entity/EntityViewIntegrationTest.java b/src/test/java/net/minestom/server/entity/EntityViewIntegrationTest.java new file mode 100644 index 000000000..b0022c38c --- /dev/null +++ b/src/test/java/net/minestom/server/entity/EntityViewIntegrationTest.java @@ -0,0 +1,114 @@ +package net.minestom.server.entity; + +import net.minestom.server.api.Env; +import net.minestom.server.api.EnvTest; +import net.minestom.server.coordinate.Pos; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnvTest +public class EntityViewIntegrationTest { + + @Test + public void emptyEntity(Env env) { + var instance = env.createFlatInstance(); + var entity = new Entity(EntityType.ZOMBIE); + entity.setInstance(instance, new Pos(0, 40, 42)).join(); + assertEquals(0, entity.getViewers().size()); + } + + @Test + public void emptyPlayer(Env env) { + var instance = env.createFlatInstance(); + var player = env.createPlayer(instance, new Pos(0, 42, 0)); + assertEquals(0, player.getViewers().size()); + } + + @Test + public void multiPlayers(Env env) { + var instance = env.createFlatInstance(); + var p1 = env.createPlayer(instance, new Pos(0, 42, 42)); + var p2 = env.createPlayer(instance, new Pos(0, 42, 42)); + + assertEquals(1, p1.getViewers().size()); + p1.getViewers().forEach(p -> assertEquals(p2, p)); + + assertEquals(1, p2.getViewers().size()); + p2.getViewers().forEach(p -> assertEquals(p1, p)); + + p2.remove(); + assertEquals(0, p1.getViewers().size()); + assertEquals(0, p2.getViewers().size()); + + var p3 = env.createPlayer(instance, new Pos(0, 42, 42)); + assertEquals(1, p1.getViewers().size()); + p1.getViewers().forEach(p -> assertEquals(p3, p)); + } + + @Test + public void movements(Env env) { + var instance = env.createFlatInstance(); + var p1 = env.createPlayer(instance, new Pos(0, 42, 0)); + var p2 = env.createPlayer(instance, new Pos(0, 42, 96)); + + assertEquals(0, p1.getViewers().size()); + assertEquals(0, p2.getViewers().size()); + + p2.teleport(new Pos(0, 42, 95)).join(); // Teleport in range (6 chunks) + assertEquals(1, p1.getViewers().size()); + assertEquals(1, p2.getViewers().size()); + } + + @Test + public void autoViewable(Env env) { + var instance = env.createFlatInstance(); + var p1 = env.createPlayer(instance, new Pos(0, 42, 0)); + assertTrue(p1.isAutoViewable()); + p1.setAutoViewable(false); + + var p2 = env.createPlayer(instance, new Pos(0, 42, 0)); + + assertEquals(0, p1.getViewers().size()); + assertEquals(1, p2.getViewers().size()); + + p1.setAutoViewable(true); + assertEquals(1, p1.getViewers().size()); + assertEquals(1, p2.getViewers().size()); + } + + @Test + public void viewableRule(Env env) { + var instance = env.createFlatInstance(); + var p1 = env.createPlayer(instance, new Pos(0, 42, 0)); + p1.updateViewableRule(player -> player.getEntityId() == p1.getEntityId() + 1); + + var p2 = env.createPlayer(instance, new Pos(0, 42, 0)); + + assertEquals(1, p1.getViewers().size()); + assertEquals(1, p2.getViewers().size()); + + p1.updateViewableRule(player -> false); + + assertEquals(0, p1.getViewers().size()); + assertEquals(1, p2.getViewers().size()); + } + + @Test + public void viewerRule(Env env) { + var instance = env.createFlatInstance(); + var p1 = env.createPlayer(instance, new Pos(0, 42, 0)); + p1.updateViewerRule(player -> player.getEntityId() == p1.getEntityId() + 1); + + var p2 = env.createPlayer(instance, new Pos(0, 42, 0)); + + assertEquals(1, p1.getViewers().size()); + assertEquals(1, p2.getViewers().size()); + + p1.updateViewerRule(player -> false); + + assertEquals(1, p1.getViewers().size()); + assertEquals(0, p2.getViewers().size()); + } +} diff --git a/src/test/java/net/minestom/server/inventory/InventoryTest.java b/src/test/java/net/minestom/server/inventory/InventoryTest.java index c0314ecba..fa3f976bc 100644 --- a/src/test/java/net/minestom/server/inventory/InventoryTest.java +++ b/src/test/java/net/minestom/server/inventory/InventoryTest.java @@ -1,9 +1,7 @@ package net.minestom.server.inventory; import net.kyori.adventure.text.Component; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.TransactionOption; +import net.minestom.server.MinecraftServer; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import org.junit.jupiter.api.Test; @@ -12,6 +10,11 @@ import static org.junit.jupiter.api.Assertions.*; public class InventoryTest { + static { + // Required to prevent initialization error during event call + MinecraftServer.init(); + } + @Test public void testCreation() { Inventory inventory = new Inventory(InventoryType.CHEST_1_ROW, "title");