mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-07 16:49:12 +00:00
Add @DefinitionSource annotations
Allows parameterized tests to use Item, Npc, and Object definitions as @ArgumentSources. This commit also adds support for using @ItemDefinitions etc on properties as well as functions.
This commit is contained in:
@@ -20,3 +20,9 @@ dependencies {
|
|||||||
|
|
||||||
implementation group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion
|
implementation group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-35
@@ -13,23 +13,14 @@ import org.apollo.game.model.entity.Npc
|
|||||||
import org.apollo.game.model.entity.Player
|
import org.apollo.game.model.entity.Player
|
||||||
import org.apollo.game.model.entity.obj.GameObject
|
import org.apollo.game.model.entity.obj.GameObject
|
||||||
import org.apollo.game.plugin.KotlinPluginEnvironment
|
import org.apollo.game.plugin.KotlinPluginEnvironment
|
||||||
import org.apollo.game.plugin.PluginMetaData
|
|
||||||
import org.apollo.game.plugin.testing.fakes.FakePluginContextFactory
|
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.ActionCapture
|
||||||
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
|
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.NpcDefinitions
|
||||||
import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions
|
import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions
|
||||||
import org.apollo.game.plugin.testing.junit.mocking.StubPrototype
|
import org.apollo.game.plugin.testing.junit.mocking.StubPrototype
|
||||||
import org.junit.jupiter.api.extension.AfterAllCallback
|
import org.junit.jupiter.api.extension.*
|
||||||
import org.junit.jupiter.api.extension.AfterEachCallback
|
import kotlin.reflect.KCallable
|
||||||
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.KMutableProperty
|
||||||
import kotlin.reflect.full.companionObject
|
import kotlin.reflect.full.companionObject
|
||||||
import kotlin.reflect.full.createType
|
import kotlin.reflect.full.createType
|
||||||
@@ -58,7 +49,7 @@ class ApolloTestingExtension :
|
|||||||
|
|
||||||
private fun cleanup(context: ExtensionContext) {
|
private fun cleanup(context: ExtensionContext) {
|
||||||
val store = context.getStore(namespace)
|
val store = context.getStore(namespace)
|
||||||
val state = store.get(ApolloTestState::class) as ApolloTestState
|
val state = store[ApolloTestState::class] as ApolloTestState
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.actionCapture?.runAction()
|
state.actionCapture?.runAction()
|
||||||
@@ -84,27 +75,32 @@ class ApolloTestingExtension :
|
|||||||
.map { it.kotlin.companionObject }
|
.map { it.kotlin.companionObject }
|
||||||
.ifPresent { companion ->
|
.ifPresent { companion ->
|
||||||
val companionInstance = companion.objectInstance!!
|
val companionInstance = companion.objectInstance!!
|
||||||
val testClassMethods = companion.declaredMemberFunctions
|
val callables: List<KCallable<*>> = companion.declaredMemberFunctions + companion.declaredMemberProperties
|
||||||
|
|
||||||
createTestDefinitions<ItemDefinition, ItemDefinitions>(
|
createTestDefinitions<ItemDefinition, ItemDefinitions>(
|
||||||
testClassMethods, companionInstance, ItemDefinition::getId, ItemDefinition::lookup
|
callables, companionInstance, ItemDefinition::getId, ItemDefinition::lookup,
|
||||||
|
ItemDefinition::getDefinitions
|
||||||
)
|
)
|
||||||
|
|
||||||
createTestDefinitions<NpcDefinition, NpcDefinitions>(
|
createTestDefinitions<NpcDefinition, NpcDefinitions>(
|
||||||
testClassMethods, companionInstance, NpcDefinition::getId, NpcDefinition::lookup
|
callables, companionInstance, NpcDefinition::getId, NpcDefinition::lookup,
|
||||||
|
NpcDefinition::getDefinitions
|
||||||
)
|
)
|
||||||
|
|
||||||
createTestDefinitions<ObjectDefinition, ObjectDefinitions>(
|
createTestDefinitions<ObjectDefinition, ObjectDefinitions>(
|
||||||
testClassMethods, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup
|
callables, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup,
|
||||||
|
ObjectDefinition::getDefinitions
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pluginEnvironment = KotlinPluginEnvironment(stubWorld)
|
KotlinPluginEnvironment(stubWorld).apply {
|
||||||
pluginEnvironment.setContext(FakePluginContextFactory.create(stubHandlers))
|
setContext(FakePluginContextFactory.create(stubHandlers))
|
||||||
pluginEnvironment.load(ArrayList<PluginMetaData>())
|
load(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
val state = ApolloTestState(stubHandlers, stubWorld)
|
|
||||||
val store = context.getStore(namespace)
|
val store = context.getStore(namespace)
|
||||||
|
val state = ApolloTestState(stubHandlers, stubWorld)
|
||||||
|
|
||||||
store.put(ApolloTestState::class, state)
|
store.put(ApolloTestState::class, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,30 +147,43 @@ class ApolloTestingExtension :
|
|||||||
* @param lookup The lookup function that returns an instance of [D] given a definition id.
|
* @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(
|
private inline fun <reified D : Any, reified A : Annotation> createTestDefinitions(
|
||||||
testClassMethods: Collection<KFunction<*>>,
|
testClassMethods: Collection<KCallable<*>>,
|
||||||
companionObjectInstance: Any?,
|
companionObjectInstance: Any?,
|
||||||
crossinline idMapper: (D) -> Int,
|
crossinline idMapper: (D) -> Int,
|
||||||
crossinline lookup: (Int) -> D
|
crossinline lookup: (Int) -> D?,
|
||||||
|
crossinline getAll: () -> Array<D>
|
||||||
) {
|
) {
|
||||||
val testDefinitions = testClassMethods.asSequence()
|
val testDefinitions = findTestDefinitions<D, A>(testClassMethods, companionObjectInstance)
|
||||||
.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)
|
.associateBy(idMapper)
|
||||||
|
|
||||||
if (testDefinitions.isNotEmpty()) {
|
if (testDefinitions.isNotEmpty()) {
|
||||||
val idSlot = slot<Int>()
|
val idSlot = slot<Int>()
|
||||||
|
|
||||||
staticMockk<D>().mock()
|
staticMockk<D>().mock()
|
||||||
every { lookup(capture(idSlot)) } answers { testDefinitions[idSlot.captured]!! }
|
|
||||||
|
every { lookup(capture(idSlot)) } answers { testDefinitions[idSlot.captured] }
|
||||||
|
every { getAll() } answers { testDefinitions.values.sortedBy(idMapper).toTypedArray() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
inline fun <reified D : Any, reified A : Annotation> findTestDefinitions(
|
||||||
|
callables: Collection<KCallable<*>>,
|
||||||
|
companionObjectInstance: Any?
|
||||||
|
): List<D> {
|
||||||
|
return callables
|
||||||
|
.filter { method -> method.annotations.any { it is A } }
|
||||||
|
.flatMap { method ->
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
method as? KCallable<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+3
-3
@@ -8,7 +8,7 @@ package org.apollo.game.plugin.testing.junit.api.annotations
|
|||||||
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
||||||
* - Return a `Collection<ItemDefinition>`.
|
* - Return a `Collection<ItemDefinition>`.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class ItemDefinitions
|
annotation class ItemDefinitions
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ annotation class ItemDefinitions
|
|||||||
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
||||||
* - Return a `Collection<NpcDefinition>`.
|
* - Return a `Collection<NpcDefinition>`.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class NpcDefinitions
|
annotation class NpcDefinitions
|
||||||
|
|
||||||
@@ -32,6 +32,6 @@ annotation class NpcDefinitions
|
|||||||
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
|
||||||
* - Return a `Collection<ObjectDefinition>`.
|
* - Return a `Collection<ObjectDefinition>`.
|
||||||
*/
|
*/
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
annotation class ObjectDefinitions
|
annotation class ObjectDefinitions
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package org.apollo.game.plugin.testing.junit.params
|
||||||
|
|
||||||
|
import org.apollo.cache.def.ItemDefinition
|
||||||
|
import org.apollo.cache.def.NpcDefinition
|
||||||
|
import org.apollo.cache.def.ObjectDefinition
|
||||||
|
import org.apollo.game.plugin.testing.junit.ApolloTestingExtension.Companion.findTestDefinitions
|
||||||
|
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.junit.jupiter.api.extension.ExtensionContext
|
||||||
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider
|
||||||
|
import org.junit.jupiter.params.support.AnnotationConsumer
|
||||||
|
import java.util.stream.Stream
|
||||||
|
import kotlin.reflect.KCallable
|
||||||
|
import kotlin.reflect.full.companionObject
|
||||||
|
import kotlin.reflect.full.declaredMemberFunctions
|
||||||
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [ArgumentsProvider] for a definition of type `D`.
|
||||||
|
*/
|
||||||
|
abstract class DefinitionsProvider<D : Any>(
|
||||||
|
private val definitionProvider: (methods: Collection<KCallable<*>>, companionObjectInstance: Any) -> List<D>
|
||||||
|
) : ArgumentsProvider {
|
||||||
|
|
||||||
|
protected lateinit var sourceNames: Set<String>
|
||||||
|
|
||||||
|
override fun provideArguments(context: ExtensionContext): Stream<out Arguments> {
|
||||||
|
val companion = context.requiredTestClass.kotlin.companionObject
|
||||||
|
?: throw RuntimeException("${context.requiredTestMethod.name} is annotated with a DefinitionsProvider," +
|
||||||
|
" but does not contain a companion object to search for Definitions in."
|
||||||
|
)
|
||||||
|
|
||||||
|
val companionInstance = companion.objectInstance!! // safe
|
||||||
|
val callables: List<KCallable<*>> = companion.declaredMemberFunctions + companion.declaredMemberProperties
|
||||||
|
|
||||||
|
val filtered = if (sourceNames.isEmpty()) {
|
||||||
|
callables
|
||||||
|
} else {
|
||||||
|
callables.filter { it.name in sourceNames }
|
||||||
|
}
|
||||||
|
|
||||||
|
return definitionProvider(filtered, companionInstance).map { Arguments.of(it) }.stream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These providers are separate because of a JUnit bug in its use of ArgumentsSource and AnnotationConsumer -
|
||||||
|
// the reflection code that invokes the AnnotationConsumer searches for an accept() method that takes an
|
||||||
|
// Annotation parameter, prohibiting usage of the actual `Annotation` type as the parameter - meaning
|
||||||
|
// DefinitionsProvider cannot abstract over different annotation implementations (i.e. over ItemDefinitionSource,
|
||||||
|
// NpcDefinitionSource, and ObjectDefinitionSource).
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [ArgumentsProvider] for [ItemDefinition]s.
|
||||||
|
*
|
||||||
|
* Test authors should not need to utilise this class, and should instead annotate their function with
|
||||||
|
* [@ItemDefinitionSource][ItemDefinitionSource].
|
||||||
|
*/
|
||||||
|
object ItemDefinitionsProvider : DefinitionsProvider<ItemDefinition>(
|
||||||
|
{ methods, companion -> findTestDefinitions<ItemDefinition, ItemDefinitions>(methods, companion) }
|
||||||
|
), AnnotationConsumer<ItemDefinitionSource> {
|
||||||
|
|
||||||
|
override fun accept(source: ItemDefinitionSource) {
|
||||||
|
sourceNames = source.sourceNames.toHashSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [ArgumentsProvider] for [NpcDefinition]s.
|
||||||
|
*
|
||||||
|
* Test authors should not need to utilise this class, and should instead annotate their function with
|
||||||
|
* [@NpcDefinitionSource][NpcDefinitionSource].
|
||||||
|
*/
|
||||||
|
object NpcDefinitionsProvider : DefinitionsProvider<NpcDefinition>(
|
||||||
|
{ methods, companion -> findTestDefinitions<NpcDefinition, NpcDefinitions>(methods, companion) }
|
||||||
|
), AnnotationConsumer<NpcDefinitionSource> {
|
||||||
|
|
||||||
|
override fun accept(source: NpcDefinitionSource) {
|
||||||
|
sourceNames = source.sourceNames.toHashSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An [ArgumentsProvider] for [ObjectDefinition]s.
|
||||||
|
*
|
||||||
|
* Test authors should not need to utilise this class, and should instead annotate their function with
|
||||||
|
* [@ObjectDefinitionSource][ObjectDefinitionSource].
|
||||||
|
*/
|
||||||
|
object ObjectDefinitionsProvider : DefinitionsProvider<ObjectDefinition>(
|
||||||
|
{ methods, companion -> findTestDefinitions<ObjectDefinition, ObjectDefinitions>(methods, companion) }
|
||||||
|
), AnnotationConsumer<ObjectDefinitionSource> {
|
||||||
|
|
||||||
|
override fun accept(source: ObjectDefinitionSource) {
|
||||||
|
sourceNames = source.sourceNames.toHashSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package org.apollo.game.plugin.testing.junit.params
|
||||||
|
|
||||||
|
import org.apollo.cache.def.ItemDefinition
|
||||||
|
import org.apollo.cache.def.NpcDefinition
|
||||||
|
import org.apollo.cache.def.ObjectDefinition
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@ItemDefinitionSource` is an [ArgumentsSource] for [ItemDefinition]s.
|
||||||
|
*
|
||||||
|
* @param sourceNames The names of the properties or functions annotated with `@ItemDefinitions` to use as sources of
|
||||||
|
* [ItemDefinition]s for the test with this annotation. Every property/function must return
|
||||||
|
* `Collection<ItemDefinition>`. If no [sourceNames] are provided, every property and function annotated with
|
||||||
|
* `@ItemDefinitions` (in this class's companion object) will be used.
|
||||||
|
*
|
||||||
|
* @see org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
|
* @see org.junit.jupiter.params.ParameterizedTest
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@ArgumentsSource(ItemDefinitionsProvider::class)
|
||||||
|
annotation class ItemDefinitionSource(vararg val sourceNames: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@NpcDefinitionSource` is an [ArgumentsSource] for [NpcDefinition]s.
|
||||||
|
*
|
||||||
|
* @param sourceNames The names of the properties or functions annotated with `@NpcDefinitions` to use as sources of
|
||||||
|
* [NpcDefinition]s for the test with this annotation. Every property/function must return
|
||||||
|
* `Collection<NpcDefinition>`. If no [sourceNames] are provided, every property and function annotated with
|
||||||
|
* `@NpcDefinitions` (in this class's companion object) will be used.
|
||||||
|
*
|
||||||
|
* @see org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
|
* @see org.junit.jupiter.params.ParameterizedTest
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@ArgumentsSource(NpcDefinitionsProvider::class)
|
||||||
|
annotation class NpcDefinitionSource(vararg val sourceNames: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `@ObjectDefinitionSource` is an [ArgumentsSource] for [ObjectDefinition]s.
|
||||||
|
*
|
||||||
|
* @param sourceNames The names of the properties or functions annotated with `@ObjectDefinitions` to use as sources of
|
||||||
|
* [ObjectDefinition]s for the test with this annotation. Every property/function must return
|
||||||
|
* `Collection<ObjectDefinition>`. If no [sourceNames] are provided, every property and function annotated with
|
||||||
|
* `@ObjectDefinitions` (in this class's companion object) will be used.
|
||||||
|
*
|
||||||
|
* @see org.junit.jupiter.params.provider.ArgumentsSource
|
||||||
|
* @see org.junit.jupiter.params.ParameterizedTest
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@ArgumentsSource(ObjectDefinitionsProvider::class)
|
||||||
|
annotation class ObjectDefinitionSource(vararg val sourceNames: String)
|
||||||
Reference in New Issue
Block a user