From db9d8e33959f1caa89365b4ce8bbbf71160b49ae Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 16 Nov 2024 20:06:54 -0800 Subject: [PATCH] New version 1.14.0 (#17) * Update pom.xml * Update to Minecraft 1.18 biome changes * Update pom.xml 1.13.0 * Update to latest Minecraft API * Added test class * Add distribution management section to POM * Update to MC 1.21.4 and CodeMC changes (#16) --------- Co-authored-by: BONNe --- pom.xml | 18 +-- .../listeners/MobsSpawnListenerTest.java | 13 +- .../listeners/mocks/ServerMocks.java | 118 ++++++++++++++++++ 3 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 src/test/java/world/bentobox/extramobs/listeners/mocks/ServerMocks.java diff --git a/pom.xml b/pom.xml index 9d261cf..5331fcd 100644 --- a/pom.xml +++ b/pom.xml @@ -30,13 +30,9 @@ - - codemc-snapshots - https://repo.codemc.org/repository/maven-snapshots - - codemc-releases - https://repo.codemc.org/repository/maven-releases + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld @@ -46,8 +42,8 @@ 17 2.0.9 - 1.20.4-R0.1-SNAPSHOT - 2.0.0-SNAPSHOT + 1.21.3-R0.1-SNAPSHOT + 2.7.1-SNAPSHOT ${build.version}-SNAPSHOT @@ -56,7 +52,7 @@ X.Y.Z -> BentoBox core version .M -> Addon development iteration. --> - 1.13 + 1.14.0 -LOCAL @@ -107,6 +103,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots + + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + codemc https://repo.codemc.org/repository/maven-snapshots/ diff --git a/src/test/java/world/bentobox/extramobs/listeners/MobsSpawnListenerTest.java b/src/test/java/world/bentobox/extramobs/listeners/MobsSpawnListenerTest.java index d5de2d3..dac2c30 100644 --- a/src/test/java/world/bentobox/extramobs/listeners/MobsSpawnListenerTest.java +++ b/src/test/java/world/bentobox/extramobs/listeners/MobsSpawnListenerTest.java @@ -18,18 +18,22 @@ import org.bukkit.entity.Fish; import org.bukkit.event.entity.CreatureSpawnEvent; import org.eclipse.jdt.annotation.NonNull; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.AddonDescription; import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.extramobs.ExtraMobsAddon; import world.bentobox.extramobs.config.Settings; +import world.bentobox.extramobs.listeners.mocks.ServerMocks; @RunWith(PowerMockRunner.class) public class MobsSpawnListenerTest { @@ -64,6 +68,7 @@ public class MobsSpawnListenerTest { @Before public void setUp() { + ServerMocks.newServer(); settings = new Settings(); when(addon.getSettings()).thenReturn(settings); @@ -93,6 +98,13 @@ public void setUp() { listener = new MobsSpawnListener(addon); } + @After + public void tearDown() { + ServerMocks.unsetBukkitServer(); + User.clearUsers(); + Mockito.framework().clearInlineMocks(); + } + // Test case for natural spawning of Zombified Piglin in the Nether @Test public void testNaturalSpawnZombifiedPiglinNether() { @@ -136,7 +148,6 @@ public void testFishSpawnDeepOcean() { when(event.getSpawnReason()).thenReturn(CreatureSpawnEvent.SpawnReason.NATURAL); when(event.getLocation()).thenReturn(location); when(world.getEnvironment()).thenReturn(World.Environment.NORMAL); - when(world.getBiome(anyInt(), anyInt(), anyInt())).thenReturn(Biome.DEEP_OCEAN); settings.setGuardianChance(1.1); // Set so that it will always spawn when(block.getType()).thenReturn(Material.WATER, Material.WATER, Material.WATER, Material.PRISMARINE); diff --git a/src/test/java/world/bentobox/extramobs/listeners/mocks/ServerMocks.java b/src/test/java/world/bentobox/extramobs/listeners/mocks/ServerMocks.java new file mode 100644 index 0000000..c8c12a5 --- /dev/null +++ b/src/test/java/world/bentobox/extramobs/listeners/mocks/ServerMocks.java @@ -0,0 +1,118 @@ +package world.bentobox.extramobs.listeners.mocks; + +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Server; +import org.bukkit.Tag; +import org.bukkit.UnsafeValues; +import org.eclipse.jdt.annotation.NonNull; + +public final class ServerMocks { + + public static @NonNull Server newServer() { + Server mock = mock(Server.class); + + Logger noOp = mock(Logger.class); + when(mock.getLogger()).thenReturn(noOp); + when(mock.isPrimaryThread()).thenReturn(true); + + // Unsafe + UnsafeValues unsafe = mock(UnsafeValues.class); + when(mock.getUnsafe()).thenReturn(unsafe); + + // Server must be available before tags can be mocked. + Bukkit.setServer(mock); + + // Bukkit has a lot of static constants referencing registry values. To initialize those, the + // registries must be able to be fetched before the classes are touched. + Map, Object> registers = new HashMap<>(); + + doAnswer(invocationGetRegistry -> registers.computeIfAbsent(invocationGetRegistry.getArgument(0), clazz -> { + Registry registry = mock(Registry.class); + Map cache = new HashMap<>(); + doAnswer(invocationGetEntry -> { + NamespacedKey key = invocationGetEntry.getArgument(0); + // Some classes (like BlockType and ItemType) have extra generics that will be + // erased during runtime calls. To ensure accurate typing, grab the constant's field. + // This approach also allows us to return null for unsupported keys. + Class constantClazz; + try { + //noinspection unchecked + constantClazz = (Class) clazz + .getField(key.getKey().toUpperCase(Locale.ROOT).replace('.', '_')).getType(); + } catch (ClassCastException e) { + throw new RuntimeException(e); + } catch (NoSuchFieldException e) { + return null; + } + + return cache.computeIfAbsent(key, key1 -> { + Keyed keyed = mock(constantClazz); + doReturn(key).when(keyed).getKey(); + return keyed; + }); + }).when(registry).get(notNull()); + return registry; + })).when(mock).getRegistry(notNull()); + + // Tags are dependent on registries, but use a different method. + // This will set up blank tags for each constant; all that needs to be done to render them + // functional is to re-mock Tag#getValues. + doAnswer(invocationGetTag -> { + Tag tag = mock(Tag.class); + doReturn(invocationGetTag.getArgument(1)).when(tag).getKey(); + doReturn(Set.of()).when(tag).getValues(); + doAnswer(invocationIsTagged -> { + Keyed keyed = invocationIsTagged.getArgument(0); + Class type = invocationGetTag.getArgument(2); + if (!type.isAssignableFrom(keyed.getClass())) { + return null; + } + // Since these are mocks, the exact instance might not be equal. Consider equal keys equal. + return tag.getValues().contains(keyed) + || tag.getValues().stream().anyMatch(value -> value.getKey().equals(keyed.getKey())); + }).when(tag).isTagged(notNull()); + return tag; + }).when(mock).getTag(notNull(), notNull(), notNull()); + + // Once the server is all set up, touch BlockType and ItemType to initialize. + // This prevents issues when trying to access dependent methods from a Material constant. + try { + Class.forName("org.bukkit.inventory.ItemType"); + Class.forName("org.bukkit.block.BlockType"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + return mock; + } + + public static void unsetBukkitServer() { + try { + Field server = Bukkit.class.getDeclaredField("server"); + server.setAccessible(true); + server.set(null, null); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private ServerMocks() { + } + +} \ No newline at end of file