[Test] Compute target backend in test generator automatically using reflection

This commit is contained in:
Dmitriy Novozhilov
2020-12-25 12:14:03 +03:00
committed by TeamCityServer
parent 9378d1ff31
commit 3c2079c926
7 changed files with 100 additions and 10 deletions
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2020 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.test.runners
import org.jetbrains.kotlin.test.TargetBackend
/**
* If your test runner has specific target backend then you can add this interface
* to your runner and after that you should not declare targetBackend
* parameter in DSL in addition to defining it in test runner
*
* Please make sure that all abstract runners which are used in test generator
* have `open` modality, not `abstract`. This is required because test generator
* instantiates runner to get value of targetBackend and generate tests properly
*/
interface RunnerWithTargetBackendForTestGeneratorMarker {
val targetBackend: TargetBackend
}
@@ -20,7 +20,7 @@ fun generateTestGroupSuiteWithJUnit5(
dryRun: Boolean = false, dryRun: Boolean = false,
init: TestGroupSuite.() -> Unit init: TestGroupSuite.() -> Unit
) { ) {
val suite = testGroupSuite(init) val suite = TestGroupSuite(ReflectionBasedTargetBackendComputer).apply(init)
for (testGroup in suite.testGroups) { for (testGroup in suite.testGroups) {
for (testClass in testGroup.testClasses) { for (testClass in testGroup.testClasses) {
val (changed, testSourceFilePath) = NewTestGeneratorImpl.generateAndSave(testClass, dryRun) val (changed, testSourceFilePath) = NewTestGeneratorImpl.generateAndSave(testClass, dryRun)
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2020 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.test.generators
import org.jetbrains.kotlin.generators.model.DefaultTargetBackendComputer
import org.jetbrains.kotlin.generators.model.TargetBackendComputer
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.test.runners.RunnerWithTargetBackendForTestGeneratorMarker
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
object ReflectionBasedTargetBackendComputer : TargetBackendComputer {
private val runnerMarkerKClass = RunnerWithTargetBackendForTestGeneratorMarker::class
private const val TARGET_BACKEND_PROPERTY_NAME = "targetBackend"
override fun compute(definedTargetBackend: TargetBackend?, testKClass: KClass<*>): TargetBackend {
if (!testKClass.isSubclassOf(runnerMarkerKClass)) return DefaultTargetBackendComputer.compute(definedTargetBackend, testKClass)
require(definedTargetBackend == null) {
"""
Test ${testKClass.simpleName} is inheritor of ${runnerMarkerKClass.simpleName} which means that
target you should not specify targetBackend in test generation DSL, because it will be
read from test runner class itself
""".trimIndent()
}
require(testKClass.isOpen) {
"""
Test runner which inherits from ${runnerMarkerKClass.simpleName} and used as base class
for real test should have `open` modality
""".trimIndent()
}
val instance = testKClass.createInstance() as RunnerWithTargetBackendForTestGeneratorMarker
val kProperty = runnerMarkerKClass.declaredMemberProperties.single { it.name == TARGET_BACKEND_PROPERTY_NAME }
return kProperty.get(instance) as TargetBackend
}
}
@@ -10,8 +10,8 @@ dependencies {
testImplementation(platform("org.junit:junit-bom:5.7.0")) testImplementation(platform("org.junit:junit-bom:5.7.0"))
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation(projectTests(":compiler:tests-common")) testImplementation(projectTests(":compiler:tests-common"))
testImplementation(projectTests(":compiler:test-infrastructure"))
testImplementation(projectTests(":compiler:tests-common-new")) testImplementation(projectTests(":compiler:tests-common-new"))
testImplementation(projectTests(":compiler:tests-common"))
testImplementation(projectTests(":compiler")) testImplementation(projectTests(":compiler"))
testImplementation(projectTests(":compiler:fir:raw-fir:psi2fir")) testImplementation(projectTests(":compiler:fir:raw-fir:psi2fir"))
testImplementation(projectTests(":compiler:fir:raw-fir:light-tree2fir")) testImplementation(projectTests(":compiler:fir:raw-fir:light-tree2fir"))
@@ -11,14 +11,15 @@ import org.jetbrains.kotlin.test.TargetBackend
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.reflect.KClass
fun testGroupSuite( fun testGroupSuite(
init: TestGroupSuite.() -> Unit init: TestGroupSuite.() -> Unit
): TestGroupSuite { ): TestGroupSuite {
return TestGroupSuite().apply(init) return TestGroupSuite(DefaultTargetBackendComputer).apply(init)
} }
class TestGroupSuite { class TestGroupSuite(val targetBackendComputer: TargetBackendComputer) {
private val _testGroups = mutableListOf<TestGroup>() private val _testGroups = mutableListOf<TestGroup>()
val testGroups: List<TestGroup> val testGroups: List<TestGroup>
get() = _testGroups get() = _testGroups
@@ -35,6 +36,7 @@ class TestGroupSuite {
testDataRoot, testDataRoot,
testRunnerMethodName, testRunnerMethodName,
additionalRunnerArguments, additionalRunnerArguments,
targetBackendComputer = targetBackendComputer
).apply(init) ).apply(init)
} }
} }
@@ -45,6 +47,7 @@ class TestGroup(
val testRunnerMethodName: String, val testRunnerMethodName: String,
val additionalRunnerArguments: List<String> = emptyList(), val additionalRunnerArguments: List<String> = emptyList(),
val annotations: List<AnnotationModel> = emptyList(), val annotations: List<AnnotationModel> = emptyList(),
val targetBackendComputer: TargetBackendComputer
) { ) {
private val _testClasses: MutableList<TestClass> = mutableListOf() private val _testClasses: MutableList<TestClass> = mutableListOf()
val testClasses: List<TestClass> val testClasses: List<TestClass>
@@ -56,24 +59,28 @@ class TestGroup(
annotations: List<AnnotationModel> = emptyList(), annotations: List<AnnotationModel> = emptyList(),
noinline init: TestClass.() -> Unit noinline init: TestClass.() -> Unit
) { ) {
testClass(T::class.java.name, suiteTestClassName, useJunit4, annotations, init) val testKClass = T::class
testClass(testKClass, testKClass.java.name, suiteTestClassName, useJunit4, annotations, init)
} }
fun testClass( fun testClass(
testKClass: KClass<*>,
baseTestClassName: String, baseTestClassName: String,
suiteTestClassName: String = getDefaultSuiteTestClassName(baseTestClassName.substringAfterLast('.')), suiteTestClassName: String = getDefaultSuiteTestClassName(baseTestClassName.substringAfterLast('.')),
useJunit4: Boolean, useJunit4: Boolean,
annotations: List<AnnotationModel> = emptyList(), annotations: List<AnnotationModel> = emptyList(),
init: TestClass.() -> Unit init: TestClass.() -> Unit
) { ) {
_testClasses += TestClass(baseTestClassName, suiteTestClassName, useJunit4, annotations).apply(init) _testClasses += TestClass(testKClass, baseTestClassName, suiteTestClassName, useJunit4, annotations, targetBackendComputer).apply(init)
} }
inner class TestClass( inner class TestClass(
val testKClass: KClass<*>,
val baseTestClassName: String, val baseTestClassName: String,
val suiteTestClassName: String, val suiteTestClassName: String,
val useJunit4: Boolean, val useJunit4: Boolean,
val annotations: List<AnnotationModel> val annotations: List<AnnotationModel>,
val targetBackendComputer: TargetBackendComputer
) { ) {
val baseDir: String val baseDir: String
get() = this@TestGroup.testsRoot get() = this@TestGroup.testsRoot
@@ -92,7 +99,7 @@ class TestGroup(
testClassName: String? = null, // specific name for generated test class testClassName: String? = null, // specific name for generated test class
// which backend will be used in test. Specifying value may affect some test with // which backend will be used in test. Specifying value may affect some test with
// directives TARGET_BACKEND/DONT_TARGET_EXACT_BACKEND won't be generated // directives TARGET_BACKEND/DONT_TARGET_EXACT_BACKEND won't be generated
targetBackend: TargetBackend = TargetBackend.ANY, targetBackend: TargetBackend? = null,
excludeDirs: List<String> = listOf(), excludeDirs: List<String> = listOf(),
filenameStartsLowerCase: Boolean? = null, // assert that file is properly named filenameStartsLowerCase: Boolean? = null, // assert that file is properly named
skipIgnored: Boolean = false, // pretty meaningless flag, affects only few test names in one test runner skipIgnored: Boolean = false, // pretty meaningless flag, affects only few test names in one test runner
@@ -102,18 +109,20 @@ class TestGroup(
val compiledPattern = Pattern.compile(pattern) val compiledPattern = Pattern.compile(pattern)
val compiledExcludedPattern = excludedPattern?.let { Pattern.compile(it) } val compiledExcludedPattern = excludedPattern?.let { Pattern.compile(it) }
val className = testClassName ?: TestGeneratorUtil.fileNameToJavaIdentifier(rootFile) val className = testClassName ?: TestGeneratorUtil.fileNameToJavaIdentifier(rootFile)
val realTargetBackend = targetBackendComputer.compute(targetBackend, testKClass)
testModels.add( testModels.add(
if (singleClass) { if (singleClass) {
if (excludeDirs.isNotEmpty()) error("excludeDirs is unsupported for SingleClassTestModel yet") if (excludeDirs.isNotEmpty()) error("excludeDirs is unsupported for SingleClassTestModel yet")
SingleClassTestModel( SingleClassTestModel(
rootFile, compiledPattern, compiledExcludedPattern, filenameStartsLowerCase, testMethod, className, rootFile, compiledPattern, compiledExcludedPattern, filenameStartsLowerCase, testMethod, className,
targetBackend, skipIgnored, testRunnerMethodName, additionalRunnerArguments, annotations realTargetBackend, skipIgnored, testRunnerMethodName, additionalRunnerArguments, annotations
) )
} else { } else {
SimpleTestClassModel( SimpleTestClassModel(
rootFile, recursive, excludeParentDirs, rootFile, recursive, excludeParentDirs,
compiledPattern, compiledExcludedPattern, filenameStartsLowerCase, testMethod, className, compiledPattern, compiledExcludedPattern, filenameStartsLowerCase, testMethod, className,
targetBackend, excludeDirs, skipIgnored, testRunnerMethodName, additionalRunnerArguments, deep, annotations realTargetBackend, excludeDirs, skipIgnored, testRunnerMethodName, additionalRunnerArguments, deep, annotations
) )
} }
) )
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2020 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.generators.model
import org.jetbrains.kotlin.test.TargetBackend
import kotlin.reflect.KClass
fun interface TargetBackendComputer {
fun compute(definedTargetBackend: TargetBackend?, testKClass: KClass<*>): TargetBackend
}
object DefaultTargetBackendComputer : TargetBackendComputer {
override fun compute(definedTargetBackend: TargetBackend?, testKClass: KClass<*>): TargetBackend {
return definedTargetBackend ?: TargetBackend.ANY
}
}
@@ -7,6 +7,7 @@ plugins {
val depenencyProjects = arrayOf( val depenencyProjects = arrayOf(
":generators", ":generators",
":compiler", ":compiler",
":compiler:test-infrastructure",
":compiler:tests-common-new", ":compiler:tests-common-new",
":compiler:tests-for-compiler-generator", ":compiler:tests-for-compiler-generator",
":js:js.tests", ":js:js.tests",