From bb8a46c3a0d69ffe57abbbe43e28afd434430fdf Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Fri, 25 Aug 2023 16:14:27 +0200 Subject: [PATCH] [Test] Parallelize test generation --- .../generators/impl/TestGenerationDSL.kt | 18 +++++++------- .../generators/impl/TestGeneratorImpl.kt | 11 +++++---- .../test/generators/GenerateCompilerTests.kt | 10 ++++++-- .../generators/GenerateJUnit3CompilerTests.kt | 4 ++-- .../generators/GenerateJUnit5CompilerTests.kt | 4 ++-- .../kotlin/generators/InconsistencyChecker.kt | 4 +++- .../kotlin/generators/NewTestGenerationDSL.kt | 24 ++++++++++++------- .../kotlin/generators/NewTestGeneratorImpl.kt | 10 ++++---- .../kotlin/generators/TestGenerationDSL.kt | 9 +++++++ .../kotlin/generators/TestGenerator.kt | 2 +- .../generators/util/TestGeneratorUtil.kt | 1 + .../infra/generate/generateTestMethods.kt | 18 +++++++------- 12 files changed, 72 insertions(+), 43 deletions(-) diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGenerationDSL.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGenerationDSL.kt index e450be255ae..58e9041aea5 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGenerationDSL.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGenerationDSL.kt @@ -8,26 +8,28 @@ package org.jetbrains.kotlin.generators.impl import org.jetbrains.kotlin.generators.InconsistencyChecker import org.jetbrains.kotlin.generators.InconsistencyChecker.Companion.inconsistencyChecker import org.jetbrains.kotlin.generators.TestGroupSuite +import org.jetbrains.kotlin.generators.forEachTestClassParallel import org.jetbrains.kotlin.generators.testGroupSuite +import org.jetbrains.kotlin.generators.util.TestGeneratorUtil fun generateTestGroupSuite( args: Array, + mainClassName: String? = TestGeneratorUtil.getMainClassName(), init: TestGroupSuite.() -> Unit ) { - generateTestGroupSuite(InconsistencyChecker.hasDryRunArg(args), init) + generateTestGroupSuite(InconsistencyChecker.hasDryRunArg(args), mainClassName, init) } fun generateTestGroupSuite( dryRun: Boolean = false, - init: TestGroupSuite.() -> Unit + mainClassName: String? = TestGeneratorUtil.getMainClassName(), + init: TestGroupSuite.() -> Unit, ) { val suite = testGroupSuite(init) - for (testGroup in suite.testGroups) { - for (testClass in testGroup.testClasses) { - val (changed, testSourceFilePath) = TestGeneratorImpl.generateAndSave(testClass, dryRun) - if (changed) { - inconsistencyChecker(dryRun).add(testSourceFilePath) - } + suite.forEachTestClassParallel { testClass -> + val (changed, testSourceFilePath) = TestGeneratorImpl.generateAndSave(testClass, dryRun, mainClassName) + if (changed) { + inconsistencyChecker(dryRun).add(testSourceFilePath) } } } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGeneratorImpl.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGeneratorImpl.kt index 95c69e8b3f8..02be1191028 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGeneratorImpl.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/generators/impl/TestGeneratorImpl.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.generators.TestGenerator import org.jetbrains.kotlin.generators.TestGroup import org.jetbrains.kotlin.generators.model.* import org.jetbrains.kotlin.generators.util.GeneratorsFileUtil -import org.jetbrains.kotlin.generators.util.TestGeneratorUtil.getMainClassName import org.jetbrains.kotlin.test.JUnit3RunnerWithInners import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.TestMetadata @@ -31,14 +30,15 @@ private val METHOD_GENERATORS = listOf( ) object TestGeneratorImpl : TestGenerator(METHOD_GENERATORS) { - override fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean): GenerationResult { + override fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean, mainClassName: String?): GenerationResult { val generatorInstance = TestGeneratorImplInstance( testClass.baseDir, testClass.suiteTestClassName, testClass.baseTestClassName, testClass.testModels, testClass.useJunit4, - methodGenerators + methodGenerators, + mainClassName, ) return generatorInstance.generateAndSave(dryRun) } @@ -50,7 +50,8 @@ private class TestGeneratorImplInstance( baseTestClassFqName: String, private val testClassModels: Collection, private val useJunit4: Boolean, - private val methodGenerators: Map> + private val methodGenerators: Map>, + private val mainClassName: String? ) { companion object { private val GENERATED_FILES = HashSet() @@ -147,7 +148,7 @@ private class TestGeneratorImplInstance( p.println("import java.io.File;") p.println("import java.util.regex.Pattern;") p.println() - p.println("/** This class is generated by {@link ", getMainClassName(), "}. DO NOT MODIFY MANUALLY */") + p.println("/** This class is generated by {@link ", mainClassName, "}. DO NOT MODIFY MANUALLY */") generateSuppressAllWarnings(p) diff --git a/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateCompilerTests.kt b/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateCompilerTests.kt index bebe8383f90..317affa091c 100644 --- a/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateCompilerTests.kt +++ b/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateCompilerTests.kt @@ -5,9 +5,15 @@ package org.jetbrains.kotlin.test.generators +import org.jetbrains.kotlin.generators.util.TestGeneratorUtil +import java.util.stream.Stream + fun main(args: Array) { System.setProperty("java.awt.headless", "true") + // Determine main class name while still on main thread. + val mainClassName = TestGeneratorUtil.getMainClassName() - generateJUnit3CompilerTests(args) - generateJUnit5CompilerTests(args) + Stream.of(::generateJUnit3CompilerTests, ::generateJUnit5CompilerTests) + .parallel() + .forEach { it.invoke(args, mainClassName) } } diff --git a/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit3CompilerTests.kt b/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit3CompilerTests.kt index 64bfaca841f..255aa1ae3c0 100644 --- a/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit3CompilerTests.kt +++ b/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit3CompilerTests.kt @@ -50,10 +50,10 @@ import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.utils.CUSTOM_TEST_DATA_EXTENSION_PATTERN import org.jetbrains.kotlin.types.AbstractTypeBindingTest -fun generateJUnit3CompilerTests(args: Array) { +fun generateJUnit3CompilerTests(args: Array, mainClassName: String?) { val excludedCustomTestdataPattern = CUSTOM_TEST_DATA_EXTENSION_PATTERN - generateTestGroupSuite(args) { + generateTestGroupSuite(args, mainClassName) { testGroup("compiler/tests-gen", "compiler/testData") { testClass { model("diagnostics/testsWithJsStdLibAndBackendCompilation") diff --git a/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit5CompilerTests.kt b/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit5CompilerTests.kt index 8590f1ed58c..f9e3e8ff380 100644 --- a/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit5CompilerTests.kt +++ b/compiler/tests-for-compiler-generator/tests/org/jetbrains/kotlin/test/generators/GenerateJUnit5CompilerTests.kt @@ -17,10 +17,10 @@ import org.jetbrains.kotlin.test.utils.CUSTOM_TEST_DATA_EXTENSION_PATTERN import org.jetbrains.kotlin.visualizer.fir.AbstractFirVisualizerTest import org.jetbrains.kotlin.visualizer.psi.AbstractPsiVisualizerTest -fun generateJUnit5CompilerTests(args: Array) { +fun generateJUnit5CompilerTests(args: Array, mainClassName: String?) { val excludedCustomTestdataPattern = CUSTOM_TEST_DATA_EXTENSION_PATTERN - generateTestGroupSuiteWithJUnit5(args) { + generateTestGroupSuiteWithJUnit5(args, mainClassName) { testGroup(testsRoot = "compiler/tests-common-new/tests-gen", testDataRoot = "compiler/testData") { testClass { model("diagnostics/tests", pattern = "^(.*)\\.kts?$", excludedPattern = excludedCustomTestdataPattern) diff --git a/generators/test-generator/tests/org/jetbrains/kotlin/generators/InconsistencyChecker.kt b/generators/test-generator/tests/org/jetbrains/kotlin/generators/InconsistencyChecker.kt index 4281c4044d5..dfedc8bb108 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/InconsistencyChecker.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/InconsistencyChecker.kt @@ -5,6 +5,8 @@ package org.jetbrains.kotlin.generators +import java.util.Collections + interface InconsistencyChecker { fun add(affectedFile: String) @@ -18,7 +20,7 @@ interface InconsistencyChecker { } object DefaultInconsistencyChecker : InconsistencyChecker { - private val files = mutableListOf() + private val files = Collections.synchronizedList(mutableListOf()) override fun add(affectedFile: String) { files.add(affectedFile) diff --git a/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGenerationDSL.kt b/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGenerationDSL.kt index 87eb9c38803..de353c8ccf1 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGenerationDSL.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGenerationDSL.kt @@ -5,26 +5,32 @@ package org.jetbrains.kotlin.generators +import org.jetbrains.kotlin.generators.util.TestGeneratorUtil + fun generateTestGroupSuiteWithJUnit5( args: Array, + // Main class name can only be determined when running on the main thread. + // If this call is not made on the main thread, the main class name must be injected. + mainClassName: String? = TestGeneratorUtil.getMainClassName(), additionalMethodGenerators: List> = emptyList(), - init: TestGroupSuite.() -> Unit + init: TestGroupSuite.() -> Unit, ) { - generateTestGroupSuiteWithJUnit5(InconsistencyChecker.hasDryRunArg(args), additionalMethodGenerators, init) + generateTestGroupSuiteWithJUnit5(InconsistencyChecker.hasDryRunArg(args), mainClassName, additionalMethodGenerators, init) } fun generateTestGroupSuiteWithJUnit5( dryRun: Boolean = false, + // See above + mainClassName: String? = TestGeneratorUtil.getMainClassName(), additionalMethodGenerators: List> = emptyList(), - init: TestGroupSuite.() -> Unit + init: TestGroupSuite.() -> Unit, ) { val suite = TestGroupSuite(ReflectionBasedTargetBackendComputer).apply(init) - for (testGroup in suite.testGroups) { - for (testClass in testGroup.testClasses) { - val (changed, testSourceFilePath) = NewTestGeneratorImpl(additionalMethodGenerators).generateAndSave(testClass, dryRun) - if (changed) { - InconsistencyChecker.inconsistencyChecker(dryRun).add(testSourceFilePath) - } + suite.forEachTestClassParallel { testClass -> + val (changed, testSourceFilePath) = NewTestGeneratorImpl(additionalMethodGenerators) + .generateAndSave(testClass, dryRun, mainClassName) + if (changed) { + InconsistencyChecker.inconsistencyChecker(dryRun).add(testSourceFilePath) } } } diff --git a/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGeneratorImpl.kt b/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGeneratorImpl.kt index 39a44d11a1d..ac9a800e8c7 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGeneratorImpl.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/NewTestGeneratorImpl.kt @@ -75,13 +75,14 @@ class NewTestGeneratorImpl( println("@SuppressWarnings(\"all\")") } - override fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean): GenerationResult { + override fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean, mainClassName: String?): GenerationResult { val generatorInstance = TestGeneratorInstance( testClass.baseDir, testClass.suiteTestClassName, testClass.baseTestClassName, testClass.testModels, - methodGenerators + methodGenerators, + mainClassName, ) return generatorInstance.generateAndSave(dryRun) } @@ -91,7 +92,8 @@ class NewTestGeneratorImpl( suiteTestClassFqName: String, baseTestClassFqName: String, private val testClassModels: Collection, - private val methodGenerators: Map> + private val methodGenerators: Map>, + private val mainClassName: String? ) { private val baseTestClassPackage: String = baseTestClassFqName.substringBeforeLast('.', "") private val baseTestClassName: String = baseTestClassFqName.substringAfterLast('.', baseTestClassFqName) @@ -154,7 +156,7 @@ class NewTestGeneratorImpl( p.println("import java.io.File;") p.println("import java.util.regex.Pattern;") p.println() - p.println("/** This class is generated by {@link ", getMainClassName(), "}. DO NOT MODIFY MANUALLY */") + p.println("/** This class is generated by {@link ", mainClassName, "}. DO NOT MODIFY MANUALLY */") p.generateSuppressAllWarnings() 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 ce579954f2d..53a1aebbb13 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerationDSL.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerationDSL.kt @@ -10,6 +10,7 @@ import org.jetbrains.kotlin.generators.util.TestGeneratorUtil import org.jetbrains.kotlin.generators.util.extractTagsFromDirectory import org.jetbrains.kotlin.test.TargetBackend import java.io.File +import java.util.concurrent.ForkJoinPool import java.util.regex.Pattern import kotlin.reflect.KClass @@ -19,6 +20,14 @@ fun testGroupSuite( return TestGroupSuite(DefaultTargetBackendComputer).apply(init) } +fun TestGroupSuite.forEachTestClassParallel(f: (TestGroup.TestClass) -> Unit) { + testGroups + .parallelStream() + .flatMap { it.testClasses.stream() } + .sorted(compareByDescending { it.testModels.sumOf { it.methods.size } }) + .forEach(f) +} + class TestGroupSuite(val targetBackendComputer: TargetBackendComputer) { private val _testGroups = mutableListOf() val testGroups: List diff --git a/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerator.kt b/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerator.kt index f17535d5b98..20c855a820f 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerator.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/TestGenerator.kt @@ -13,7 +13,7 @@ abstract class TestGenerator( protected val methodGenerators: Map> = methodGenerators.associateBy { it.kind }.withDefault { error("Generator for method with kind $it not found") } - abstract fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean): GenerationResult + abstract fun generateAndSave(testClass: TestGroup.TestClass, dryRun: Boolean, mainClassName: String?): GenerationResult data class GenerationResult(val newFileGenerated: Boolean, val testSourceFilePath: String) } diff --git a/generators/test-generator/tests/org/jetbrains/kotlin/generators/util/TestGeneratorUtil.kt b/generators/test-generator/tests/org/jetbrains/kotlin/generators/util/TestGeneratorUtil.kt index 8c2a166d12b..4cd1731d9c0 100644 --- a/generators/test-generator/tests/org/jetbrains/kotlin/generators/util/TestGeneratorUtil.kt +++ b/generators/test-generator/tests/org/jetbrains/kotlin/generators/util/TestGeneratorUtil.kt @@ -36,6 +36,7 @@ object TestGeneratorUtil { return escapeForJavaIdentifier(file.name).replaceFirstChar(Char::uppercaseChar) } + /** Must be called on the main thread, otherwise returns the root class of the worker thread. */ fun getMainClassName(): String? = Throwable().stackTrace.lastOrNull()?.className } diff --git a/libraries/tools/kotlin-project-model/src/testFixtures/kotlin/org/jetbrains/kotlin/project/model/infra/generate/generateTestMethods.kt b/libraries/tools/kotlin-project-model/src/testFixtures/kotlin/org/jetbrains/kotlin/project/model/infra/generate/generateTestMethods.kt index cd60e1e9b8e..79fc8b09714 100644 --- a/libraries/tools/kotlin-project-model/src/testFixtures/kotlin/org/jetbrains/kotlin/project/model/infra/generate/generateTestMethods.kt +++ b/libraries/tools/kotlin-project-model/src/testFixtures/kotlin/org/jetbrains/kotlin/project/model/infra/generate/generateTestMethods.kt @@ -7,24 +7,24 @@ package org.jetbrains.kotlin.project.model.infra.generate import org.jetbrains.kotlin.generators.* import org.jetbrains.kotlin.generators.model.* +import org.jetbrains.kotlin.generators.util.TestGeneratorUtil import org.jetbrains.kotlin.project.model.infra.KpmCoreCasesTestRunner import java.io.File fun generateKpmTestCases( dryRun: Boolean = false, - init: TestGroupSuite.() -> Unit + init: TestGroupSuite.() -> Unit, ) { val suite = TestGroupSuite(DefaultTargetBackendComputer).apply { init() } - for (testGroup in suite.testGroups) { - for (testClass in testGroup.testClasses) { - val (changed, testSourceFilePath) = NewTestGeneratorImpl( - listOf(KpmCoreCaseTestMethodGenerator) - ).generateAndSave(testClass, dryRun) - if (changed) { - InconsistencyChecker.inconsistencyChecker(dryRun).add(testSourceFilePath) - } + val mainClassName = TestGeneratorUtil.getMainClassName() + suite.forEachTestClassParallel { testClass -> + val (changed, testSourceFilePath) = NewTestGeneratorImpl( + listOf(KpmCoreCaseTestMethodGenerator) + ).generateAndSave(testClass, dryRun, mainClassName) + if (changed) { + InconsistencyChecker.inconsistencyChecker(dryRun).add(testSourceFilePath) } } }