[Build] Make projectTest() extension compatible with configuration cache
Relates to #KT-44611
This commit is contained in:
+127
-113
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user