mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-03 00:38:21 +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:
@@ -19,4 +19,10 @@ dependencies {
|
||||
api group: 'com.willowtreeapps.assertk', name: 'assertk', version: assertkVersion
|
||||
|
||||
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.obj.GameObject
|
||||
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.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.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 org.junit.jupiter.api.extension.*
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.KMutableProperty
|
||||
import kotlin.reflect.full.companionObject
|
||||
import kotlin.reflect.full.createType
|
||||
@@ -58,7 +49,7 @@ class ApolloTestingExtension :
|
||||
|
||||
private fun cleanup(context: ExtensionContext) {
|
||||
val store = context.getStore(namespace)
|
||||
val state = store.get(ApolloTestState::class) as ApolloTestState
|
||||
val state = store[ApolloTestState::class] as ApolloTestState
|
||||
|
||||
try {
|
||||
state.actionCapture?.runAction()
|
||||
@@ -84,27 +75,32 @@ class ApolloTestingExtension :
|
||||
.map { it.kotlin.companionObject }
|
||||
.ifPresent { companion ->
|
||||
val companionInstance = companion.objectInstance!!
|
||||
val testClassMethods = companion.declaredMemberFunctions
|
||||
val callables: List<KCallable<*>> = companion.declaredMemberFunctions + companion.declaredMemberProperties
|
||||
|
||||
createTestDefinitions<ItemDefinition, ItemDefinitions>(
|
||||
testClassMethods, companionInstance, ItemDefinition::getId, ItemDefinition::lookup
|
||||
callables, companionInstance, ItemDefinition::getId, ItemDefinition::lookup,
|
||||
ItemDefinition::getDefinitions
|
||||
)
|
||||
|
||||
createTestDefinitions<NpcDefinition, NpcDefinitions>(
|
||||
testClassMethods, companionInstance, NpcDefinition::getId, NpcDefinition::lookup
|
||||
callables, companionInstance, NpcDefinition::getId, NpcDefinition::lookup,
|
||||
NpcDefinition::getDefinitions
|
||||
)
|
||||
|
||||
createTestDefinitions<ObjectDefinition, ObjectDefinitions>(
|
||||
testClassMethods, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup
|
||||
callables, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup,
|
||||
ObjectDefinition::getDefinitions
|
||||
)
|
||||
}
|
||||
|
||||
val pluginEnvironment = KotlinPluginEnvironment(stubWorld)
|
||||
pluginEnvironment.setContext(FakePluginContextFactory.create(stubHandlers))
|
||||
pluginEnvironment.load(ArrayList<PluginMetaData>())
|
||||
KotlinPluginEnvironment(stubWorld).apply {
|
||||
setContext(FakePluginContextFactory.create(stubHandlers))
|
||||
load(emptyList())
|
||||
}
|
||||
|
||||
val state = ApolloTestState(stubHandlers, stubWorld)
|
||||
val store = context.getStore(namespace)
|
||||
val state = ApolloTestState(stubHandlers, stubWorld)
|
||||
|
||||
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.
|
||||
*/
|
||||
private inline fun <reified D : Any, reified A : Annotation> createTestDefinitions(
|
||||
testClassMethods: Collection<KFunction<*>>,
|
||||
testClassMethods: Collection<KCallable<*>>,
|
||||
companionObjectInstance: Any?,
|
||||
crossinline idMapper: (D) -> Int,
|
||||
crossinline lookup: (Int) -> D
|
||||
crossinline lookup: (Int) -> D?,
|
||||
crossinline getAll: () -> Array<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()
|
||||
}
|
||||
val testDefinitions = findTestDefinitions<D, A>(testClassMethods, companionObjectInstance)
|
||||
.associateBy(idMapper)
|
||||
|
||||
if (testDefinitions.isNotEmpty()) {
|
||||
val idSlot = slot<Int>()
|
||||
|
||||
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).
|
||||
* - Return a `Collection<ItemDefinition>`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
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).
|
||||
* - Return a `Collection<NpcDefinition>`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
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).
|
||||
* - Return a `Collection<ObjectDefinition>`.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
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