diff --git a/game/plugins.gradle b/game/plugins.gradle index ca12f833..01759226 100644 --- a/game/plugins.gradle +++ b/game/plugins.gradle @@ -24,6 +24,19 @@ task testPlugins { } } +sourceSets { + pluginTesting { + kotlin { + srcDir 'src/pluginTesting/kotlin' + } + } +} + +dependencies { + pluginTestingCompile sourceSets.test.output + pluginTestingCompile sourceSets.test.compileClasspath +} + check.dependsOn testPlugins class PluginBuildData { @@ -65,6 +78,8 @@ def configurePluginDependencies(SourceSet mainSources, SourceSet testSources, dependencies.add(testConfiguration, mainSources.output) dependencies.add(testConfiguration, configurations.testCompile) dependencies.add(testConfiguration, sourceSets.test.output) + dependencies.add(testConfiguration, sourceSets.test.compileClasspath) + dependencies.add(testConfiguration, sourceSets.pluginTesting.output) } def configurePluginTasks(String name, SourceSet mainSources, SourceSet testSources, diff --git a/game/src/main/java/org/apollo/game/scheduling/ScheduledTask.java b/game/src/main/java/org/apollo/game/scheduling/ScheduledTask.java index 3b360673..f14b64d1 100644 --- a/game/src/main/java/org/apollo/game/scheduling/ScheduledTask.java +++ b/game/src/main/java/org/apollo/game/scheduling/ScheduledTask.java @@ -71,7 +71,7 @@ public abstract class ScheduledTask { /** * Pulses this task: updates the delay and calls {@link #execute()} if necessary. */ - final void pulse() { + public final void pulse() { if (running && --pulses <= 0) { execute(); pulses = delay; diff --git a/game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTest.kt b/game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTest.kt new file mode 100644 index 00000000..cb2e620e --- /dev/null +++ b/game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTest.kt @@ -0,0 +1,68 @@ +package org.apollo.game.plugins.testing + +import org.apollo.game.message.handler.MessageHandler +import org.apollo.game.message.handler.MessageHandlerChainSet +import org.apollo.game.model.Position +import org.apollo.game.model.World +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.* +import org.apollo.net.message.Message +import org.apollo.util.security.PlayerCredentials +import org.junit.After +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Matchers.any +import org.mockito.Mockito.mock +import org.mockito.invocation.InvocationOnMock +import org.mockito.stubbing.Answer +import org.powermock.api.mockito.PowerMockito.spy +import org.powermock.core.classloader.annotations.PrepareForTest +import org.powermock.modules.junit4.PowerMockRunner +import java.util.* + +@RunWith(PowerMockRunner::class) +@PrepareForTest(World::class, PluginContext::class, Player::class) +abstract class KotlinPluginTest { + private var ctx: KotlinPluginTestContext? = null + + protected fun context(): KotlinPluginTestContext { + return ctx ?: throw IllegalStateException("Setup method not called") + } + + @Before + fun setup() { + val messageHandlerChainSet = MessageHandlerChainSet() + val world = spy(World()) + + val answer = Answer { invocation: InvocationOnMock -> + messageHandlerChainSet.putHandler( + invocation.arguments[0] as Class, + invocation.arguments[1] as MessageHandler<*>) + } + + val pluginContext = mock(PluginContext::class.java, answer) + val pluginEnvironment = KotlinPluginEnvironment(world) + + pluginEnvironment.setContext(pluginContext) + pluginEnvironment.load(ArrayList()) + + val credentials = PlayerCredentials("test", "test", 1, 1, "0.0.0.0") + val position = Position(3200, 3200, 0) + + val player = spy(Player(world, credentials, position)) + org.powermock.api.mockito.PowerMockito.doNothing().`when`(player).send(any()) + + world.register(player) + + val region = world.regionRepository.fromPosition(position) + region.addEntity(player) + + ctx = KotlinPluginTestContext(world, player, messageHandlerChainSet) + } + + @After + fun destroy() { + ctx!!.shutdown() + } + +} diff --git a/game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTestContext.kt b/game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTestContext.kt new file mode 100644 index 00000000..2b1af62e --- /dev/null +++ b/game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTestContext.kt @@ -0,0 +1,74 @@ +package org.apollo.game.plugins.testing + +import org.apollo.cache.def.NpcDefinition +import org.apollo.game.action.Action +import org.apollo.game.message.handler.MessageHandlerChainSet +import org.apollo.game.message.impl.* +import org.apollo.game.model.* +import org.apollo.game.model.entity.* +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.model.entity.obj.StaticGameObject +import org.apollo.net.message.Message +import org.junit.Assert +import org.mockito.ArgumentCaptor +import org.mockito.Mockito + + +class KotlinPluginTestContext(val world: World, val activePlayer: Player, val messageHandlers: MessageHandlerChainSet) { + + fun interactWith(entity: Entity, option: Int = 1) { + activePlayer.position = entity.position.step(1, Direction.NORTH) + + when (entity) { + is GameObject -> notify(ObjectActionMessage(option, entity.id, entity.position)) + is Npc -> notify(NpcActionMessage(option, entity.index)) + is Player -> notify(PlayerActionMessage(option, entity.index)) + } + } + + fun spawnNpc(id: Int, position: Position): Npc { + val definition = NpcDefinition(id) + val npc = Npc(world, position, definition, arrayOfNulls(4)) + val region = world.regionRepository.fromPosition(position) + val npcs = world.npcRepository + + world.register(npc) + npcs.add(npc) + region.addEntity(npc) + + return npc + } + + fun spawnObject(id: Int, position: Position): GameObject { + val obj = StaticGameObject(world, id, position, 0, 0) + + world.spawn(obj) + + return obj + } + + fun notify(message: Message) { + messageHandlers.notify(activePlayer, message) + } + + fun shutdown() { + + } + + fun waitForActionCompletion(predicate: (Action) -> Boolean = { _ -> true }, timeout: Int = 15) { + val actionCaptor: ArgumentCaptor> = ArgumentCaptor.forClass(Action::class.java) + Mockito.verify(activePlayer).startAction(actionCaptor.capture()) + + val action: Action = actionCaptor.value as Action + Assert.assertTrue("Found wrong action type", predicate.invoke(action)) + + var pulses = 0 + + do { + action.pulse() + } while (action.isRunning && pulses++ < timeout) + + Assert.assertFalse("Exceeded timeout waiting for action completion", pulses > timeout) + } + +} diff --git a/game/src/plugins/bank/test/BankingTests.kt b/game/src/plugins/bank/test/BankingTests.kt deleted file mode 100644 index e69de29b..00000000 diff --git a/game/src/plugins/bank/test/OpenBankTest.kt b/game/src/plugins/bank/test/OpenBankTest.kt new file mode 100644 index 00000000..aba9b67e --- /dev/null +++ b/game/src/plugins/bank/test/OpenBankTest.kt @@ -0,0 +1,38 @@ +import org.apollo.game.model.Position +import org.apollo.game.plugins.testing.KotlinPluginTest +import org.junit.Test +import org.mockito.Mockito.verify + +class OpenBankTest() : KotlinPluginTest() { + + companion object { + const val BANK_BOOTH_ID = 2213 + const val BANK_TELLER_ID = 166 + + val BANK_POSITION = Position(3200, 3200, 0) + } + + @Test + fun `Interacting with a bank teller should open the players bank`() { + val ctx = context() + val bankTeller = ctx.spawnNpc(BANK_TELLER_ID, BANK_POSITION) + + // @todo - these option numbers only match by coincidence, we should be looking up the correct ones + ctx.interactWith(bankTeller, option = 2) + ctx.waitForActionCompletion() + + verify(ctx.activePlayer).openBank() + } + + @Test + fun `Interacting with a bank booth object should open the players bank`() { + val ctx = context() + val bankBooth = ctx.spawnObject(BANK_BOOTH_ID, BANK_POSITION) + + ctx.interactWith(bankBooth, option = 2) + ctx.waitForActionCompletion() + + verify(ctx.activePlayer).openBank() + } + +} \ No newline at end of file diff --git a/game/src/plugins/dummy/meta.toml b/game/src/plugins/dummy/meta.toml index e69de29b..46c61366 100644 --- a/game/src/plugins/dummy/meta.toml +++ b/game/src/plugins/dummy/meta.toml @@ -0,0 +1,7 @@ +name = "training-dummy" +package = "org.apollo.game.plugin.entity" +authors = [ "Gary Tierney" ] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/dummy/test/TrainingDummyTest.kt b/game/src/plugins/dummy/test/TrainingDummyTest.kt new file mode 100644 index 00000000..d8a7ddaa --- /dev/null +++ b/game/src/plugins/dummy/test/TrainingDummyTest.kt @@ -0,0 +1,45 @@ +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.SkillSet +import org.apollo.game.plugins.testing.KotlinPluginTest +import org.junit.Assert.assertTrue +import org.junit.Test +import org.mockito.Matchers +import org.mockito.Matchers.* +import org.mockito.Mockito.verify + +class TrainingDummyTest : KotlinPluginTest() { + + companion object { + const val DUMMY_ID = 823 + val DUMMY_POSITION = Position(3200, 3230, 0) + } + + @Test fun `Hitting the training dummy should give the player attack experience`() { + val ctx = context() + val dummy = ctx.spawnObject(DUMMY_ID, DUMMY_POSITION) + val skills = ctx.activePlayer.skillSet + val attackExp = skills.getExperience(Skill.ATTACK) + + ctx.interactWith(dummy, option = 2) + ctx.waitForActionCompletion() + + assertTrue("Did not gain exp after hitting dummy", skills.getExperience(Skill.ATTACK) > attackExp) + } + + @Test fun `The player should stop getting attack experience from the training dummy at level 8`() { + val ctx = context() + + val dummy = ctx.spawnObject(DUMMY_ID, DUMMY_POSITION) + val skills = ctx.activePlayer.skillSet + skills.setMaximumLevel(Skill.ATTACK, 8) + val attackExp = skills.getExperience(Skill.ATTACK) + + ctx.interactWith(dummy, option = 2) + ctx.waitForActionCompletion() + + verify(ctx.activePlayer).sendMessage(contains("nothing more you can learn")) + assertTrue("Attack exp has changed since hitting the dummy", attackExp == skills.getExperience(Skill.ATTACK)) + } + +} \ No newline at end of file