From b536b2ed9da137c48810765bcd99f078ee0cce42 Mon Sep 17 00:00:00 2001 From: Gary Tierney Date: Mon, 19 Jun 2017 02:50:50 +0100 Subject: [PATCH] Add start of test framework for plugins Adds a basic testing framework suitable for plugins that start simple Actions for players, which can be expanded on in the future. The banking and training dummy tests have been updated to use this framework and serve as samples. --- game/plugins.gradle | 15 ++++ .../apollo/game/scheduling/ScheduledTask.java | 2 +- .../game/plugins/testing/KotlinPluginTest.kt | 68 +++++++++++++++++ .../testing/KotlinPluginTestContext.kt | 74 +++++++++++++++++++ game/src/plugins/bank/test/BankingTests.kt | 0 game/src/plugins/bank/test/OpenBankTest.kt | 38 ++++++++++ game/src/plugins/dummy/meta.toml | 7 ++ .../plugins/dummy/test/TrainingDummyTest.kt | 45 +++++++++++ 8 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTest.kt create mode 100644 game/src/pluginTesting/kotlin/org/apollo/game/plugins/testing/KotlinPluginTestContext.kt delete mode 100644 game/src/plugins/bank/test/BankingTests.kt create mode 100644 game/src/plugins/bank/test/OpenBankTest.kt create mode 100644 game/src/plugins/dummy/test/TrainingDummyTest.kt 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