
com.wesleyhome.test.jupiter.ParametersGenerator.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of junit-jupiter-params-generated Show documentation
Show all versions of junit-jupiter-params-generated Show documentation
Library to help generate test parameter permutations for parameterized tests in JUnit.
This version is an initial attempt to convert to building with Gradle.
package com.wesleyhome.test.jupiter
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.params.provider.Arguments
import java.util.concurrent.atomic.AtomicLong
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.javaMethod
import kotlin.reflect.jvm.kotlinFunction
class ParametersGenerator(
private val parameterClassList: List,
private val testObject: Any,
private val dataProviderClasses: List>,
private val testMethodName: String
) : Iterable {
private val testClass = testObject.javaClass.kotlin
private val filterMethod: KFunction?
private val options: List>
private val totalPermutations: Long
private val pointers: Array
private val current = AtomicLong(0)
init {
filterMethod = (testClass.allFunctions()
.firstOrNull { isFilterMethod(it) })
?.let { it as KFunction }
if (filterMethod != null) {
filterMethod.javaMethod!!.trySetAccessible()
}
options = parameterClassList.map(::deriveOption)
totalPermutations = options
.asSequence()
.map { it.size }
.map { it.toLong() }
.reduce { acc, i -> acc * i }
pointers = Array(options.size) { 0 }
}
private fun deriveOption(parameter: KParameter): List {
val javaType = (parameter.type.classifier as KClass<*>).java
val booleanClass = java.lang.Boolean::class.java
val booleanType = java.lang.Boolean.TYPE
val kotlinBoolean = Boolean::class
val values = when {
javaType.isEnum -> {
javaType.enumConstants.toList().let { list ->
if (parameter.type.isMarkedNullable) list + null else list
}
}
javaType in listOf(booleanType, booleanClass, kotlinBoolean)
-> listOf(true, false)
else -> createOptionsFromProviders(parameter)
}
return values
}
private fun createOptionsFromProviders(parameter: KParameter): List {
return dataProviderClasses.firstNotNullOfOrNull {
getOptionsFromProvider(it, parameter)
} ?: emptyList()
}
private fun getOptionsFromProvider(providerClass: KClass<*>, parameter: KParameter): List? =
providerClass.allFunctions()
.asSequence()
.filter {
val staticNonTestInstance = it.isStaticNonTestInstance(providerClass)
val instanceMethodOfTestClass = it.isInstanceMethodOfTestClass(testClass)
staticNonTestInstance || instanceMethodOfTestClass
}
.filter {
val noArg = it.isNoArg()
noArg
}
.filter {
val returnsIterable = it.returnsIterable(parameter)
returnsIterable
}
.flatMap {
val invoke = invoke(it as KFunction>)
invoke
}
.toList()
.ifEmpty { null }
private fun invoke(function: KFunction>): Iterable {
function.javaMethod?.trySetAccessible()
return if (function.isStatic()) {
function.call()
} else {
function.call(testObject)
}
}
private fun isFilterMethod(function: KFunction<*>): Boolean {
val methodName: String = function.name
val returnType: KType = function.returnType
val functionParameters = function.parameters.drop(1).map {
it.type
}
val expectedParameters = parameterClassList.map { it.type }
val parametersEqual = functionParameters == expectedParameters
val returnBoolean = returnType.isSubtypeOf(Boolean::class.starProjectedType)
val containsFilterMethodName = methodName == testMethodName + "_filter"
return parametersEqual && returnBoolean && containsFilterMethodName
}
override fun iterator(): Iterator {
return object : Iterator {
override fun hasNext(): Boolean {
if (filterMethod == null) {
return current.get() < totalPermutations
}
while (current.get() < totalPermutations) {
val argument = createArgument()
if (shouldFilter(argument)) {
return true
}
increment()
}
return false
}
override fun next(): Arguments {
return createArgument().also { increment() }
}
}
}
private fun shouldFilter(argument: Arguments): Boolean {
return filterMethod!!.call(testObject, *argument.get())
}
private fun increment() {
current.incrementAndGet()
incrementIndex(pointers.size - 1)
}
private fun incrementIndex(index: Int) {
if (index < 0) {
return
}
val parameterIndex = pointers[index] + 1
val length = options[index].size
if (parameterIndex < length) {
pointers[index] = parameterIndex
} else {
pointers[index] = 0
incrementIndex(index - 1)
}
}
fun createArgument(): Arguments {
val indexed = options
.mapIndexed { index, list ->
val paramIndex = pointers[index]
list[paramIndex]
}
return Arguments.of(*indexed.toTypedArray())
}
companion object {
fun create(context: ExtensionContext): ParametersGenerator {
val requiredTestMethod = context.requiredTestMethod.kotlinFunction!!
val requiredTestClass = context.requiredTestClass.kotlin
val requiredTestInstance = ReflectionHelper.invokeConstructor(requiredTestClass.java)
val parametersSource = requiredTestMethod.findAnnotation()!!
val methodName = requiredTestMethod.name
val parameterClassList = requiredTestMethod.parameters.drop(1)
val dataProviderClasses = parametersSource.value.toList() + requiredTestClass
return ParametersGenerator(parameterClassList, requiredTestInstance, dataProviderClasses, methodName)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy