[Build] Make projectTest() extension compatible with configuration cache

Relates to #KT-44611
This commit is contained in:
Alexander Likhachev
2021-02-15 16:21:09 +03:00
parent ce49664366
commit da6544ae3c
+127 -113
View File
@@ -9,11 +9,13 @@
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.extra import org.gradle.kotlin.dsl.extra
import org.gradle.kotlin.dsl.project import org.gradle.kotlin.dsl.project
import org.gradle.kotlin.dsl.support.serviceOf
import java.io.File import java.io.File
import java.lang.Character.isLowerCase import java.lang.Character.isLowerCase
import java.lang.Character.isUpperCase import java.lang.Character.isUpperCase
@@ -80,129 +82,141 @@ fun Project.projectTest(
shortenTempRootName: Boolean = false, shortenTempRootName: Boolean = false,
jUnit5Enabled: Boolean = false, jUnit5Enabled: Boolean = false,
body: Test.() -> Unit = {} body: Test.() -> Unit = {}
): TaskProvider<Test> = getOrCreateTask(taskName) { ): TaskProvider<Test> {
doFirst { val shouldInstrument = project.providers.gradleProperty("kotlin.test.instrumentation.disable")
val commandLineIncludePatterns = (filter as? DefaultTestFilter)?.commandLineIncludePatterns ?: mutableSetOf() .forUseAtConfigurationTime().orNull?.toBoolean() != true
val patterns = filter.includePatterns + commandLineIncludePatterns if (shouldInstrument) {
if (patterns.isEmpty() || patterns.any { '*' in it }) return@doFirst evaluationDependsOn(":test-instrumenter")
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$"))
}
}
}
}
} }
return getOrCreateTask<Test>(taskName) {
if (project.findProperty("kotlin.test.instrumentation.disable")?.toString()?.toBoolean() != true) {
doFirst { doFirst {
val agent = tasks.findByPath(":test-instrumenter:jar")!!.outputs.files.singleFile val commandLineIncludePatterns = (filter as? DefaultTestFilter)?.commandLineIncludePatterns ?: mutableSetOf()
val args = project.findProperty("kotlin.test.instrumentation.args")?.let { "=$it" }.orEmpty() val patterns = filter.includePatterns + commandLineIncludePatterns
jvmArgs("-javaagent:$agent$args") if (patterns.isEmpty() || patterns.any { '*' in it }) return@doFirst
} patterns.forEach { pattern ->
dependsOn(":test-instrumenter:jar") var isClassPattern = false
} val maybeMethodName = pattern.substringAfterLast('.')
val maybeClassFqName = if (maybeMethodName.isFirstChar(::isLowerCase)) {
pattern.substringBeforeLast('.')
} else {
isClassPattern = true
pattern
}
jvmArgs( if (!maybeClassFqName.substringAfterLast('.').isFirstChar(::isUpperCase)) {
"-ea", return@forEach
"-XX:+HeapDumpOnOutOfMemoryError", }
"-XX:+UseCodeCacheFlushing",
"-XX:ReservedCodeCacheSize=256m",
"-Djna.nosys=true"
)
maxHeapSize = "1600m" val classFileNameWithoutExtension = maybeClassFqName.replace('.', '/')
systemProperty("idea.is.unit.test", "true") val classFileName = "$classFileNameWithoutExtension.class"
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")
if (Platform[202].orHigher()) { if (isClassPattern) {
systemProperty("idea.ignore.disabled.plugins", "true") val innerClassPattern = "$pattern$*"
} if (pattern in commandLineIncludePatterns) {
commandLineIncludePatterns.add(innerClassPattern)
(filter as? DefaultTestFilter)?.setCommandLineIncludePatterns(commandLineIncludePatterns)
} else {
filter.includePatterns.add(innerClassPattern)
}
}
var subProjectTempRoot: Path? = null val parentNames = if (jUnit5Enabled) {
doFirst { /*
val teamcity = rootProject.findProperty("teamcity") as? Map<*, *> * If we run test from inner test class with junit 5 we need
val systemTempRoot = * to include all containing classes of our class
// 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 val nestedNames = classFileNameWithoutExtension.split("$")
(teamcity?.get("teamcity.build.tempDir") as? String) mutableListOf(nestedNames.first()).also {
?: System.getProperty("java.io.tmpdir") for (s in nestedNames.subList(1, nestedNames.size)) {
systemTempRoot.let { it += "${it.last()}\$$s"
val prefix = (project.name + "Project_" + taskName + "_").takeUnless { shortenTempRootName } }
subProjectTempRoot = Files.createTempDirectory(File(systemTempRoot).toPath(), prefix) }
systemProperty("java.io.tmpdir", subProjectTempRoot.toString()) } else emptyList()
}
}
doLast { include { treeElement ->
subProjectTempRoot?.let { val path = treeElement.path
try { if (treeElement.isDirectory) {
delete(it) classFileNameWithoutExtension.startsWith(path)
} catch (e: Exception) { } else {
project.logger.warn("Can't delete test temp root folder $it", e.printStackTrace()) if (jUnit5Enabled) {
path == classFileName || (path.endsWith(".class") && parentNames.any { path.startsWith(it) })
} else {
path == classFileName || (path.endsWith(".class") && path.startsWith("$classFileNameWithoutExtension$"))
}
}
}
} }
} }
}
if (parallel && !jUnit5Enabled) { if (shouldInstrument) {
maxParallelForks = val instrumentationArgsProperty = project.providers.gradleProperty("kotlin.test.instrumentation.args")
project.findProperty("kotlin.test.maxParallelForks")?.toString()?.toInt() val testInstrumenterOutputs = project.tasks.findByPath(":test-instrumenter:jar")!!.outputs.files
?: (Runtime.getRuntime().availableProcessors() / if (kotlinBuildProperties.isTeamcityBuild) 2 else 4).coerceAtLeast(1) doFirst {
} val agent = testInstrumenterOutputs.singleFile
body() 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<FileSystemOperations>()
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()) private inline fun String.isFirstChar(f: (Char) -> Boolean) = isNotEmpty() && f(first())