diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 35b2b32..6fd3a4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,24 +1,23 @@ -name: SonarCloud +name: Build on: push: branches: - develop - - master pull_request: types: [opened, synchronize, reopened] jobs: build: - name: Build and analyze + name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: distribution: 'adopt' - java-version: 17 + java-version: '21' - name: Cache SonarCloud packages uses: actions/cache@v3 with: @@ -35,4 +34,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=BentoBoxWorld_AcidIsland \ No newline at end of file + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=BentoBoxWorld_AcidIsland + - run: mvn --batch-mode clean org.jacoco:jacoco-maven-plugin:prepare-agent install + - run: mkdir staging && cp target/*.jar staging + - name: Save artifacts + uses: actions/upload-artifact@v3 + with: + name: Package + path: staging + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 77f934b..bac3da5 100644 --- a/pom.xml +++ b/pom.xml @@ -41,31 +41,27 @@ - - 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/ UTF-8 UTF-8 - 17 + 21 2.0.9 - 1.20.4-R0.1-SNAPSHOT - 2.5.0-SNAPSHOT + 1.21.3-R0.1-SNAPSHOT + 2.7.1-SNAPSHOT ${build.version}-SNAPSHOT -LOCAL - 1.19.0 + 1.20.0 BentoBoxWorld_AcidIsland bentobox-world @@ -121,11 +117,7 @@ codemc-repo - https://repo.codemc.org/repository/maven-public/ - - - codemc - https://repo.codemc.org/repository/maven-snapshots/ + https://repo.codemc.io/repository/bentoboxworld/ ess-repo diff --git a/src/main/java/world/bentobox/acidisland/AISettings.java b/src/main/java/world/bentobox/acidisland/AISettings.java index 2dc9988..ad3f084 100644 --- a/src/main/java/world/bentobox/acidisland/AISettings.java +++ b/src/main/java/world/bentobox/acidisland/AISettings.java @@ -260,7 +260,7 @@ public class AISettings implements WorldSettings { private Biome defaultBiome = Biome.WARM_OCEAN; @ConfigComment("The default biome for the nether world (this may affect what mobs can spawn)") @ConfigEntry(path = "world.default-nether-biome") - private Biome defaultNetherBiome = Enums.getIfPresent(Biome.class, "NETHER").or(Enums.getIfPresent(Biome.class, "NETHER_WASTES").or(Biome.BADLANDS)); + private Biome defaultNetherBiome = Biome.NETHER_WASTES; @ConfigComment("The default biome for the end world (this may affect what mobs can spawn)") @ConfigEntry(path = "world.default-end-biome") private Biome defaultEndBiome = Biome.THE_END; diff --git a/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java b/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java index c0f0806..aa1c2c9 100644 --- a/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java +++ b/src/main/java/world/bentobox/acidisland/listeners/AcidEffect.java @@ -12,7 +12,6 @@ import org.bukkit.Sound; import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.block.BlockFace; -import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -56,9 +55,9 @@ public class AcidEffect implements Listener { private static final List EFFECTS; static { if (!inTest()) { - EFFECTS = List.of(PotionEffectType.BLINDNESS, PotionEffectType.CONFUSION, PotionEffectType.HUNGER, - PotionEffectType.SLOW, PotionEffectType.SLOW_DIGGING, PotionEffectType.WEAKNESS, - PotionEffectType.POISON); + EFFECTS = List.of(PotionEffectType.BLINDNESS, PotionEffectType.NAUSEA, PotionEffectType.HUNGER, + PotionEffectType.SLOWNESS, PotionEffectType.MINING_FATIGUE, PotionEffectType.WEAKNESS, + PotionEffectType.POISON, PotionEffectType.DARKNESS, PotionEffectType.UNLUCK); } else { EFFECTS = List.of(); } @@ -294,8 +293,8 @@ public class AcidEffect implements Listener { return true; } // Check if player is on a boat - if (player.getVehicle() != null && (player.getVehicle().getType().equals(EntityType.BOAT) - || player.getVehicle().getType().equals(EntityType.CHEST_BOAT))) { + if (player.getVehicle() != null && (player.getVehicle().getType().getKey().getKey().contains("boat") + || player.getVehicle().getType().getKey().getKey().contains("raft"))) { // I'M ON A BOAT! I'M ON A BOAT! A %^&&* BOAT! SNL Sketch. https://youtu.be/avaSdC0QOUM. return true; } @@ -330,7 +329,7 @@ public class AcidEffect implements Listener { */ public static double getDamageReduced(LivingEntity le) { // Full diamond armor value = 20. This normalizes it to a max of 0.8. Enchantments can raise it out further. - double red = le.getAttribute(Attribute.GENERIC_ARMOR).getValue() * 0.04; + double red = le.getAttribute(Attribute.ARMOR).getValue() * 0.04; EntityEquipment inv = le.getEquipment(); ItemStack boots = inv.getBoots(); ItemStack helmet = inv.getHelmet(); diff --git a/src/test/java/world/bentobox/acidisland/AISettingsTest.java b/src/test/java/world/bentobox/acidisland/AISettingsTest.java index a6fb573..defc3c9 100644 --- a/src/test/java/world/bentobox/acidisland/AISettingsTest.java +++ b/src/test/java/world/bentobox/acidisland/AISettingsTest.java @@ -7,6 +7,7 @@ import static org.junit.Assert.assertTrue; import java.util.Collections; import java.util.List; +import org.bukkit.Bukkit; import org.bukkit.Difficulty; import org.bukkit.GameMode; import org.bukkit.block.Biome; @@ -14,15 +15,24 @@ import org.bukkit.entity.EntityType; import org.bukkit.potion.PotionEffectType; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import world.bentobox.acidisland.mocks.ServerMocks; import world.bentobox.bentobox.lists.Flags; /** * @author tastybento * */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class }) public class AISettingsTest { /** @@ -30,6 +40,11 @@ public class AISettingsTest { */ private AISettings s; + @BeforeClass + public static void beforeClass() { + ServerMocks.newServer(); + } + /** * @throws java.lang.Exception */ diff --git a/src/test/java/world/bentobox/acidisland/AcidIslandTest.java b/src/test/java/world/bentobox/acidisland/AcidIslandTest.java index a72fc34..c8c22ec 100644 --- a/src/test/java/world/bentobox/acidisland/AcidIslandTest.java +++ b/src/test/java/world/bentobox/acidisland/AcidIslandTest.java @@ -46,6 +46,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; +import world.bentobox.acidisland.mocks.ServerMocks; import world.bentobox.acidisland.world.ChunkGeneratorWorld; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; @@ -104,6 +105,7 @@ public class AcidIslandTest { when(DatabaseSetup.getDatabase()).thenReturn(dbSetup); when(dbSetup.getHandler(any())).thenReturn(h); when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true)); + ServerMocks.newServer(); } @After diff --git a/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java b/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java index 22d55b2..ac9520f 100644 --- a/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java +++ b/src/test/java/world/bentobox/acidisland/listeners/AcidEffectTest.java @@ -24,6 +24,7 @@ import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; import org.bukkit.block.Block; import org.bukkit.entity.Boat; import org.bukkit.entity.Entity; @@ -44,6 +45,7 @@ import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.util.Vector; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -59,6 +61,7 @@ import com.earth2me.essentials.User; import world.bentobox.acidisland.AISettings; import world.bentobox.acidisland.AcidIsland; +import world.bentobox.acidisland.mocks.ServerMocks; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; @@ -118,8 +121,11 @@ public class AcidEffectTest { @Mock private Server server; - /** - */ + @BeforeClass + public static void beforeClass() { + ServerMocks.newServer(); + } + @Before public void setUp() { PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); @@ -480,7 +486,7 @@ public class AcidEffectTest { public void testOnPlayerMoveInBoat() { when(settings.getAcidRainDamage()).thenReturn(0); Entity boat = mock(Boat.class); - when(boat.getType()).thenReturn(EntityType.BOAT); + when(boat.getType()).thenReturn(EntityType.ACACIA_BOAT); when(player.getVehicle()).thenReturn(boat); PlayerMoveEvent e = new PlayerMoveEvent(player, from, to); ae.onPlayerMove(e); @@ -574,10 +580,11 @@ public class AcidEffectTest { */ @Test public void testGetDamageReducedFullDiamond() { - AttributeInstance value = mock(AttributeInstance.class); - when(value.getValue()).thenReturn(20D); + Att value = new Att(); + //AttributeInstance value = new AttributeInstance(); + //when(value.getValue()).thenReturn(20D); // Diamond armor - when(player.getAttribute(eq(Attribute.GENERIC_ARMOR))).thenReturn(value); + when(player.getAttribute(eq(Attribute.ARMOR))).thenReturn(value); EntityEquipment equip = mock(EntityEquipment.class); when(equip.getBoots()).thenReturn(new ItemStack(Material.DIAMOND_BOOTS)); when(equip.getHelmet()).thenReturn(new ItemStack(Material.DIAMOND_HELMET)); @@ -589,6 +596,57 @@ public class AcidEffectTest { } + class Att implements AttributeInstance { + + @Override + public Attribute getAttribute() { + // TODO Auto-generated method stub + return null; + } + + @Override + public double getBaseValue() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setBaseValue(double value) { + // TODO Auto-generated method stub + + } + + @Override + public Collection getModifiers() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void addModifier(AttributeModifier modifier) { + // TODO Auto-generated method stub + + } + + @Override + public void removeModifier(AttributeModifier modifier) { + // TODO Auto-generated method stub + + } + + @Override + public double getValue() { + return 20; + } + + @Override + public double getDefaultValue() { + // TODO Auto-generated method stub + return 0; + } + + } + /** * Test method for {@link world.bentobox.acidisland.listeners.AcidEffect#checkForRain(Player)}. */ @@ -629,7 +687,7 @@ public class AcidEffectTest { AttributeInstance value = mock(AttributeInstance.class); when(value.getValue()).thenReturn(20D); // Diamond armor - when(player.getAttribute(eq(Attribute.GENERIC_ARMOR))).thenReturn(value); + when(player.getAttribute(eq(Attribute.ARMOR))).thenReturn(value); EntityEquipment equip = mock(EntityEquipment.class); when(equip.getBoots()).thenReturn(new ItemStack(Material.DIAMOND_BOOTS)); when(equip.getHelmet()).thenReturn(new ItemStack(Material.DIAMOND_HELMET)); @@ -672,7 +730,7 @@ public class AcidEffectTest { public void testIsSafeFromAcidBoat() { when(player.isInsideVehicle()).thenReturn(true); Entity boat = mock(Entity.class); - when(boat.getType()).thenReturn(EntityType.BOAT); + when(boat.getType()).thenReturn(EntityType.ACACIA_BOAT); when(player.getVehicle()).thenReturn(boat); assertTrue(ae.isSafeFromAcid(player)); } @@ -684,7 +742,7 @@ public class AcidEffectTest { public void testIsSafeFromAcidChestBoat() { when(player.isInsideVehicle()).thenReturn(true); Entity boat = mock(Entity.class); - when(boat.getType()).thenReturn(EntityType.CHEST_BOAT); + when(boat.getType()).thenReturn(EntityType.ACACIA_CHEST_BOAT); when(player.getVehicle()).thenReturn(boat); assertTrue(ae.isSafeFromAcid(player)); } diff --git a/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java b/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java index 2be518e..3999ad5 100644 --- a/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java +++ b/src/test/java/world/bentobox/acidisland/listeners/LavaCheckTest.java @@ -19,6 +19,7 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.plugin.PluginManager; import org.bukkit.scheduler.BukkitScheduler; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -33,6 +34,7 @@ import com.earth2me.essentials.User; import world.bentobox.acidisland.AISettings; import world.bentobox.acidisland.AcidIsland; +import world.bentobox.acidisland.mocks.ServerMocks; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; @@ -86,8 +88,11 @@ public class LavaCheckTest { private LavaCheck lc; - /** - */ + @BeforeClass + public static void beforeClass() { + ServerMocks.newServer(); + } + @Before public void setUp() { PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); diff --git a/src/test/java/world/bentobox/acidisland/mocks/ServerMocks.java b/src/test/java/world/bentobox/acidisland/mocks/ServerMocks.java new file mode 100644 index 0000000..a81215c --- /dev/null +++ b/src/test/java/world/bentobox/acidisland/mocks/ServerMocks.java @@ -0,0 +1,118 @@ +package world.bentobox.acidisland.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 diff --git a/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java b/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java index f8b300f..b0648c6 100644 --- a/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java +++ b/src/test/java/world/bentobox/acidisland/world/AcidTaskTest.java @@ -34,6 +34,7 @@ import org.bukkit.scheduler.BukkitTask; import org.eclipse.jdt.annotation.Nullable; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -44,6 +45,7 @@ import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.acidisland.AISettings; import world.bentobox.acidisland.AcidIsland; +import world.bentobox.acidisland.mocks.ServerMocks; /** * @author tastybento @@ -77,9 +79,11 @@ public class AcidTaskTest { @Mock private Location l; + @BeforeClass + public static void beforeClass() { + ServerMocks.newServer(); + } - /** - */ @Before public void setUp() { PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS); @@ -116,7 +120,7 @@ public class AcidTaskTest { mob.add(mock(Cod.class)); Item i = mock(Item.class); when(i.getLocation()).thenReturn(l); - when(i.getType()).thenReturn(EntityType.DROPPED_ITEM); + when(i.getType()).thenReturn(EntityType.ITEM); when(i.getWorld()).thenReturn(world); mob.add(i); when(world.getEntities()).thenReturn(mob); @@ -176,7 +180,7 @@ public class AcidTaskTest { at.setItemsInWater(map); at.applyDamage(e, 0); - verify(world).playSound(eq(l), any(Sound.class), anyFloat(), anyFloat()); + verify(world).playSound(eq(l), (Sound) Mockito.isNull(), anyFloat(), anyFloat()); verify(e).remove(); assertTrue(map.isEmpty()); } diff --git a/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java b/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java index fc7239f..ae4f3d5 100644 --- a/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java +++ b/src/test/java/world/bentobox/acidisland/world/ChunkGeneratorWorldTest.java @@ -1,24 +1,23 @@ package world.bentobox.acidisland.world; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.bukkit.Bukkit; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.generator.ChunkGenerator.ChunkData; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.acidisland.AISettings; import world.bentobox.acidisland.AcidIsland; +import world.bentobox.acidisland.mocks.ServerMocks; /** * @author tastybento @@ -33,19 +32,18 @@ public class ChunkGeneratorWorldTest { private ChunkGeneratorWorld cg; @Mock private World world; + private AISettings settings; @Mock private ChunkData data; - /** - */ + @BeforeClass + public static void beforeClass() { + ServerMocks.newServer(); + } + @Before public void setUp() { - // Bukkit - PowerMockito.mockStatic(Bukkit.class); - Server server = mock(Server.class); - when(server.createChunkData(any())).thenReturn(data); - when(Bukkit.getServer()).thenReturn(server); // World when(world.getEnvironment()).thenReturn(World.Environment.NORMAL); when(world.getMaxHeight()).thenReturn(256);