diff --git a/build.gradle b/build.gradle index f99d1f1..234a863 100644 --- a/build.gradle +++ b/build.gradle @@ -86,6 +86,10 @@ runtime 'mysql:mysql-connector-java:5.1.31' compileOnly ('net.milkbowl.vault:VaultAPI:1.6') { transitive = false } + + + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:2.+' } ext.mixinSrg = file("$buildDir/mixin/mcp2srg.srg") diff --git a/src/test/java/org/ultramine/libtest/BlockPos.java b/src/test/java/org/ultramine/libtest/BlockPos.java new file mode 100644 index 0000000..44ba67e --- /dev/null +++ b/src/test/java/org/ultramine/libtest/BlockPos.java @@ -0,0 +1,100 @@ +package org.ultramine.libtest; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.ChunkPosition; +import net.minecraftforge.common.util.ForgeDirection; +import org.bukkit.util.Vector; + +import javax.annotation.Nonnull; + +public class BlockPos +{ + public static final BlockPos ZERO = new BlockPos(0, 0, 0); + + private final int x; + private final int y; + private final int z; + + public BlockPos(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public int getX() + { + return x; + } + + public int getY() + { + return y; + } + + public int getZ() + { + return z; + } + + @Nonnull + public BlockPos step(ForgeDirection d) + { + return new BlockPos(x + d.offsetX, y + d.offsetY, z + d.offsetZ); + } + + @Nonnull + public BlockPos stepBack(ForgeDirection d) + { + return new BlockPos(x - d.offsetX, y - d.offsetY, z - d.offsetZ); + } + + public boolean isInAABB(BlockPos min, BlockPos max) + { + return x >= min.getX() && x <= max.getX() && y >= min.getY() && y <= max.getY() && z >= min.getZ() && z <= max.getZ(); + } + + @Nonnull + public static BlockPos fromChunkPosition(ChunkPosition block) + { + return new BlockPos(block.chunkPosX, block.chunkPosY, block.chunkPosZ); + } + + @Nonnull + public static BlockPos fromTileEntity(TileEntity block) + { + return new BlockPos(block.xCoord, block.yCoord, block.zCoord); + } + + @Nonnull + public Vector toVector() + { + return new Vector(x, y, z); + } + + @Override + public boolean equals(Object o) + { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; + + BlockPos blockPos = (BlockPos) o; + + return x == blockPos.x && y == blockPos.y && z == blockPos.z; + } + + @Override + public int hashCode() + { + int result = x; + result = 31 * result + y; + result = 31 * result + z; + return result; + } + + @Override + public String toString() + { + return "BlockPos(" + x + ", " + y + ", " + z + ")"; + } +} diff --git a/src/test/java/org/ultramine/libtest/UBlockState.java b/src/test/java/org/ultramine/libtest/UBlockState.java new file mode 100644 index 0000000..7f2020e --- /dev/null +++ b/src/test/java/org/ultramine/libtest/UBlockState.java @@ -0,0 +1,95 @@ +package org.ultramine.libtest; + +import net.minecraft.block.Block; +import net.minecraftforge.oredict.OreDictionary; + +import javax.annotation.Nonnull; +import java.util.Objects; + +public class UBlockState +{ + @Nonnull private final Block block; + @Nonnull private final int meta; + + public UBlockState(@Nonnull Block block, int meta) + { + if(meta != OreDictionary.WILDCARD_VALUE) //allow wildcard value + checkBlockMeta(meta); + this.block = block; + this.meta = meta; + } + + public UBlockState(Block block) + { + this(block, 0); + } + + @Nonnull + public Block getType() + { + return block; + } + + public int getMeta() + { + return meta; + } + + public boolean isType(Block block) + { + return Block.isEqualTo(getType(), block); + } + + public boolean isType(Block block, int meta) + { + return isType(block) && (this.meta == meta || meta == OreDictionary.WILDCARD_VALUE || this.meta == OreDictionary.WILDCARD_VALUE); + } + + public boolean isType(UBlockState other) + { + return isType(other.getType()) && (this.meta == other.meta || this.meta == OreDictionary.WILDCARD_VALUE || other.meta == OreDictionary.WILDCARD_VALUE); + } + + @Nonnull + public UBlockState withMeta(int meta) + { + return new UBlockState(block, meta); + } + + @Override + public int hashCode() + { + return Block.getIdFromBlock(block); //ignoring metadata in hash + } + + @Override + public boolean equals(Object o) + { + if(this == o) + return true; + if(o == null || getClass() != o.getClass()) + return false; + UBlockState other = (UBlockState) o; + return Objects.equals(this.block, other.block) && (this.meta == other.meta + || this.meta == OreDictionary.WILDCARD_VALUE || other.meta == OreDictionary.WILDCARD_VALUE); + } + + public static UBlockState of(Object o) + { + if (o == null) + throw new NullPointerException(); + + if (o instanceof UBlockState) + return (UBlockState) o; + else if (o instanceof Block) + return new UBlockState((Block) o, 0); + + throw new IllegalArgumentException("Unknown block type: " + o.getClass()); + } + + private static void checkBlockMeta(int meta) throws IllegalArgumentException + { + if(meta > 15 || meta < 0) + throw new IllegalArgumentException("Block meta cat't be more then 15 or less then 0. Given: " + meta); + } +} diff --git a/src/test/java/org/ultramine/libtest/WorldBuilder.java b/src/test/java/org/ultramine/libtest/WorldBuilder.java new file mode 100644 index 0000000..91471ca --- /dev/null +++ b/src/test/java/org/ultramine/libtest/WorldBuilder.java @@ -0,0 +1,96 @@ +package org.ultramine.libtest; + +import net.minecraft.world.WorldServer; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +public class WorldBuilder +{ + @Nonnull private final WorldServer world; + @Nonnull private final BlockPos start; + @Nonnull private final String[] lines; + @Nonnull private final Map blockTypes = new HashMap<>(); + + public WorldBuilder(@Nonnull WorldServer world, @Nonnull BlockPos start, @Nonnull String[] lines) + { + this.world = world; + this.start = start; + this.lines = lines; + } + + @Nonnull + public WorldBuilder set(char ch, Object block) + { + blockTypes.put(ch, UBlockState.of(block)); + return this; + } + + @Nonnull + public WorldBuilder set(char ch1, Object block1, char ch2, Object block2) + { + set(ch1, block1); + set(ch2, block2); + return this; + } + + @Nonnull + public WorldBuilder set(char ch1, Object block1, char ch2, Object block2, char ch3, Object block3) + { + set(ch1, block1); + set(ch2, block2); + set(ch3, block3); + return this; + } + + @Nonnull + public WorldBuilder set(char ch1, Object block1, char ch2, Object block2, char ch3, Object block3, char ch4, Object block4) + { + set(ch1, block1); + set(ch2, block2); + set(ch3, block3); + set(ch4, block4); + return this; + } + + @Nonnull + public BlockPos posOf(char block) + { + for (int i = lines.length - 1; i >= 0; i--) + { + int y = lines.length - 1 - i; + String line = lines[i]; + for (int x = 0; x < line.length(); x++) + { + char ch = line.charAt(x); + if (ch == block) { + return new BlockPos(start.getX() + x, start.getY() + y, start.getZ()); + } + } + } + + throw new NoSuchElementException(); + } + + @Nonnull + public WorldServer build() + { + for (int i = lines.length - 1; i >= 0; i--) + { + int y = lines.length - 1 - i; + String line = lines[i]; + for (int x = 0; x < line.length(); x++) + { + char ch = line.charAt(x); + UBlockState block = blockTypes.get(ch); + if (block != null) { + world.setBlock(start.getX() + x, start.getY() + y, start.getZ(), block.getType(), block.getMeta(), 0); + } + } + } + + return world; + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinDedicatedServer.java b/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinDedicatedServer.java new file mode 100644 index 0000000..919cac6 --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinDedicatedServer.java @@ -0,0 +1,15 @@ +package org.ultramine.libtest.internal.mixin; + +import net.minecraft.server.dedicated.DedicatedServer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(DedicatedServer.class) +public class TestMixinDedicatedServer +{ +// @Overwrite +// protected void systemExitNow() +// { +// +// } +} diff --git a/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinModDiscoverer.java b/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinModDiscoverer.java new file mode 100644 index 0000000..6cfa626 --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinModDiscoverer.java @@ -0,0 +1,45 @@ +package org.ultramine.libtest.internal.mixin; + +import cpw.mods.fml.common.ModClassLoader; +import cpw.mods.fml.common.discovery.ContainerType; +import cpw.mods.fml.common.discovery.ModCandidate; +import cpw.mods.fml.common.discovery.ModDiscoverer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.ultramine.libtest.internal.mod.TestRunnerMod; +import org.ultramine.mods.bukkit.UMBukkitImplMod; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; + +/** + * This mixin exists only for test's startup time optimization purposes. + */ +@Mixin(ModDiscoverer.class) +public class TestMixinModDiscoverer +{ + @Shadow(remap = false) private List candidates; + + public void findClasspathMods(ModClassLoader modClassLoader) + { + try { + String mainClassPath = new File(UMBukkitImplMod.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getCanonicalPath(); + String testClassPath = new File(TestRunnerMod.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getCanonicalPath(); + File[] minecraftSources = modClassLoader.getParentSources(); + for (File source : minecraftSources) + { + String path = source.getCanonicalPath(); + if (testClassPath.startsWith(path)) { + candidates.add(new ModCandidate(source, source, ContainerType.DIR, false, true)); + } + if (mainClassPath.startsWith(path)) { + candidates.add(new ModCandidate(source, source, ContainerType.DIR, false, true)); + } + } + } catch(URISyntaxException | IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinWorldServer.java b/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinWorldServer.java new file mode 100644 index 0000000..634e39b --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/mixin/TestMixinWorldServer.java @@ -0,0 +1,26 @@ +package org.ultramine.libtest.internal.mixin; + +import net.minecraft.profiler.Profiler; +import net.minecraft.world.World; +import net.minecraft.world.WorldProvider; +import net.minecraft.world.WorldServer; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.storage.ISaveHandler; +import org.spongepowered.asm.mixin.Mixin; + +/** + * This mixin exists only for test's startup time optimization purposes. + */ +@Mixin(WorldServer.class) +public abstract class TestMixinWorldServer extends World +{ + public TestMixinWorldServer(ISaveHandler p_i45368_1_, String p_i45368_2_, WorldProvider p_i45368_3_, WorldSettings p_i45368_4_, Profiler p_i45368_5_) + { + super(p_i45368_1_, p_i45368_2_, p_i45368_3_, p_i45368_4_, p_i45368_5_); + } + + protected void createSpawnPosition(WorldSettings p_73052_1_) + { + this.worldInfo.setSpawnPosition(0, 64, 0); + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/mod/TestPluginInjectorMod.java b/src/test/java/org/ultramine/libtest/internal/mod/TestPluginInjectorMod.java new file mode 100644 index 0000000..9014837 --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/mod/TestPluginInjectorMod.java @@ -0,0 +1,21 @@ +package org.ultramine.libtest.internal.mod; + +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import org.bukkit.plugin.PluginDescriptionFile; +import org.ultramine.core.service.InjectService; +import org.ultramine.mods.bukkit.api.BukkitRegistry; + +@Mod(modid = "TestPluginInjectorMod", name = "TestPluginInjectorMod", version = "1.0.0", acceptableRemoteVersions = "*") +public class TestPluginInjectorMod +{ + @InjectService + private static BukkitRegistry bukkitRegistry; + + @Mod.EventHandler + public void init(FMLInitializationEvent e) + { + bukkitRegistry.injectPlugin("org.ultramine.libtest.internal.plugin", + new PluginDescriptionFile("test_plugin", "1.0.0", "org.ultramine.libtest.internal.plugin.InjectedTestPlugin")); + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/mod/TestRunnerMod.java b/src/test/java/org/ultramine/libtest/internal/mod/TestRunnerMod.java new file mode 100644 index 0000000..9677c8c --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/mod/TestRunnerMod.java @@ -0,0 +1,43 @@ +package org.ultramine.libtest.internal.mod; + +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.event.FMLServerStartedEvent; +import net.minecraft.server.MinecraftServer; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + +@Mod(modid = "TestRunnerMod", name = "TestRunnerMod", version = "1.0.0", acceptableRemoteVersions = "*") +public class TestRunnerMod +{ + private static ArrayBlockingQueue tasks = new ArrayBlockingQueue<>(1); + + @SuppressWarnings("unused") + public static void addTask(Runnable task) + { + try { + tasks.put(task); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Mod.EventHandler + public void serverStarted(FMLServerStartedEvent e) + { + try { + tasks.take().run(); + while (true) + { + Runnable task = tasks.poll(2000, TimeUnit.MILLISECONDS); + if (task == null) + break; + task.run(); + } + } catch(InterruptedException e1) { + e1.printStackTrace(); + } + + MinecraftServer.getServer().initiateShutdown(); + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/plugin/InjectedTestPlugin.java b/src/test/java/org/ultramine/libtest/internal/plugin/InjectedTestPlugin.java new file mode 100644 index 0000000..61fa887 --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/plugin/InjectedTestPlugin.java @@ -0,0 +1,13 @@ +package org.ultramine.libtest.internal.plugin; + +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; + +public class InjectedTestPlugin extends JavaPlugin implements Listener +{ + @Override + public void onEnable() + { + getLogger().info("TestPlugin has been enabled!"); + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/runner/EmptyTweaker.java b/src/test/java/org/ultramine/libtest/internal/runner/EmptyTweaker.java new file mode 100644 index 0000000..9fd83eb --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/runner/EmptyTweaker.java @@ -0,0 +1,39 @@ +package org.ultramine.libtest.internal.runner; + +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; + +import java.io.File; +import java.util.List; + +public class EmptyTweaker implements ITweaker +{ + @Override + public void acceptOptions(List args, File gameDir, File assetsDir, String profile) + { + + } + + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) + { + + } + + @Override + public String getLaunchTarget() + { + return "org.ultramine.libtest.internal.runner.EmptyTweaker"; + } + + @Override + public String[] getLaunchArguments() + { + return new String[0]; + } + + public static void main(String[] args) + { + + } +} diff --git a/src/test/java/org/ultramine/libtest/internal/runner/MinecraftServerRunner.java b/src/test/java/org/ultramine/libtest/internal/runner/MinecraftServerRunner.java new file mode 100644 index 0000000..bca6ad0 --- /dev/null +++ b/src/test/java/org/ultramine/libtest/internal/runner/MinecraftServerRunner.java @@ -0,0 +1,178 @@ +package org.ultramine.libtest.internal.runner; + +import cpw.mods.fml.relauncher.CoreModManager; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; +import org.apache.commons.io.FileUtils; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerScheduler; +import org.junit.runners.model.TestClass; +import org.ultramine.server.bootstrap.UMBootstrap; +import org.ultramine.server.util.Resources; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MinecraftServerRunner extends BlockJUnit4ClassRunner +{ + private static final String serverPort = System.getProperty("org.ultramine.libtest..test.port", "40573"); + private static File gameDir; + private static Method addTaskM; + private static boolean serverRunning; + + static + { + try { + createGameDir(); + prepareConfigs(); + injectRootBukkitCoremod(); + initLaunch(); + + LaunchClassLoader lcl = Launch.classLoader; + lcl.addClassLoaderExclusion("org.junit."); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void createGameDir() throws Exception + { + gameDir = Files.createTempDirectory("ultramine-test").toFile(); + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { + FileUtils.deleteQuietly(gameDir); + } + }); + } + + private static void prepareConfigs() throws Exception + { + File settingsDir = new File(gameDir, "settings"); + FileUtils.forceMkdir(settingsDir); + String serverYml = Resources.getAsString("/testassets/server.yml") + .replace("{port}", serverPort); + FileUtils.writeStringToFile(new File(settingsDir, "server.yml"), serverYml); + FileUtils.writeStringToFile(new File(settingsDir, "worlds.yml"), Resources.getAsString("/testassets/worlds.yml")); + } + + private static void injectRootBukkitCoremod() throws Exception + { + Field rootPluginsF = CoreModManager.class.getDeclaredField("rootPlugins"); + rootPluginsF.setAccessible(true); + String[] rootPlugins = (String[]) rootPluginsF.get(null); + String[] newRootPlugins = Arrays.copyOf(rootPlugins, rootPlugins.length + 1); + newRootPlugins[newRootPlugins.length - 1] = "org.ultramine.mods.bukkit.asm.UmBukkitCoremod"; + rootPluginsF.set(null, newRootPlugins); + } + + private static void initLaunch() throws Exception + { + UMBootstrap.handleFirstLine(new String[]{}); + + Launch.main(new String[]{ + "--gameDir", gameDir.getAbsolutePath(), + "--tweakClass", "org.ultramine.libtest.internal.runner.EmptyTweaker", + "--tweakClass", "cpw.mods.fml.common.launcher.FMLServerTweaker", + "--tweakClass", "org.spongepowered.asm.launch.MixinTweaker", + "--mixin", "mixin.umbukkitimpl.json", + "--mixin", "mixin.test-umbukkitimpl.json" + }); + } + + private static void runServer() throws Exception + { + LaunchClassLoader lcl = Launch.classLoader; + Class msc = lcl.findClass("net.minecraft.server.MinecraftServer"); + + msc.getDeclaredMethod("main", String[].class).invoke(null, (Object) new String[]{ + "--gameDir", gameDir.getAbsolutePath() + }); + + Class mod = lcl.findClass("org.ultramine.libtest.internal.mod.TestRunnerMod"); + addTaskM = mod.getDeclaredMethod("addTask", Runnable.class); + } + + private static void runServerOnce() + { + try { + if (!serverRunning) + { + serverRunning = true; + runServer(); + } + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + public MinecraftServerRunner(Class klass) throws InitializationError + { + super(klass); + } + + @Override + protected TestClass createTestClass(Class testClass) + { + try { + return super.createTestClass(setUp(testClass)); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + private Class setUp(Class testClass) throws Exception + { + return Launch.classLoader.findClass(testClass.getName()); + } + + private void addTask(Runnable task) { + try { + addTaskM.invoke(null, task); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void run(final RunNotifier notifier) + { + final List runs = new ArrayList(); + + setScheduler(new RunnerScheduler() + { + @Override + public void schedule(Runnable childStatement) + { + runs.add(childStatement); + } + + @Override + public void finished() + { + + } + }); + + super.run(notifier); + + if (runs.isEmpty()) + return; + + runServerOnce(); + + addTask(() -> { + for (Runnable run : runs) + run.run(); + }); + + addTask(() -> {}); + addTask(() -> {}); // awaits previous task + } +} diff --git a/src/test/java/org/ultramine/tests/bukkit/EmptyTest.java b/src/test/java/org/ultramine/tests/bukkit/EmptyTest.java new file mode 100644 index 0000000..1f962fb --- /dev/null +++ b/src/test/java/org/ultramine/tests/bukkit/EmptyTest.java @@ -0,0 +1,15 @@ +package org.ultramine.tests.bukkit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ultramine.libtest.internal.runner.MinecraftServerRunner; + +@RunWith(MinecraftServerRunner.class) +public class EmptyTest +{ + @Test + public void test() + { + + } +} diff --git a/src/test/java/org/ultramine/tests/bukkit/event/BaseBukkitEventTest.java b/src/test/java/org/ultramine/tests/bukkit/event/BaseBukkitEventTest.java new file mode 100644 index 0000000..1ef97eb --- /dev/null +++ b/src/test/java/org/ultramine/tests/bukkit/event/BaseBukkitEventTest.java @@ -0,0 +1,126 @@ +package org.ultramine.tests.bukkit.event; + +import com.mojang.authlib.GameProfile; +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.NetHandlerPlayServer; +import net.minecraft.network.NetworkManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.ItemInWorldManager; +import net.minecraft.world.WorldServer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; +import org.junit.After; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.ultramine.libtest.BlockPos; +import org.ultramine.libtest.WorldBuilder; +import org.ultramine.libtest.internal.runner.MinecraftServerRunner; +import org.ultramine.server.world.WorldDescriptor; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RunWith(MinecraftServerRunner.class) +public abstract class BaseBukkitEventTest +{ + // (x, y, z) coordinates are chosen at a great distance from each other + // to catch x/y/z swap bugs (e.g. (z, y, x) instead of (x, y, z)) + private static BlockPos BASE_POS = new BlockPos(1000, 0, 3000); + + private MinecraftServer server = MinecraftServer.getServer(); + private CraftServer bserver = (CraftServer) Bukkit.getServer(); + private List createdWorlds = new ArrayList<>(); + + protected MinecraftServer getServer() + { + return server; + } + + protected CraftServer getCraftServer() + { + return bserver; + } + + protected WorldServer newEmptyWorld() + { + WorldDescriptor desc = server.getMultiWorld().makeTempWorld(); + desc.forceLoadNow(); + createdWorlds.add(desc); + return desc.getOrLoadWorld(); + } + + protected WorldBuilder worldFromText(String... fixture) + { + return new WorldBuilder(newEmptyWorld(), BASE_POS, fixture); + } + + protected void doBlockUpdate(WorldServer world, int x, int y, int z, int times) + { + for (int i = 0; i < times; i++) + world.getBlock(x, y, z).updateTick(world, x, y, z, world.rand); + } + + protected void doBlockUpdate(WorldServer world, BlockPos pos, int times) + { + doBlockUpdate(world, pos.getX(), pos.getY(), pos.getZ(), times); + } + + protected void registerEvent(final Class cls, final IEventHandler handler) + { + Plugin plugin = bserver.getPluginManager().getPlugin("test_plugin"); + bserver.getPluginManager().registerEvent( + cls, + (Listener) plugin, + EventPriority.NORMAL, + (listener, event) -> handler.handle(cls.cast(event)), + plugin + ); + } + + protected IEventHandler mockEvent(final Class cls) + { + @SuppressWarnings("unchecked") + IEventHandler mock = Mockito.mock(IEventHandler.class); + registerEvent(cls, mock); + return mock; + } + + protected EntityPlayerMP newPlayerAt(WorldServer world, BlockPos pos) + { + EntityPlayerMP player = new EntityPlayerMP(server, world, new GameProfile(UUID.randomUUID(), "test_player"), new ItemInWorldManager(world)); + player.playerNetServerHandler = new NetHandlerPlayServer(server, new NetworkManager(false), player); + player.setPositionAndRotation(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0); + return player; + } + + protected void simulateRightClickBlock(EntityPlayerMP player, BlockPos pos) + { + player.theItemInWorldManager.activateBlockOrUseItem(player, player.worldObj, null, pos.getX(), pos.getY(), pos.getZ(), 0, 0, 0, 0); + } + + @After + public void cleanup() + { + for(WorldDescriptor desc : createdWorlds) + { + desc.deleteNow(); + desc.drop(); + } + + createdWorlds.clear(); + + HandlerList.unregisterAll(bserver.getPluginManager().getPlugin("test_plugin")); + } + + protected interface IEventHandler + { + void handle(T event); + } +} diff --git a/src/test/java/org/ultramine/tests/bukkit/event/BlockEventTest.java b/src/test/java/org/ultramine/tests/bukkit/event/BlockEventTest.java new file mode 100644 index 0000000..2a15b18 --- /dev/null +++ b/src/test/java/org/ultramine/tests/bukkit/event/BlockEventTest.java @@ -0,0 +1,58 @@ +package org.ultramine.tests.bukkit.event; + +import net.minecraft.init.Blocks; +import org.bukkit.event.block.BlockEvent; +import org.bukkit.event.block.BlockGrowEvent; +import org.junit.Test; +import org.ultramine.libtest.BlockPos; +import org.ultramine.libtest.UBlockState; +import org.ultramine.libtest.WorldBuilder; + +import static org.mockito.Mockito.*; + +public class BlockEventTest extends BaseBukkitEventTest +{ + private static final UBlockState blockJungleLog = new UBlockState(Blocks.log, 3); + + @Test + public void test_BlockGrowEvent_with_cactus() + { + WorldBuilder w = worldFromText( + "X", + "C", + "S" + ).set( + 'C', Blocks.cactus, + 'S', Blocks.sand + ); + + IEventHandler mock = mockEvent(BlockGrowEvent.class); + // Cactus growth strictly after 15 updates, but we perform 100 to be sure event calls once + doBlockUpdate(w.build(), w.posOf('C'), 100); + verify(mock).handle(thatBlockAtPos(w.posOf('X'))); + verifyNoMoreInteractions(mock); + } + + @Test + public void test_BlockGrowEvent_with_cocoa() + { + WorldBuilder w = worldFromText( + "LC" + ).set( + 'L', blockJungleLog, + 'C', new UBlockState(Blocks.cocoa, 1) + ); + + IEventHandler mock = mockEvent(BlockGrowEvent.class); + // Cocoa growth with a probability of 20%, so 100 random updates leads to much more than 2 hits + doBlockUpdate(w.build(), w.posOf('C'), 100); + // Cocoa growth in 2 stages, so the event should be called 2 times + verify(mock, times(2)).handle(thatBlockAtPos(w.posOf('C'))); + verifyNoMoreInteractions(mock); + } + + private static T thatBlockAtPos(BlockPos pos) + { + return argThat(e -> e.getBlock().getLocation().toVector().equals(pos.toVector())); + } +} diff --git a/src/test/java/org/ultramine/tests/bukkit/event/InventoryEventTest.java b/src/test/java/org/ultramine/tests/bukkit/event/InventoryEventTest.java new file mode 100644 index 0000000..040eb0d --- /dev/null +++ b/src/test/java/org/ultramine/tests/bukkit/event/InventoryEventTest.java @@ -0,0 +1,37 @@ +package org.ultramine.tests.bukkit.event; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Blocks; +import net.minecraft.world.WorldServer; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.junit.Test; +import org.ultramine.libtest.WorldBuilder; + +import static org.mockito.Mockito.*; + +public class InventoryEventTest extends BaseBukkitEventTest +{ + @Test + public void test_open_close() + { + WorldBuilder w = worldFromText( + "#P", + "^^" + ).set( + '#', Blocks.chest, + '^', Blocks.stone + ); + WorldServer world = w.build(); + EntityPlayerMP player = newPlayerAt(world, w.posOf('P')); + + IEventHandler open = mockEvent(InventoryOpenEvent.class); + simulateRightClickBlock(player, w.posOf('#')); + verify(open).handle(any()); + + IEventHandler close = mockEvent(InventoryCloseEvent.class); + player.closeScreen(); + verify(close).handle(any()); + verifyNoMoreInteractions(open, close); + } +} diff --git a/src/test/resources/mixin.test-umbukkitimpl.json b/src/test/resources/mixin.test-umbukkitimpl.json new file mode 100644 index 0000000..4723112 --- /dev/null +++ b/src/test/resources/mixin.test-umbukkitimpl.json @@ -0,0 +1,10 @@ +{ + "compatibilityLevel": "JAVA_8", + "package": "org.ultramine.libtest.internal.mixin", + "refmap": "mixin.umbukkit.refmap.json", + "mixins": [ + "TestMixinModDiscoverer", + "TestMixinDedicatedServer", + "TestMixinWorldServer" + ] +} \ No newline at end of file diff --git a/src/test/resources/testassets/server.yml b/src/test/resources/testassets/server.yml new file mode 100644 index 0000000..65f6649 --- /dev/null +++ b/src/test/resources/testassets/server.yml @@ -0,0 +1,71 @@ +listen: + minecraft: + serverIP: '' + port: {port} + query: + enabled: false + port: {port} + rcon: + enabled: false + port: {port} + password: '' + whitelist: null +settings: + authorization: + onlineMode: false + player: + playerIdleTimeout: 0 + gamemode: 0 + maxPlayers: 20 + forceGamemode: false + whiteList: false + other: + snooperEnabled: false + hardcore: false + resourcePack: '' + enableCommandBlock: false + splitWorldDirs: true + recipeCacheEnabled: true + spawnLocations: + firstSpawn: spawn + deathSpawn: spawn + respawnOnBed: true + teleportation: + cooldown: 60 + delay: 5 + interWorldHome: true + interWorldWarp: true + messages: + announcePlayerAchievements: true + motd: A Minecraft Server + watchdogThread: + timeout: 120000000 + restart: false + inSQLServerStorage: + enabled: false + database: global + tablePrefix: mc_ + security: + allowFlight: false + checkBreakSpeed: true +tools: + autobroadcast: + enabled: false + intervalSeconds: 600 + messages: [] + showAllMessages: false + autoDebugInfo: + enabled: false + intervalSeconds: 600 + autobackup: + enabled: false + interval: 60 + maxBackups: 10 + maxDirSize: 50000 + worlds: null + notifyPlayers: true + economy: + startBalance: 30.0 +databases: {} +vanilla: + unresolved: {} diff --git a/src/test/resources/testassets/worlds.yml b/src/test/resources/testassets/worlds.yml new file mode 100644 index 0000000..58cf853 --- /dev/null +++ b/src/test/resources/testassets/worlds.yml @@ -0,0 +1,106 @@ +global: &global + dimension: 0 + generation: &global_gen + providerID: -10 + levelType: DEFAULT + seed: 6550309589305233361 + generateStructures: true + generatorSettings: '' + disableModGeneration: false + modGenerationBlackList: [] + mobSpawn: + allowAnimals: true + spawnAnimals: true + spawnMonsters: true + allowNPCs: true + spawnEngine: NEW + newEngineSettings: + monsters: + enabled: true + minRadius: 2 + maxRadius: 2 + minPlayerDistance: 0 + performInterval: 20 + localCheckRadius: 1 + localLimit: 3 + nightlyLocalLimit: 5 + animals: + enabled: true + minRadius: 5 + maxRadius: 6 + minPlayerDistance: 0 + performInterval: 401 + localCheckRadius: 5 + localLimit: 3 + water: + enabled: true + minRadius: 3 + maxRadius: 5 + minPlayerDistance: 0 + performInterval: 400 + localCheckRadius: 4 + localLimit: 3 + ambient: + enabled: true + minPlayerDistance: 8 + performInterval: 1203 + localCheckRadius: 4 + localLimit: 10 + settings: + difficulty: 1 + maxBuildHeight: 256 + pvp: true + time: NORMAL + weather: NORMAL + useIsolatedPlayerData: false + respawnOnWarp: null + reconnectOnWarp: null + fastLeafDecay: false + borders: [] + chunkLoading: &global_cl + viewDistance: 15 + chunkActivateRadius: 7 + chunkCacheSize: 0 + enableChunkLoaders: true + maxSendRate: 4 + loadBalancer: + limits: + monsters: + updateRadius: 7 + updateByChunkLoader: true + lowerLimit: 16 + higherLimit: 16 + animals: + updateRadius: 7 + updateByChunkLoader: true + lowerLimit: 4 + higherLimit: 32 + water: + updateRadius: 7 + updateByChunkLoader: true + lowerLimit: 4 + higherLimit: 16 + ambient: + updateRadius: 7 + updateByChunkLoader: true + lowerLimit: 16 + higherLimit: 16 + items: + updateRadius: 7 + updateByChunkLoader: true + lowerLimit: 16 + higherLimit: 1024 + xpOrbs: + updateRadius: 7 + updateByChunkLoader: true + lowerLimit: 8 + higherLimit: 16 + +worlds: + - <<: *global + dimension: 0 + name: 'world' + generation: + <<: *global_gen + providerID: -10 + portals: []