From 32fda13ef9fd55b50b43ac71157de534d5f98ba9 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Wed, 2 Dec 2020 17:23:43 +0300 Subject: [PATCH] [TEST] Implement test generators for junit 5 based tests --- .../test/generators/NewTestGenerationDSL.kt | 32 +++ .../test/generators/NewTestGeneratorImpl.kt | 262 ++++++++++++++++++ .../kotlin/generators/TestGenerationDSL.kt | 2 +- 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGenerationDSL.kt create mode 100644 compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGeneratorImpl.kt diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGenerationDSL.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGenerationDSL.kt new file mode 100644 index 00000000000..1f95054aeb1 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGenerationDSL.kt @@ -0,0 +1,32 @@ +/* + * 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.InconsistencyChecker +import org.jetbrains.kotlin.generators.TestGroupSuite +import org.jetbrains.kotlin.generators.testGroupSuite + +fun generateNewTestGroupSuite( + args: Array, + init: TestGroupSuite.() -> Unit +) { + generateNewTestGroupSuite(InconsistencyChecker.hasDryRunArg(args), init) +} + +fun generateNewTestGroupSuite( + dryRun: Boolean = false, + init: TestGroupSuite.() -> Unit +) { + val suite = testGroupSuite(init) + for (testGroup in suite.testGroups) { + for (testClass in testGroup.testClasses) { + val (changed, testSourceFilePath) = NewTestGeneratorImpl.generateAndSave(testClass, dryRun) + if (changed) { + InconsistencyChecker.inconsistencyChecker(dryRun).add(testSourceFilePath) + } + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGeneratorImpl.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGeneratorImpl.kt new file mode 100644 index 00000000000..715155167c8 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/generators/NewTestGeneratorImpl.kt @@ -0,0 +1,262 @@ +/* + * 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.MethodGenerator +import org.jetbrains.kotlin.generators.TestGenerator +import org.jetbrains.kotlin.generators.TestGroup +import org.jetbrains.kotlin.generators.impl.* +import org.jetbrains.kotlin.generators.model.* +import org.jetbrains.kotlin.generators.util.GeneratorsFileUtil +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.TestMetadata +import org.jetbrains.kotlin.utils.Printer +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import java.io.File +import java.io.IOException +import java.util.* + +private val METHOD_GENERATORS = listOf( + RunTestMethodGenerator, + SimpleTestClassModelTestAllFilesPresentMethodGenerator, + SimpleTestMethodGenerator, + SingleClassTestModelAllFilesPresentedMethodGenerator +) + +object NewTestGeneratorImpl : TestGenerator(METHOD_GENERATORS) { + private val GENERATED_FILES = HashSet() + + private fun Printer.generateMetadata(testDataSource: TestEntityModel) { + val dataString = testDataSource.dataString + if (dataString != null) { + println("@TestMetadata(\"", dataString, "\")") + } + } + + private fun Printer.generateTestAnnotation() { + println("@Test") + } + + private fun Printer.generateNestedAnnotation(isNested: Boolean) { + if (isNested) { + println("@Nested") + } + } + + private fun Printer.generateTestDataPath(testClassModel: TestClassModel) { + val dataPathRoot = testClassModel.dataPathRoot + if (dataPathRoot != null) { + println("@TestDataPath(\"", dataPathRoot, "\")") + } + } + + private fun Printer.generateParameterAnnotations(testClassModel: TestClassModel) { + for (annotationModel in testClassModel.annotations) { + annotationModel.generate(this) + println() + } + } + + private fun Printer.generateSuppressAllWarnings() { + println("@SuppressWarnings(\"all\")") + } + + override fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean): GenerationResult { + val generatorInstance = TestGeneratorInstance( + testClass.baseDir, + testClass.suiteTestClassName, + testClass.baseTestClassName, + testClass.testModels, + methodGenerators + ) + return generatorInstance.generateAndSave(dryRun) + } + + private class TestGeneratorInstance( + baseDir: String, + suiteTestClassFqName: String, + baseTestClassFqName: String, + private val testClassModels: Collection, + methodGenerators: Map> + ) { + private val methodGenerators = methodGenerators.toMutableMap().apply { + val newGenerator = this.computeIfPresent(CoroutinesTestMethodModel.Kind) { _, _ -> + SimpleTestMethodGenerator + } ?: return@apply + this[CoroutinesTestMethodModel.Kind] = newGenerator + } + + private val baseTestClassPackage: String = baseTestClassFqName.substringBeforeLast('.', "") + private val baseTestClassName: String = baseTestClassFqName.substringAfterLast('.', baseTestClassFqName) + private val suiteClassPackage: String = suiteTestClassFqName.substringBeforeLast('.', baseTestClassPackage) + private val suiteClassName: String = suiteTestClassFqName.substringAfterLast('.', suiteTestClassFqName) + private val testSourceFilePath: String = baseDir + "/" + this.suiteClassPackage.replace(".", "/") + "/" + this.suiteClassName + ".java" + + init { + if (!GENERATED_FILES.add(testSourceFilePath)) { + throw IllegalArgumentException("Same test file already generated in current session: $testSourceFilePath") + } + } + + @Throws(IOException::class) + fun generateAndSave(dryRun: Boolean): GenerationResult { + val generatedCode = generate() + + val testSourceFile = File(testSourceFilePath) + val changed = + GeneratorsFileUtil.isFileContentChangedIgnoringLineSeparators(testSourceFile, generatedCode) + if (!dryRun) { + GeneratorsFileUtil.writeFileIfContentChanged(testSourceFile, generatedCode, false) + } + return GenerationResult(changed, testSourceFilePath) + } + + private fun generate(): String { + val out = StringBuilder() + val p = Printer(out) + + val year = GregorianCalendar()[Calendar.YEAR] + p.println( + """/* + | * Copyright 2010-$year 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. + | */ + |""".trimMargin() + ) + p.println("package $suiteClassPackage;") + p.println() + p.println("import com.intellij.testFramework.TestDataPath;") + p.println("import ${KotlinTestUtils::class.java.canonicalName};") + + for (clazz in testClassModels.flatMapTo(mutableSetOf()) { classModel -> classModel.imports }) { + p.println("import ${clazz.name};") + } + + if (suiteClassPackage != baseTestClassPackage) { + p.println("import $baseTestClassPackage.$baseTestClassName;") + } + + p.println("import ${TestMetadata::class.java.canonicalName};") + p.println("import ${Nested::class.java.canonicalName};") + p.println("import ${Test::class.java.canonicalName};") + p.println() + p.println("import java.io.File;") + p.println("import java.util.regex.Pattern;") + p.println() + p.println("/** This class is generated by {@link ", KotlinTestUtils.TEST_GENERATOR_NAME, "}. DO NOT MODIFY MANUALLY */") + + p.generateSuppressAllWarnings() + + val model: TestClassModel + if (testClassModels.size == 1) { + model = object : DelegatingTestClassModel(testClassModels.single()) { + override val name: String + get() = suiteClassName + } + } else { + model = object : TestClassModel() { + override val innerTestClasses: Collection + get() = testClassModels + + override val methods: Collection + get() = emptyList() + + override val isEmpty: Boolean + get() = false + + override val name: String + get() = suiteClassName + + override val dataString: String? + get() = null + + override val dataPathRoot: String? + get() = null + + override val annotations: Collection + get() = emptyList() + + override val imports: Set> + get() = super.imports + } + } + + generateTestClass(p, model, false) + return out.toString() + } + + private fun generateTestClass(p: Printer, testClassModel: TestClassModel, isNested: Boolean) { + p.generateNestedAnnotation(isNested) + p.generateMetadata(testClassModel) + p.generateTestDataPath(testClassModel) + p.generateParameterAnnotations(testClassModel) + + p.println("public class ${testClassModel.name} extends $baseTestClassName {") + p.pushIndent() + + val testMethods = testClassModel.methods + val innerTestClasses = testClassModel.innerTestClasses + + var first = true + + for (methodModel in testMethods) { + if (methodModel is RunTestMethodModel) continue + if (!methodModel.shouldBeGenerated()) continue + + if (first) { + first = false + } else { + p.println() + } + + generateTestMethod(p, methodModel) + } + + for (innerTestClass in innerTestClasses) { + if (!innerTestClass.isEmpty) { + if (first) { + first = false + } else { + p.println() + } + + generateTestClass(p, innerTestClass, true) + } + } + + p.popIndent() + p.println("}") + } + + private fun generateTestMethod(p: Printer, methodModel: MethodModel) { + val generator = methodGenerators.getValue(methodModel.kind) + + p.generateTestAnnotation() + p.generateMetadata(methodModel) + generator.hackyGenerateSignature(methodModel, p) + p.printWithNoIndent(" {") + p.println() + + p.pushIndent() + + generator.hackyGenerateBody(methodModel, p) + + p.popIndent() + p.println("}") + } + + private fun MethodGenerator.hackyGenerateBody(method: MethodModel, p: Printer) { + @Suppress("UNCHECKED_CAST") + generateBody(method as T, p) + } + + private fun MethodGenerator.hackyGenerateSignature(method: MethodModel, p: Printer) { + @Suppress("UNCHECKED_CAST") + generateSignature(method as T, p) + } + } +} diff --git a/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerationDSL.kt b/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerationDSL.kt index 9cb4a090161..835cae4cd54 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerationDSL.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerationDSL.kt @@ -76,7 +76,7 @@ class TestGroup( val testClasses: List get() = _testClasses - inline fun testClass( + inline fun testClass( suiteTestClassName: String = getDefaultSuiteTestClassName(T::class.java.simpleName), useJunit4: Boolean = false, annotations: List = emptyList(),