diff --git a/buildSrc/src/main/kotlin/tasks.kt b/buildSrc/src/main/kotlin/tasks.kt index a61cf59b0f9..92b2781f41e 100644 --- a/buildSrc/src/main/kotlin/tasks.kt +++ b/buildSrc/src/main/kotlin/tasks.kt @@ -9,11 +9,13 @@ import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.FileSystemOperations import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.extra import org.gradle.kotlin.dsl.project +import org.gradle.kotlin.dsl.support.serviceOf import java.io.File import java.lang.Character.isLowerCase import java.lang.Character.isUpperCase @@ -80,129 +82,141 @@ fun Project.projectTest( shortenTempRootName: Boolean = false, jUnit5Enabled: Boolean = false, body: Test.() -> Unit = {} -): TaskProvider = getOrCreateTask(taskName) { - doFirst { - val commandLineIncludePatterns = (filter as? DefaultTestFilter)?.commandLineIncludePatterns ?: mutableSetOf() - val patterns = filter.includePatterns + commandLineIncludePatterns - if (patterns.isEmpty() || patterns.any { '*' in it }) return@doFirst - patterns.forEach { pattern -> - var isClassPattern = false - val maybeMethodName = pattern.substringAfterLast('.') - val maybeClassFqName = if (maybeMethodName.isFirstChar(::isLowerCase)) { - pattern.substringBeforeLast('.') - } else { - isClassPattern = true - pattern - } - - if (!maybeClassFqName.substringAfterLast('.').isFirstChar(::isUpperCase)) { - return@forEach - } - - val classFileNameWithoutExtension = maybeClassFqName.replace('.', '/') - val classFileName = "$classFileNameWithoutExtension.class" - - if (isClassPattern) { - val innerClassPattern = "$pattern$*" - if (pattern in commandLineIncludePatterns) { - commandLineIncludePatterns.add(innerClassPattern) - (filter as? DefaultTestFilter)?.setCommandLineIncludePatterns(commandLineIncludePatterns) - } else { - filter.includePatterns.add(innerClassPattern) - } - } - - val parentNames = if (jUnit5Enabled) { - /* - * If we run test from inner test class with junit 5 we need - * to include all containing classes of our class - */ - val nestedNames = classFileNameWithoutExtension.split("$") - mutableListOf(nestedNames.first()).also { - for (s in nestedNames.subList(1, nestedNames.size)) { - it += "${it.last()}\$$s" - } - } - } else emptyList() - - include { treeElement -> - val path = treeElement.path - if (treeElement.isDirectory) { - classFileNameWithoutExtension.startsWith(path) - } else { - if (jUnit5Enabled) { - path == classFileName || (path.endsWith(".class") && parentNames.any { path.startsWith(it) }) - } else { - path == classFileName || (path.endsWith(".class") && path.startsWith("$classFileNameWithoutExtension$")) - } - } - } - } +): TaskProvider { + val shouldInstrument = project.providers.gradleProperty("kotlin.test.instrumentation.disable") + .forUseAtConfigurationTime().orNull?.toBoolean() != true + if (shouldInstrument) { + evaluationDependsOn(":test-instrumenter") } - - if (project.findProperty("kotlin.test.instrumentation.disable")?.toString()?.toBoolean() != true) { + return getOrCreateTask(taskName) { doFirst { - val agent = tasks.findByPath(":test-instrumenter:jar")!!.outputs.files.singleFile - val args = project.findProperty("kotlin.test.instrumentation.args")?.let { "=$it" }.orEmpty() - jvmArgs("-javaagent:$agent$args") - } - dependsOn(":test-instrumenter:jar") - } + val commandLineIncludePatterns = (filter as? DefaultTestFilter)?.commandLineIncludePatterns ?: mutableSetOf() + val patterns = filter.includePatterns + commandLineIncludePatterns + if (patterns.isEmpty() || patterns.any { '*' in it }) return@doFirst + patterns.forEach { pattern -> + var isClassPattern = false + val maybeMethodName = pattern.substringAfterLast('.') + val maybeClassFqName = if (maybeMethodName.isFirstChar(::isLowerCase)) { + pattern.substringBeforeLast('.') + } else { + isClassPattern = true + pattern + } - jvmArgs( - "-ea", - "-XX:+HeapDumpOnOutOfMemoryError", - "-XX:+UseCodeCacheFlushing", - "-XX:ReservedCodeCacheSize=256m", - "-Djna.nosys=true" - ) + if (!maybeClassFqName.substringAfterLast('.').isFirstChar(::isUpperCase)) { + return@forEach + } - maxHeapSize = "1600m" - systemProperty("idea.is.unit.test", "true") - systemProperty("idea.home.path", intellijRootDir().canonicalPath) - systemProperty("java.awt.headless", "true") - environment("NO_FS_ROOTS_ACCESS_CHECK", "true") - environment("PROJECT_CLASSES_DIRS", testSourceSet.output.classesDirs.asPath) - environment("PROJECT_BUILD_DIR", buildDir) - systemProperty("jps.kotlin.home", rootProject.extra["distKotlinHomeDir"]!!) - systemProperty("kotlin.ni", if (rootProject.hasProperty("newInferenceTests")) "true" else "false") - systemProperty("org.jetbrains.kotlin.skip.muted.tests", if (rootProject.hasProperty("skipMutedTests")) "true" else "false") + val classFileNameWithoutExtension = maybeClassFqName.replace('.', '/') + val classFileName = "$classFileNameWithoutExtension.class" - if (Platform[202].orHigher()) { - systemProperty("idea.ignore.disabled.plugins", "true") - } + if (isClassPattern) { + val innerClassPattern = "$pattern$*" + if (pattern in commandLineIncludePatterns) { + commandLineIncludePatterns.add(innerClassPattern) + (filter as? DefaultTestFilter)?.setCommandLineIncludePatterns(commandLineIncludePatterns) + } else { + filter.includePatterns.add(innerClassPattern) + } + } - var subProjectTempRoot: Path? = null - doFirst { - val teamcity = rootProject.findProperty("teamcity") as? Map<*, *> - val systemTempRoot = - // TC by default doesn't switch `teamcity.build.tempDir` to 'java.io.tmpdir' so it could cause to wasted disk space - // Should be fixed soon on Teamcity side - (teamcity?.get("teamcity.build.tempDir") as? String) - ?: System.getProperty("java.io.tmpdir") - systemTempRoot.let { - val prefix = (project.name + "Project_" + taskName + "_").takeUnless { shortenTempRootName } - subProjectTempRoot = Files.createTempDirectory(File(systemTempRoot).toPath(), prefix) - systemProperty("java.io.tmpdir", subProjectTempRoot.toString()) - } - } + val parentNames = if (jUnit5Enabled) { + /* + * If we run test from inner test class with junit 5 we need + * to include all containing classes of our class + */ + val nestedNames = classFileNameWithoutExtension.split("$") + mutableListOf(nestedNames.first()).also { + for (s in nestedNames.subList(1, nestedNames.size)) { + it += "${it.last()}\$$s" + } + } + } else emptyList() - doLast { - subProjectTempRoot?.let { - try { - delete(it) - } catch (e: Exception) { - project.logger.warn("Can't delete test temp root folder $it", e.printStackTrace()) + include { treeElement -> + val path = treeElement.path + if (treeElement.isDirectory) { + classFileNameWithoutExtension.startsWith(path) + } else { + if (jUnit5Enabled) { + path == classFileName || (path.endsWith(".class") && parentNames.any { path.startsWith(it) }) + } else { + path == classFileName || (path.endsWith(".class") && path.startsWith("$classFileNameWithoutExtension$")) + } + } + } } } - } - if (parallel && !jUnit5Enabled) { - maxParallelForks = - project.findProperty("kotlin.test.maxParallelForks")?.toString()?.toInt() - ?: (Runtime.getRuntime().availableProcessors() / if (kotlinBuildProperties.isTeamcityBuild) 2 else 4).coerceAtLeast(1) - } - body() + if (shouldInstrument) { + val instrumentationArgsProperty = project.providers.gradleProperty("kotlin.test.instrumentation.args") + val testInstrumenterOutputs = project.tasks.findByPath(":test-instrumenter:jar")!!.outputs.files + doFirst { + val agent = testInstrumenterOutputs.singleFile + val args = instrumentationArgsProperty.orNull?.let { "=$it" }.orEmpty() + jvmArgs("-javaagent:$agent$args") + } + dependsOn(":test-instrumenter:jar") + } + + jvmArgs( + "-ea", + "-XX:+HeapDumpOnOutOfMemoryError", + "-XX:+UseCodeCacheFlushing", + "-XX:ReservedCodeCacheSize=256m", + "-Djna.nosys=true" + ) + + maxHeapSize = "1600m" + systemProperty("idea.is.unit.test", "true") + systemProperty("idea.home.path", project.intellijRootDir().canonicalPath) + systemProperty("java.awt.headless", "true") + environment("NO_FS_ROOTS_ACCESS_CHECK", "true") + environment("PROJECT_CLASSES_DIRS", project.testSourceSet.output.classesDirs.asPath) + environment("PROJECT_BUILD_DIR", project.buildDir) + systemProperty("jps.kotlin.home", project.rootProject.extra["distKotlinHomeDir"]!!) + systemProperty("kotlin.ni", if (project.rootProject.hasProperty("newInferenceTests")) "true" else "false") + systemProperty("org.jetbrains.kotlin.skip.muted.tests", if (project.rootProject.hasProperty("skipMutedTests")) "true" else "false") + + if (Platform[202].orHigher()) { + systemProperty("idea.ignore.disabled.plugins", "true") + } + + var subProjectTempRoot: Path? = null + val projectName = project.name + val teamcity = project.rootProject.findProperty("teamcity") as? Map<*, *> + doFirst { + val systemTempRoot = + // TC by default doesn't switch `teamcity.build.tempDir` to 'java.io.tmpdir' so it could cause to wasted disk space + // Should be fixed soon on Teamcity side + (teamcity?.get("teamcity.build.tempDir") as? String) + ?: System.getProperty("java.io.tmpdir") + systemTempRoot.let { + val prefix = (projectName + "Project_" + taskName + "_").takeUnless { shortenTempRootName } + subProjectTempRoot = Files.createTempDirectory(File(systemTempRoot).toPath(), prefix) + systemProperty("java.io.tmpdir", subProjectTempRoot.toString()) + } + } + + val fs = project.serviceOf() + doLast { + subProjectTempRoot?.let { + try { + fs.delete { + delete(it) + } + } catch (e: Exception) { + logger.warn("Can't delete test temp root folder $it", e.printStackTrace()) + } + } + } + + if (parallel && !jUnit5Enabled) { + maxParallelForks = + project.findProperty("kotlin.test.maxParallelForks")?.toString()?.toInt() + ?: (Runtime.getRuntime().availableProcessors() / if (kotlinBuildProperties.isTeamcityBuild) 2 else 4).coerceAtLeast(1) + } + }.apply { configure(body) } } private inline fun String.isFirstChar(f: (Char) -> Boolean) = isNotEmpty() && f(first())