[Gradle] Rework KotlinTestReport to be compatible with configuration cache
Minimal supported version is effectively changed to 6.1 due to usage of Gradle build services Relates to #KT-43605
This commit is contained in:
committed by
TeamCityServer
parent
3e09bb3d3f
commit
ef890464d8
+1
-1
@@ -22,7 +22,7 @@ open class KotlinAndroid36GradleIT : KotlinAndroid33GradleIT() {
|
|||||||
get() = AGPVersion.v3_6_0
|
get() = AGPVersion.v3_6_0
|
||||||
|
|
||||||
override val defaultGradleVersion: GradleVersionRequired
|
override val defaultGradleVersion: GradleVersionRequired
|
||||||
get() = GradleVersionRequired.AtLeast("6.0")
|
get() = GradleVersionRequired.AtLeast("6.1")
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ sealed class GradleVersionRequired(val minVersion: String, val maxVersion: Strin
|
|||||||
companion object {
|
companion object {
|
||||||
const val OLDEST_SUPPORTED = "5.3"
|
const val OLDEST_SUPPORTED = "5.3"
|
||||||
|
|
||||||
val FOR_MPP_SUPPORT = AtLeast("6.0")
|
val FOR_MPP_SUPPORT = AtLeast("6.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
class Exact(version: String) : GradleVersionRequired(version, version)
|
class Exact(version: String) : GradleVersionRequired(version, version)
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ import kotlin.test.assertFalse
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class KlibBasedMppIT : BaseGradleIT() {
|
class KlibBasedMppIT : BaseGradleIT() {
|
||||||
override val defaultGradleVersion = GradleVersionRequired.AtLeast("6.0")
|
override val defaultGradleVersion = GradleVersionRequired.AtLeast("6.1")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MODULE_GROUP = "com.example"
|
private const val MODULE_GROUP = "com.example"
|
||||||
|
|||||||
+2
-2
@@ -88,13 +88,11 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
|
|||||||
val appProject = transformProjectWithPluginsDsl(
|
val appProject = transformProjectWithPluginsDsl(
|
||||||
projectName = "app",
|
projectName = "app",
|
||||||
directoryPrefix = rootProjectName,
|
directoryPrefix = rootProjectName,
|
||||||
wrapperVersion = GradleVersionRequired.AtLeast("5.4")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val libProject = transformProjectWithPluginsDsl(
|
val libProject = transformProjectWithPluginsDsl(
|
||||||
projectName = "lib",
|
projectName = "lib",
|
||||||
directoryPrefix = rootProjectName,
|
directoryPrefix = rootProjectName,
|
||||||
wrapperVersion = GradleVersionRequired.AtLeast("5.4")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
libProject.gradleProperties().appendText(jsCompilerType(KotlinJsCompilerType.IR))
|
libProject.gradleProperties().appendText(jsCompilerType(KotlinJsCompilerType.IR))
|
||||||
@@ -300,6 +298,8 @@ class Kotlin2JsGradlePluginIT : AbstractKotlin2JsGradlePluginIT(false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class AbstractKotlin2JsGradlePluginIT(val irBackend: Boolean) : BaseGradleIT() {
|
abstract class AbstractKotlin2JsGradlePluginIT(val irBackend: Boolean) : BaseGradleIT() {
|
||||||
|
override val defaultGradleVersion = GradleVersionRequired.AtLeast("6.1")
|
||||||
|
|
||||||
override fun defaultBuildOptions(): BuildOptions =
|
override fun defaultBuildOptions(): BuildOptions =
|
||||||
super.defaultBuildOptions().copy(
|
super.defaultBuildOptions().copy(
|
||||||
jsIrBackend = irBackend,
|
jsIrBackend = irBackend,
|
||||||
|
|||||||
+2
@@ -10,6 +10,8 @@ import org.jetbrains.kotlin.gradle.util.modify
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class KotlinJsIrLibraryGradlePluginIT : BaseGradleIT() {
|
class KotlinJsIrLibraryGradlePluginIT : BaseGradleIT() {
|
||||||
|
override val defaultGradleVersion = GradleVersionRequired.AtLeast("6.1")
|
||||||
|
|
||||||
override fun defaultBuildOptions(): BuildOptions =
|
override fun defaultBuildOptions(): BuildOptions =
|
||||||
super.defaultBuildOptions().copy(
|
super.defaultBuildOptions().copy(
|
||||||
jsIrBackend = true,
|
jsIrBackend = true,
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import org.junit.Test
|
|||||||
class CleanDataTaskIT : BaseGradleIT() {
|
class CleanDataTaskIT : BaseGradleIT() {
|
||||||
|
|
||||||
override val defaultGradleVersion: GradleVersionRequired
|
override val defaultGradleVersion: GradleVersionRequired
|
||||||
get() = GradleVersionRequired.AtLeast("5.5.1")
|
get() = GradleVersionRequired.AtLeast("6.1")
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDownloadedFolderDeletion() {
|
fun testDownloadedFolderDeletion() {
|
||||||
|
|||||||
+47
-51
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.testing.internal
|
|||||||
|
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.execution.TaskExecutionGraph
|
import org.gradle.api.execution.TaskExecutionGraph
|
||||||
|
import org.gradle.api.provider.Provider
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
import org.gradle.api.tasks.Internal
|
import org.gradle.api.tasks.Internal
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
@@ -47,9 +48,12 @@ open class KotlinTestReport : TestReport() {
|
|||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var parent: KotlinTestReport? = null
|
private var parent: KotlinTestReport? = null
|
||||||
|
private val parentPaths = project.provider {
|
||||||
|
computeAllParentTasksPaths()
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this field is used in task action when the task is aggregate report, which makes it incompatible with configuration cache
|
|
||||||
@Internal
|
@Internal
|
||||||
|
@Transient
|
||||||
val children = mutableListOf<TaskProvider<KotlinTestReport>>()
|
val children = mutableListOf<TaskProvider<KotlinTestReport>>()
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
@@ -66,14 +70,24 @@ open class KotlinTestReport : TestReport() {
|
|||||||
@Input
|
@Input
|
||||||
var ignoreFailures: Boolean = false
|
var ignoreFailures: Boolean = false
|
||||||
|
|
||||||
private val hasOwnFailedTests
|
private val testReportServiceProvider = TestReportService.registerIfAbsent(project.gradle)
|
||||||
get() = failedTestsListener.hasOwnFailedTests ?: false
|
private val testReportService
|
||||||
|
get() = testReportServiceProvider.get()
|
||||||
|
|
||||||
private val hasFailedTests: Boolean
|
private val hasFailedTests: Boolean
|
||||||
get() = hasOwnFailedTests || children.any { it.get().hasFailedTests }
|
get() = testReportService.hasFailedTests(path)
|
||||||
|
|
||||||
private val suppressedRunningFailureListeners: MutableSet<SuppressedTestRunningFailureListener> = mutableSetOf()
|
private val failedTestsListener = FailedTestListener(parentPaths, testReportServiceProvider)
|
||||||
|
|
||||||
private val failedTestsListener = FailedTestListener()
|
private fun computeAllParentTasksPaths(): List<String> {
|
||||||
|
val allParents = mutableListOf<String>()
|
||||||
|
var cur: KotlinTestReport? = this
|
||||||
|
while (cur != null) {
|
||||||
|
allParents.add(cur.path)
|
||||||
|
cur = cur.parent
|
||||||
|
}
|
||||||
|
return allParents
|
||||||
|
}
|
||||||
|
|
||||||
fun addChild(childProvider: TaskProvider<KotlinTestReport>) {
|
fun addChild(childProvider: TaskProvider<KotlinTestReport>) {
|
||||||
val child = childProvider.get()
|
val child = childProvider.get()
|
||||||
@@ -85,7 +99,7 @@ open class KotlinTestReport : TestReport() {
|
|||||||
reportOnChildTasks(childProvider)
|
reportOnChildTasks(childProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reportOnChildTasks(childProvider: TaskProvider<KotlinTestReport>): Unit {
|
private fun reportOnChildTasks(childProvider: TaskProvider<KotlinTestReport>) {
|
||||||
val child = childProvider.get()
|
val child = childProvider.get()
|
||||||
|
|
||||||
child.testTasks.forEach {
|
child.testTasks.forEach {
|
||||||
@@ -101,9 +115,8 @@ open class KotlinTestReport : TestReport() {
|
|||||||
|
|
||||||
task.addTestListener(failedTestsListener)
|
task.addTestListener(failedTestsListener)
|
||||||
if (task is KotlinTest) {
|
if (task is KotlinTest) {
|
||||||
val listener = SuppressedTestRunningFailureListener(task.path)
|
val listener = SuppressedTestRunningFailureListener(parentPaths, task.path, testReportServiceProvider)
|
||||||
task.addRunListener(listener)
|
task.addRunListener(listener)
|
||||||
suppressedRunningFailureListeners.add(listener)
|
|
||||||
}
|
}
|
||||||
reportOn(task)
|
reportOn(task)
|
||||||
|
|
||||||
@@ -119,7 +132,7 @@ open class KotlinTestReport : TestReport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun reportOn(task: AbstractTestTask) {
|
private fun reportOn(task: AbstractTestTask) {
|
||||||
reportOn(task.binResultsDir)
|
reportOn(task.binaryResultsDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
open val htmlReportUrl: String?
|
open val htmlReportUrl: String?
|
||||||
@@ -133,7 +146,6 @@ open class KotlinTestReport : TestReport() {
|
|||||||
fun checkFailedTests() {
|
fun checkFailedTests() {
|
||||||
if (checkFailedTests) {
|
if (checkFailedTests) {
|
||||||
checkSuppressedRunningFailures()
|
checkSuppressedRunningFailures()
|
||||||
|
|
||||||
if (hasFailedTests) {
|
if (hasFailedTests) {
|
||||||
if (ignoreFailures) {
|
if (ignoreFailures) {
|
||||||
logger.warn(getFailingTestsMessage())
|
logger.warn(getFailingTestsMessage())
|
||||||
@@ -155,27 +167,12 @@ open class KotlinTestReport : TestReport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkSuppressedRunningFailures() {
|
private fun checkSuppressedRunningFailures() {
|
||||||
val allSuppressedRunningFailures = mutableListOf<Pair<String, Error>>()
|
val taskFailures = testReportService.getAggregatedTaskFailures(path)
|
||||||
|
if (taskFailures.isNotEmpty()) {
|
||||||
fun visitSuppressedRunningFailures(report: KotlinTestReport) {
|
|
||||||
report.suppressedRunningFailureListeners.forEach { listener ->
|
|
||||||
listener.failures.forEach { failure ->
|
|
||||||
allSuppressedRunningFailures.add(listener.taskPath to failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
report.children.forEach {
|
|
||||||
visitSuppressedRunningFailures(it.get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
visitSuppressedRunningFailures(this)
|
|
||||||
|
|
||||||
if (allSuppressedRunningFailures.isNotEmpty()) {
|
|
||||||
val allErrors = mutableListOf<Error>()
|
val allErrors = mutableListOf<Error>()
|
||||||
val msg = buildString {
|
val msg = buildString {
|
||||||
appendln("Failed to execute all tests:")
|
appendln("Failed to execute all tests:")
|
||||||
allSuppressedRunningFailures.groupBy { it.first }.forEach { path, errors ->
|
taskFailures.groupBy { it.first }.forEach { (path, errors) ->
|
||||||
append(path)
|
append(path)
|
||||||
append(": ")
|
append(": ")
|
||||||
var first = true
|
var first = true
|
||||||
@@ -246,40 +243,39 @@ open class KotlinTestReport : TestReport() {
|
|||||||
task.reports.junitXml.isEnabled = false
|
task.reports.junitXml.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SuppressedTestRunningFailureListener(val taskPath: String) : KotlinTestRunnerListener {
|
private class SuppressedTestRunningFailureListener(
|
||||||
@Transient
|
private val allListenedTaskParentsPaths: Provider<List<String>>,
|
||||||
private var failuresMutable: MutableList<Error>? = mutableListOf()
|
private val failedTaskPath: String,
|
||||||
|
private val testReportServiceProvider: Provider<TestReportService>
|
||||||
val failures: List<Error>
|
) : KotlinTestRunnerListener {
|
||||||
get() = failuresMutable ?: emptyList()
|
|
||||||
|
|
||||||
override fun runningFailure(failure: Error) {
|
override fun runningFailure(failure: Error) {
|
||||||
var failures = failuresMutable
|
allListenedTaskParentsPaths.get().forEach {
|
||||||
if (failures == null) {
|
testReportServiceProvider.get().reportFailure(failedTaskPath, it, failure)
|
||||||
// it is possible after deserialization
|
|
||||||
failures = mutableListOf()
|
|
||||||
failuresMutable = failures
|
|
||||||
}
|
}
|
||||||
failures.add(failure)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FailedTestListener : TestListener {
|
private class FailedTestListener(
|
||||||
@Transient
|
private val allListenedTaskParentsPaths: Provider<List<String>>,
|
||||||
var hasOwnFailedTests: Boolean? = false
|
private val testReportServiceProvider: Provider<TestReportService>
|
||||||
|
) : TestListener {
|
||||||
override fun beforeTest(testDescriptor: TestDescriptor) {
|
override fun beforeTest(testDescriptor: TestDescriptor) {}
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterSuite(suite: TestDescriptor, result: TestResult) {
|
override fun afterSuite(suite: TestDescriptor, result: TestResult) {
|
||||||
|
reportFailure(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeSuite(suite: TestDescriptor) {
|
override fun beforeSuite(suite: TestDescriptor) {}
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) {
|
override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) {
|
||||||
|
reportFailure(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reportFailure(result: TestResult) {
|
||||||
if (result.failedTestCount > 0) {
|
if (result.failedTestCount > 0) {
|
||||||
hasOwnFailedTests = true
|
allListenedTaskParentsPaths.get().forEach {
|
||||||
|
testReportServiceProvider.get().testFailed(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.gradle.testing.internal
|
||||||
|
|
||||||
|
import org.gradle.api.invocation.Gradle
|
||||||
|
import org.gradle.api.provider.Provider
|
||||||
|
import org.gradle.api.services.BuildService
|
||||||
|
import org.gradle.api.services.BuildServiceParameters
|
||||||
|
|
||||||
|
typealias TaskError = Pair<String, Error>
|
||||||
|
|
||||||
|
abstract class TestReportService : BuildService<BuildServiceParameters.None> {
|
||||||
|
private val taskHasFailedTests = mutableMapOf<String, Boolean>()
|
||||||
|
private val taskSuppressedFailures = mutableMapOf<String, MutableList<TaskError>>()
|
||||||
|
|
||||||
|
fun testFailed(taskPath: String) {
|
||||||
|
taskHasFailedTests[taskPath] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasFailedTests(path: String): Boolean {
|
||||||
|
return taskHasFailedTests[path] ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reportFailure(failedTaskPath: String, parentTaskPath: String, failure: Error) {
|
||||||
|
taskSuppressedFailures.computeIfAbsent(parentTaskPath) { mutableListOf() }.add(failedTaskPath to failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAggregatedTaskFailures(taskPath: String): List<TaskError> {
|
||||||
|
return taskSuppressedFailures[taskPath] ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun registerIfAbsent(gradle: Gradle): Provider<TestReportService> {
|
||||||
|
// Use class loader hashcode in case there are multiple class loaders in the same build
|
||||||
|
return gradle.sharedServices
|
||||||
|
.registerIfAbsent(
|
||||||
|
"${TestReportService::class.java.canonicalName}_${TestReportService::class.java.classLoader.hashCode()}",
|
||||||
|
TestReportService::class.java
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user