Files
Troels Bjerre Lund 111bb461a9 CLI: Change kotlin reflection to java reflection
The command line argument parser is using between 0.25s and 0.5s
(depending on platform) on finding annotated properties. This fix
replaces the slow kotlin reflection with java reflection, which is an
order of magnitude faster.

 #KT-58183 Fixed
2023-06-27 08:34:40 +00:00

142 lines
5.4 KiB
Kotlin

/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.compilerRunner
import org.jetbrains.kotlin.cli.common.arguments.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Named
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.Base64.getEncoder
import kotlin.random.Random
import kotlin.reflect.*
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.withNullability
import kotlin.reflect.jvm.javaField
import kotlin.test.assertContentEquals
import kotlin.test.fail
class CompilerArgumentParsingTest {
@ParameterizedTest
@MethodSource("parameters")
fun `test - parsing random compiler arguments`(
type: KClass<out CommonToolArguments>,
seed: Int,
shortArgumentKeys: Boolean,
compactArgumentValues: Boolean
) {
val constructor = type.constructors.find { it.parameters.isEmpty() } ?: error("Missing empty constructor on $type")
val arguments = constructor.call()
arguments.fillRandomValues(Random(seed))
val argumentsAsStrings = arguments.toArgumentStrings(
shortArgumentKeys = shortArgumentKeys,
compactArgumentValues = compactArgumentValues
)
val parsedArguments = parseCommandLineArguments(type, argumentsAsStrings)
assertEqualArguments(arguments, parsedArguments)
assertEquals(
argumentsAsStrings,
parsedArguments.toArgumentStrings(
shortArgumentKeys = shortArgumentKeys,
compactArgumentValues = compactArgumentValues
)
)
}
companion object {
@JvmStatic
fun parameters(): List<Arguments> = getCompilerArgumentImplementations()
.flatMap { clazz ->
listOf(1002, 2803, 2411).flatMap { seed ->
listOf(true, false).flatMap { shortArgumentKeys ->
listOf(true, false).map { compactArgumentValues ->
Arguments.of(
Named.of("${clazz.simpleName}", clazz),
Named.of("seed: $seed", seed),
Named.of("shortArgumentKeys: $shortArgumentKeys", shortArgumentKeys),
Named.of("compactArgumentValues: $compactArgumentValues", compactArgumentValues)
)
}
}
}
}
}
}
private fun assertEqualArguments(expected: CommonToolArguments, actual: CommonToolArguments) {
if (expected::class != actual::class) fail("Expected class '${expected::class}', found: '${actual::class}'")
expected::class.memberProperties
.filter { it.javaField?.getAnnotation(Argument::class.java) != null }
.ifEmpty { fail("No members with ${Argument::class} annotation") }
.map { property ->
@Suppress("UNCHECKED_CAST")
property as KProperty1<Any, Any?>
val expectedValue = property.get(expected)
val actualValue = property.get(actual)
val message = "Unexpected value in '${property.name}: '${property.returnType}'"
if (property.returnType.isSubtypeOf(typeOf<Array<*>?>())) {
@Suppress("UNCHECKED_CAST")
assertContentEquals(
expectedValue as Array<Any?>?, actualValue as Array<Any?>?,
message
)
} else assertEquals(
expectedValue, actualValue,
message
)
}
}
private fun CommonToolArguments.fillRandomValues(random: Random) {
this::class.memberProperties.filterIsInstance<KMutableProperty1<*, *>>().forEach { property ->
@Suppress("UNCHECKED_CAST")
property as KMutableProperty1<Any, Any>
runCatching {
property.set(this, random.randomValue(property.returnType) ?: return@forEach)
}.getOrElse {
throw Throwable("Failed setting random value for: ${property.name}: ${property.returnType}", it)
}
}
}
private fun Random.randomString() = nextBytes(nextInt(8, 12)).let { data ->
getEncoder().withoutPadding().encodeToString(data)
}
private fun Random.randomBoolean() = nextBoolean()
private fun Random.randomStringArray(): Array<String> {
val size = nextInt(5, 10)
return Array(size) {
randomString()
}
}
private fun Random.randomList(elementType: KType): List<Any>? {
val size = nextInt(5, 10)
return List(size) {
randomValue(elementType) ?: return null
}
}
fun Random.randomValue(type: KType): Any? {
@Suppress("NAME_SHADOWING")
val type = type.withNullability(false)
return when {
type == typeOf<String>() -> randomString()
type == typeOf<Boolean>() -> randomBoolean()
type == typeOf<Array<String>>() -> randomStringArray()
type.isSubtypeOf(typeOf<List<*>>()) -> randomList(type.arguments.first().type ?: error("Missing elementType on $type"))
type == typeOf<InternalArgument>() -> return null
(type.classifier as? KClass<*>)?.isData == true -> null
else -> error("Unsupported type '$type'")
}
}