mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +00:00
Support npc and object definitions in plugin tests
This commit is contained in:
+74
-21
@@ -5,6 +5,8 @@ import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.staticMockk
|
||||
import org.apollo.cache.def.ItemDefinition
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.cache.def.ObjectDefinition
|
||||
import org.apollo.game.message.handler.MessageHandlerChainSet
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.entity.Npc
|
||||
@@ -15,14 +17,25 @@ import org.apollo.game.plugin.PluginMetaData
|
||||
import org.apollo.game.plugin.testing.fakes.FakePluginContextFactory
|
||||
import org.apollo.game.plugin.testing.junit.api.ActionCapture
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.mocking.StubPrototype
|
||||
import org.junit.jupiter.api.extension.*
|
||||
import java.util.*
|
||||
import org.junit.jupiter.api.extension.AfterAllCallback
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||
import org.junit.jupiter.api.extension.AfterTestExecutionCallback
|
||||
import org.junit.jupiter.api.extension.BeforeAllCallback
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.ParameterContext
|
||||
import org.junit.jupiter.api.extension.ParameterResolver
|
||||
import java.util.ArrayList
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KMutableProperty
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.declaredMemberFunctions
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
internal val supportedTestDoubleTypes = setOf(
|
||||
@@ -67,6 +80,25 @@ class ApolloTestingExtension :
|
||||
val stubHandlers = MessageHandlerChainSet()
|
||||
val stubWorld = spyk(World())
|
||||
|
||||
context.testClass // This _must_ come before plugin environment initialisation
|
||||
.map { it.kotlin.companionObject }
|
||||
.ifPresent { companion ->
|
||||
val companionInstance = companion.objectInstance!!
|
||||
val testClassMethods = companion.declaredMemberFunctions
|
||||
|
||||
createTestDefinitions<ItemDefinition, ItemDefinitions>(
|
||||
testClassMethods, companionInstance, ItemDefinition::getId, ItemDefinition::lookup
|
||||
)
|
||||
|
||||
createTestDefinitions<NpcDefinition, NpcDefinitions>(
|
||||
testClassMethods, companionInstance, NpcDefinition::getId, NpcDefinition::lookup
|
||||
)
|
||||
|
||||
createTestDefinitions<ObjectDefinition, ObjectDefinitions>(
|
||||
testClassMethods, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup
|
||||
)
|
||||
}
|
||||
|
||||
val pluginEnvironment = KotlinPluginEnvironment(stubWorld)
|
||||
pluginEnvironment.setContext(FakePluginContextFactory.create(stubHandlers))
|
||||
pluginEnvironment.load(ArrayList<PluginMetaData>())
|
||||
@@ -77,22 +109,8 @@ class ApolloTestingExtension :
|
||||
}
|
||||
|
||||
override fun beforeEach(context: ExtensionContext) {
|
||||
val testClass = context.requiredTestClass.kotlin
|
||||
val testClassInstance = context.requiredTestInstance
|
||||
val testClassProps = testClass.declaredMemberProperties
|
||||
val testClassMethods = context.testClass.map { it.kotlin.declaredMemberFunctions }.orElse(emptyList())
|
||||
val testClassItemDefs = testClassMethods.asSequence()
|
||||
.mapNotNull { it.findAnnotation<ItemDefinitions>()?.let { anno -> it to anno } }
|
||||
.flatMap { (it.first.call(context.requiredTestInstance as Any) as Collection<ItemDefinition>).asSequence() }
|
||||
.map { it.id to it }
|
||||
.toMap()
|
||||
|
||||
if (testClassItemDefs.isNotEmpty()) {
|
||||
val itemIdSlot = slot<Int>()
|
||||
|
||||
staticMockk<ItemDefinition>().mock()
|
||||
every { ItemDefinition.lookup(capture(itemIdSlot)) } answers { testClassItemDefs[itemIdSlot.captured] }
|
||||
}
|
||||
val testClassProps = context.requiredTestClass.kotlin.declaredMemberProperties
|
||||
|
||||
val store = context.getStore(namespace)
|
||||
val state = store.get(ApolloTestState::class) as ApolloTestState
|
||||
@@ -101,10 +119,10 @@ class ApolloTestingExtension :
|
||||
.mapNotNull { it as? KMutableProperty<*> }
|
||||
.filter { supportedTestDoubleTypes.contains(it.returnType) }
|
||||
|
||||
propertyStubSites.forEach {
|
||||
it.setter.call(
|
||||
propertyStubSites.forEach { property ->
|
||||
property.setter.call(
|
||||
testClassInstance,
|
||||
state.createStub(StubPrototype(it.returnType.jvmErasure, it.annotations))
|
||||
state.createStub(StubPrototype(property.returnType.jvmErasure, property.annotations))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -124,4 +142,39 @@ class ApolloTestingExtension :
|
||||
|
||||
return testState.createStub(StubPrototype(paramType, param.annotations.toList()))
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks the definition class of type [D] for any function with the attached annotation [A].
|
||||
*
|
||||
* @param testClassMethods All of the methods in the class being tested.
|
||||
* @param idMapper The map function that returns an id given a definition [D].
|
||||
* @param lookup The lookup function that returns an instance of [D] given a definition id.
|
||||
*/
|
||||
private inline fun <reified D : Any, reified A : Annotation> createTestDefinitions(
|
||||
testClassMethods: Collection<KFunction<*>>,
|
||||
companionObjectInstance: Any?,
|
||||
crossinline idMapper: (D) -> Int,
|
||||
crossinline lookup: (Int) -> D
|
||||
) {
|
||||
val testDefinitions = testClassMethods.asSequence()
|
||||
.filter { method -> method.annotations.any { it is A } }
|
||||
.flatMap { method ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
method as? KFunction<Collection<D>> ?: throw RuntimeException("${method.name} is annotated with " +
|
||||
"${A::class.simpleName} but does not return Collection<${D::class.simpleName}."
|
||||
)
|
||||
|
||||
method.isAccessible = true // lets us call methods in private companion objects
|
||||
method.call(companionObjectInstance).asSequence()
|
||||
}
|
||||
.associateBy(idMapper)
|
||||
|
||||
if (testDefinitions.isNotEmpty()) {
|
||||
val idSlot = slot<Int>()
|
||||
|
||||
staticMockk<D>().mock()
|
||||
every { lookup(capture(idSlot)) } answers { testDefinitions[idSlot.captured]!! }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+33
-1
@@ -1,5 +1,37 @@
|
||||
package org.apollo.game.plugin.testing.junit.api.annotations
|
||||
|
||||
/**
|
||||
* Specifies that the the ItemDefinitions returned by the annotated function should be inserted into the definition
|
||||
* table.
|
||||
*
|
||||
* The annotated function **must**:
|
||||
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
||||
* - Return a `Collection<ItemDefinition>`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ItemDefinitions
|
||||
annotation class ItemDefinitions
|
||||
|
||||
/**
|
||||
* Specifies that the the NpcDefinitions returned by the annotated function should be inserted into the definition
|
||||
* table.
|
||||
*
|
||||
* The annotated function **must**:
|
||||
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
||||
* - Return a `Collection<NpcDefinition>`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class NpcDefinitions
|
||||
|
||||
/**
|
||||
* Specifies that the the ObjectDefinitions returned by the annotated function should be inserted into the definition
|
||||
* table.
|
||||
*
|
||||
* The annotated function **must**:
|
||||
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
||||
* - Return a `Collection<ObjectDefinition>`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ObjectDefinitions
|
||||
@@ -22,7 +22,11 @@ object Definitions {
|
||||
}
|
||||
|
||||
fun npc(id: Int): NpcDefinition? {
|
||||
return NpcDefinition.lookup(id)
|
||||
try {
|
||||
return NpcDefinition.lookup(id)
|
||||
} catch (e: NullPointerException) {
|
||||
throw RuntimeException("Failed to find npc $id: count=${NpcDefinition.count()}")
|
||||
}
|
||||
}
|
||||
|
||||
fun npc(name: String): NpcDefinition? {
|
||||
|
||||
@@ -12,13 +12,13 @@ import org.apollo.game.plugin.skills.mining.Ore
|
||||
import org.apollo.game.plugin.skills.mining.Pickaxe
|
||||
import org.apollo.game.plugin.skills.mining.TIN_OBJECTS
|
||||
import org.apollo.game.plugin.testing.assertions.after
|
||||
import org.apollo.game.plugin.testing.assertions.contains
|
||||
import org.apollo.game.plugin.testing.assertions.verifyAfter
|
||||
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension
|
||||
import org.apollo.game.plugin.testing.junit.api.ActionCapture
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.TestMock
|
||||
import org.apollo.game.plugin.testing.junit.api.interactions.spawnObject
|
||||
import org.apollo.game.plugin.testing.assertions.contains
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -26,14 +26,6 @@ import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class MiningActionTests {
|
||||
private val TIN_OBJ_IDS = TIN_OBJECTS.entries.first()
|
||||
|
||||
@ItemDefinitions
|
||||
fun ores() = Ore.values()
|
||||
.map { ItemDefinition(it.id).also { it.name = "<ore_type>" } }
|
||||
|
||||
@ItemDefinitions
|
||||
fun pickaxes() = listOf(ItemDefinition(Pickaxe.BRONZE.id))
|
||||
|
||||
@TestMock
|
||||
lateinit var world: World
|
||||
@@ -50,8 +42,12 @@ class MiningActionTests {
|
||||
val target = spyk(MiningTarget(obj.id, obj.position, Ore.TIN))
|
||||
|
||||
every { target.skillRequirementsMet(player) } returns false
|
||||
|
||||
player.startAction(MiningAction(player, Pickaxe.BRONZE, target))
|
||||
verifyAfter(action.complete()) { player.sendMessage(contains("do not have the required level")) }
|
||||
|
||||
verifyAfter(action.complete()) {
|
||||
player.sendMessage(contains("do not have the required level"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,12 +59,15 @@ class MiningActionTests {
|
||||
|
||||
every { target.skillRequirementsMet(player) } returns true
|
||||
every { target.isSuccessful(player, any()) } returns true
|
||||
every { world.expireObject(obj, any(), any()) } answers {}
|
||||
every { world.expireObject(obj, any(), any()) } answers { }
|
||||
|
||||
player.skillSet.setCurrentLevel(Skill.MINING, Ore.TIN.level)
|
||||
player.startAction(MiningAction(player, Pickaxe.BRONZE, target))
|
||||
|
||||
verifyAfter(action.ticks(1)) { player.sendMessage(contains("You swing your pick")) }
|
||||
verifyAfter(action.ticks(1)) {
|
||||
player.sendMessage(contains("You swing your pick"))
|
||||
}
|
||||
|
||||
after(action.complete()) {
|
||||
verify { player.sendMessage("You manage to mine some <ore_type>") }
|
||||
verify { world.expireObject(obj, expiredTinId, Ore.TIN.respawn) }
|
||||
@@ -77,4 +76,16 @@ class MiningActionTests {
|
||||
assertEquals(player.skillSet.getExperience(Skill.MINING), Ore.TIN.exp)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val TIN_OBJ_IDS = TIN_OBJECTS.entries.first()
|
||||
|
||||
@ItemDefinitions
|
||||
fun ores() = Ore.values()
|
||||
.map { ItemDefinition(it.id).apply { name = "<ore_type>" } }
|
||||
|
||||
@ItemDefinitions
|
||||
fun pickaxes() = listOf(ItemDefinition(Pickaxe.BRONZE.id))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,11 +13,6 @@ import org.junit.jupiter.params.provider.EnumSource
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class PickaxeTests {
|
||||
|
||||
@ItemDefinitions
|
||||
fun pickaxes() = Pickaxe.values().map {
|
||||
ItemDefinition(it.id).apply { isStackable = false }
|
||||
}
|
||||
|
||||
@TestMock
|
||||
lateinit var player: Player
|
||||
|
||||
@@ -72,4 +67,11 @@ class PickaxeTests {
|
||||
assertEquals(pickaxe, Pickaxe.bestFor(player))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
@ItemDefinitions
|
||||
fun pickaxes() = Pickaxe.values().map {
|
||||
ItemDefinition(it.id).apply { isStackable = false }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,13 +2,13 @@
|
||||
import org.apollo.cache.def.ItemDefinition
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.plugin.skills.mining.Ore
|
||||
import org.apollo.game.plugin.testing.assertions.contains
|
||||
import org.apollo.game.plugin.testing.assertions.verifyAfter
|
||||
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension
|
||||
import org.apollo.game.plugin.testing.junit.api.ActionCapture
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.TestMock
|
||||
import org.apollo.game.plugin.testing.junit.api.interactions.interactWithObject
|
||||
import org.apollo.game.plugin.testing.assertions.contains
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||
@@ -16,11 +16,6 @@ import org.junit.jupiter.params.provider.ArgumentsSource
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class ProspectingTests {
|
||||
|
||||
@ItemDefinitions
|
||||
fun ores() = Ore.values().map {
|
||||
ItemDefinition(it.id).also { it.name = "<ore_type>" }
|
||||
}
|
||||
|
||||
@TestMock
|
||||
lateinit var player: Player
|
||||
|
||||
@@ -43,4 +38,12 @@ class ProspectingTests {
|
||||
|
||||
verifyAfter(action.complete()) { player.sendMessage(contains("no ore available in this rock")) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
@ItemDefinitions
|
||||
fun ores() = Ore.values().map {
|
||||
ItemDefinition(it.id).also { it.name = "<ore_type>" }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,11 +26,6 @@ class BuryBoneTests {
|
||||
@TestMock
|
||||
lateinit var action: ActionCapture
|
||||
|
||||
@ItemDefinitions
|
||||
fun bones(): Collection<ItemDefinition> {
|
||||
return Bone.values().map { ItemDefinition(it.id) }
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(value = Bone::class)
|
||||
fun `Burying a bone should send a message`(bone: Bone) {
|
||||
@@ -69,4 +64,11 @@ class BuryBoneTests {
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
@ItemDefinitions
|
||||
fun bones(): Collection<ItemDefinition> {
|
||||
return Bone.values().map { ItemDefinition(it.id) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,11 +13,6 @@ import org.junit.jupiter.params.provider.EnumSource
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class AxeTests {
|
||||
|
||||
@ItemDefinitions
|
||||
fun axes() = Axe.values().map {
|
||||
ItemDefinition(it.id).apply { isStackable = false }
|
||||
}
|
||||
|
||||
@TestMock
|
||||
lateinit var player: Player
|
||||
|
||||
@@ -69,4 +64,11 @@ class AxeTests {
|
||||
assertEquals(axe, Axe.bestFor(player))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
@ItemDefinitions
|
||||
fun axes() = Axe.values().map {
|
||||
ItemDefinition(it.id).apply { isStackable = false }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,19 +20,11 @@ import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||
import java.util.*
|
||||
import java.util.Random
|
||||
|
||||
@ExtendWith(ApolloTestingExtension::class)
|
||||
class WoodcuttingTests {
|
||||
|
||||
@ItemDefinitions
|
||||
fun logs() = woodcuttingTestData().map {
|
||||
ItemDefinition(it.tree.id).also { it.name = "<tree_type>" }
|
||||
}
|
||||
|
||||
@ItemDefinitions
|
||||
fun tools() = listOf(ItemDefinition(Axe.BRONZE.id))
|
||||
|
||||
@TestMock
|
||||
lateinit var action: ActionCapture
|
||||
|
||||
@@ -85,4 +77,14 @@ class WoodcuttingTests {
|
||||
assertEquals(1, player.inventory.getAmount(data.tree.id))
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
@ItemDefinitions
|
||||
fun logs() = woodcuttingTestData().map {
|
||||
ItemDefinition(it.tree.id).also { it.name = "<tree_type>" }
|
||||
}
|
||||
|
||||
@ItemDefinitions
|
||||
fun tools() = listOf(ItemDefinition(Axe.BRONZE.id))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package org.apollo.game.plugin;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.github.classgraph.ClassGraph;
|
||||
import io.github.classgraph.ClassInfo;
|
||||
import io.github.classgraph.ClassInfoList;
|
||||
@@ -7,12 +13,6 @@ import io.github.classgraph.ScanResult;
|
||||
import org.apollo.game.model.World;
|
||||
import org.apollo.game.plugin.kotlin.KotlinPluginScript;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class KotlinPluginEnvironment implements PluginEnvironment {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(KotlinPluginEnvironment.class.getName());
|
||||
@@ -28,7 +28,6 @@ public class KotlinPluginEnvironment implements PluginEnvironment {
|
||||
@Override
|
||||
public void load(Collection<PluginMetaData> plugins) {
|
||||
List<KotlinPluginScript> pluginScripts = new ArrayList<>();
|
||||
List<Class<? extends KotlinPluginScript>> pluginClasses = new ArrayList<>();
|
||||
|
||||
ClassGraph classGraph = new ClassGraph().enableAllInfo();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user