diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt index 73f3070c037..cd4a2fb8ea8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTarget.kt @@ -17,7 +17,8 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsNodeDsl import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinBrowserJs import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinNodeJs -import org.jetbrains.kotlin.gradle.testing.internal.getAggregatedTestTask +import org.jetbrains.kotlin.gradle.testing.internal.allTestsTask +import org.jetbrains.kotlin.gradle.testing.internal.getOrCreateAggregatedTestTask import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName class KotlinJsTarget(project: Project, platformType: KotlinPlatformType) : @@ -25,10 +26,11 @@ class KotlinJsTarget(project: Project, platformType: KotlinPlatformType) : val testTaskName get() = lowerCamelCaseName(disambiguationClassifier, testTaskNameSuffix) val testTask - get() = project.getAggregatedTestTask( + get() = project.getOrCreateAggregatedTestTask( name = testTaskName, description = "Run JS tests for all platforms", - reportName = "jsAll" + reportName = "jsAll", + parent = project.allTestsTask.doGetTask() ) val runTaskName get() = lowerCamelCaseName(disambiguationClassifier, runTaskNameSuffix) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt index a7183812341..1ac56ce7991 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.gradle.targets.js +import org.gradle.language.base.plugins.LifecycleBasePlugin import org.jetbrains.kotlin.gradle.plugin.Kotlin2JsSourceSetProcessor import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator @@ -27,7 +28,11 @@ open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) : } override fun configureTest(target: KotlinOnlyTarget) { - // tests configured in KotlinJsSubTarget.configure + target as KotlinJsTarget + + // always create jsTest task for compatibility (KT-31527) + // actual tests tasks for browser and nodejs will be configured in KotlinJsSubTarget.configure + target.testTask } override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/AggregateTestReport.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/AggregateTestReport.kt deleted file mode 100644 index 78ea58aa6a2..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/AggregateTestReport.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2010-2019 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.GradleException -import org.gradle.api.internal.AbstractTask -import org.gradle.api.tasks.* -import org.gradle.api.tasks.testing.AbstractTestTask -import org.gradle.api.tasks.testing.TestDescriptor -import org.gradle.api.tasks.testing.TestListener -import org.gradle.api.tasks.testing.TestResult -import java.io.File - -open class AggregateTestReport : AbstractTask() { - @Internal - val testTasks = mutableListOf() - - /** - * Returns the set of binary test results to include in the report. - */ - @Suppress("MemberVisibilityCanBePrivate") - val testResultDirs: Collection - @PathSensitive(PathSensitivity.NONE) - @InputFiles - @SkipWhenEmpty - get() { - val dirs = mutableListOf() - testTasks.forEach { - @Suppress("UnstableApiUsage") - dirs.add(it.binResultsDir) - } - return dirs - } - - @Input - var ignoreFailures: Boolean = false - - private var hasFailedTests = false - - private val failedTestsListener = object : TestListener { - override fun beforeTest(testDescriptor: TestDescriptor) { - } - - override fun afterSuite(suite: TestDescriptor, result: TestResult) { - } - - override fun beforeSuite(suite: TestDescriptor) { - } - - override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { - if (result.failedTestCount > 0) { - hasFailedTests = true - } - } - } - - @Input - var checkFailedTests: Boolean = false - - fun registerTestTask(task: AbstractTestTask) { - testTasks.add(task) - task.addTestListener(failedTestsListener) - } - - open fun configureReportsConvention(name: String) { - - } - - @Internal - protected open val htmlReportUrl: String? = null - - protected fun checkFailedTests() { - if (checkFailedTests && hasFailedTests) { - val message = StringBuilder("There were failing tests.") - - val reportUrl = htmlReportUrl - if (reportUrl != null) { - message.append(" See the report at: $reportUrl") - } - - if (ignoreFailures) { - logger.warn(message.toString()) - } else { - throw GradleException(message.toString()) - } - } - } - - open fun overrideReporting(task: AbstractTestTask) { - task.ignoreFailures = true - checkFailedTests = true - ignoreFailures = false - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/AggregateTestReportGradle5.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/AggregateTestReportGradle5.kt deleted file mode 100644 index 237ef441091..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/AggregateTestReportGradle5.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2010-2019 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.internal.CollectionCallbackActionDecorator -import org.gradle.api.internal.tasks.testing.DefaultTestTaskReports -import org.gradle.api.internal.tasks.testing.junit.result.* -import org.gradle.api.internal.tasks.testing.report.DefaultTestReport -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.Nested -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.testing.AbstractTestTask -import org.gradle.internal.concurrent.CompositeStoppable.stoppable -import org.gradle.internal.logging.ConsoleRenderer -import org.gradle.internal.operations.BuildOperationExecutor -import org.gradle.internal.reflect.Instantiator -import org.gradle.internal.remote.internal.inet.InetAddressFactory -import org.jetbrains.kotlin.gradle.utils.injected -import java.util.* -import javax.inject.Inject - -@Suppress("UnstableApiUsage") -open class AggregateTestReportGradle5 : AggregateTestReport() { - @Internal - val reports: DefaultTestTaskReports - - protected open val instantiator: Instantiator - @Inject get() = injected - - protected open val collectionCallbackActionDecorator: CollectionCallbackActionDecorator - @Inject get() = injected - - protected open val buildOperationExecutor: BuildOperationExecutor - @Inject get() = injected - - protected open val inetAddressFactory: InetAddressFactory - @Inject get() = injected - - init { - @Suppress("LeakingThis") - reports = instantiator.newInstance(DefaultTestTaskReports::class.java, this, collectionCallbackActionDecorator) - reports.junitXml.isEnabled = true - reports.html.isEnabled = true - } - - override fun configureReportsConvention(name: String) { - reports.configureConventions(project, name) - } - - override val htmlReportUrl: String? - get() = - if (reports.html.isEnabled) ConsoleRenderer().asClickableFileUrl(reports.html.destination.resolve("index.html")) - else null - - @TaskAction - fun generateReport() { - val resultsProvider = createAggregateProvider() - try { - if (resultsProvider.isHasResults) { - val junitXml = reports.junitXml - if (junitXml.isEnabled) { - Binary2JUnitXmlReportGenerator( - junitXml.destination, - resultsProvider, - when { - junitXml.isOutputPerTestCase -> TestOutputAssociation.WITH_TESTCASE - else -> TestOutputAssociation.WITH_SUITE - }, - buildOperationExecutor, - inetAddressFactory.hostname - ).generate() - } - - val html = reports.html - if (html.isEnabled) { - val testReport = DefaultTestReport(buildOperationExecutor) - testReport.generateReport(resultsProvider, html.destination) - } - } else { - logger.info("{} - no binary test results found in dirs: {}.", path, testResultDirs) - didWork = false - } - } finally { - stoppable(resultsProvider).stop() - } - - checkFailedTests() - } - - private fun createAggregateProvider(): TestResultsProvider { - val resultsProviders = LinkedList() - try { - val resultDirs = testResultDirs - return if (resultDirs.size == 1) BinaryResultBackedTestResultsProvider(resultDirs.single()) - else AggregateTestResultsProvider(resultDirs.mapTo(resultsProviders) { BinaryResultBackedTestResultsProvider(it) }) - } catch (e: RuntimeException) { - stoppable(resultsProviders).stop() - throw e - } - } - - override fun overrideReporting(task: AbstractTestTask) { - task.ignoreFailures = true - - @Suppress("UnstableApiUsage") - task.reports.html.isEnabled = false - - @Suppress("UnstableApiUsage") - task.reports.junitXml.isEnabled = false - - checkFailedTests = true - ignoreFailures = false - } -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/KotlinTestReport.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/KotlinTestReport.kt new file mode 100644 index 00000000000..09958c9d5a9 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/KotlinTestReport.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2010-2019 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.GradleException +import org.gradle.api.execution.TaskExecutionGraph +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.testing.* +import java.io.File +import java.net.URI + +/** + * Aggregates tests reports for kotlin test tasks added by [registerTestTask]. + * + * Individual test tasks will not fail build if this task will be executed, + * also individual html and xml reports will replaced by one consolidated html report. + * This behavior can be disabled by setting [overrideReporting] to `false`. + * + * Aggregate test reports may form hierarchy, for example: + * - allTest // aggregates all tests + * - jvmTest + * - nativeTest // aggregates all native tests + * - macos64Test + * - linux64Test + * - mingw64 + * - jsTests // aggregates all js tests + * - browserTest + * - nodejsTest + * + * In this case, only topmost aggregate test task will override reporting, + * event if child tasks will be executed. + */ +open class KotlinTestReport : TestReport() { + @Internal + val testTasks = mutableListOf() + + @Internal + private var parent: KotlinTestReport? = null + + @Internal + val children = mutableListOf() + + @Input + var overrideReporting: Boolean = true + + @Input + var checkFailedTests: Boolean = false + + @Input + var ignoreFailures: Boolean = false + + private var hasOwnFailedTests = false + private val hasFailedTests: Boolean + get() = hasOwnFailedTests || children.any { it.hasFailedTests } + + private val failedTestsListener = object : TestListener { + override fun beforeTest(testDescriptor: TestDescriptor) { + } + + override fun afterSuite(suite: TestDescriptor, result: TestResult) { + } + + override fun beforeSuite(suite: TestDescriptor) { + } + + override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { + if (result.failedTestCount > 0) { + hasOwnFailedTests = true + } + } + } + + fun addChild(child: KotlinTestReport) { + check(child.parent == null) { "$child already registers as child of ${child.parent}" } + child.parent = this + + children.add(child) + reportOnChildTasks(child) + } + + private fun reportOnChildTasks(child: KotlinTestReport) { + child.testTasks.forEach { + reportOn(it) + } + child.children.forEach { + reportOnChildTasks(it) + } + } + + fun registerTestTask(task: AbstractTestTask) { + testTasks.add(task) + task.addTestListener(failedTestsListener) + reportOn(task) + + addToParents(task) + } + + private fun addToParents(task: AbstractTestTask) { + val parent = parent + if (parent != null) { + parent.reportOn(task) + parent.addToParents(task) + } + } + + private fun reportOn(task: AbstractTestTask) { + @Suppress("UnstableApiUsage") + reportOn(task.binResultsDir) + } + + open val htmlReportUrl: String? + @Internal get() = destinationDir?.let { asClickableFileUrl(it.resolve("index.html")) } + + private fun asClickableFileUrl(path: File): String { + return URI("file", "", path.toURI().path, null, null).toString() + } + + @TaskAction + fun checkFailedTests() { + if (checkFailedTests && hasFailedTests) { + val message = StringBuilder("There were failing tests.") + + val reportUrl = htmlReportUrl + if (reportUrl != null) { + message.append(" See the report at: $reportUrl") + } + + if (ignoreFailures) { + logger.warn(message.toString()) + } else { + throw GradleException(message.toString()) + } + } + } + + fun maybeOverrideReporting(graph: TaskExecutionGraph) { + if (!graph.hasTask(this)) return + + // only topmost aggregate should override reporting + var parent = parent + while (parent != null) { + if (parent.overrideReporting && graph.hasTask(parent)) return + parent = parent.parent + } + + overrideReporting() + } + + private fun overrideReporting() { + ignoreFailures = false + checkFailedTests = true + + disableIndividualTestTaskReportingAndFailing() + } + + private fun disableIndividualTestTaskReportingAndFailing() { + testTasks.forEach { + disableTestReporting(it) + } + + children.forEach { + it.checkFailedTests = false + it.disableIndividualTestTaskReportingAndFailing() + } + } + + private fun disableTestReporting(task: AbstractTestTask) { + task.ignoreFailures = true + + @Suppress("UnstableApiUsage") + task.reports.html.isEnabled = false + + @Suppress("UnstableApiUsage") + task.reports.junitXml.isEnabled = false + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt index 2141c0004c8..148c16e11f4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/internal/testsRegistry.kt @@ -11,33 +11,51 @@ import org.gradle.api.plugins.JavaBasePlugin import org.gradle.api.tasks.testing.AbstractTestTask import org.gradle.language.base.plugins.LifecycleBasePlugin import org.jetbrains.kotlin.gradle.plugin.TaskHolder -import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask +import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask +import org.jetbrains.kotlin.gradle.tasks.locateTask -private val Project.allTestsTask: TaskHolder - get() = getAggregatedTestTask( +internal val Project.allTestsTask: TaskHolder + get() = getOrCreateAggregatedTestTask( name = "allTests", description = "Runs the tests for all targets and create aggregated report", reportName = "all" - ).also { + ) { tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME) .dependsOn(it.getTaskOrProvider()) } -internal fun Project.getAggregatedTestTask(name: String, description: String, reportName: String): TaskHolder { - return locateOrRegisterTask(name) { aggregate -> +internal fun Project.getOrCreateAggregatedTestTask( + name: String, + description: String, + reportName: String, + parent: KotlinTestReport? = null, + configure: (TaskHolder) -> Unit = {} +): TaskHolder { + val existed = locateTask(name) + if (existed != null) return existed + + val aggregate: TaskHolder = createOrRegisterTask(name) { aggregate -> aggregate.description = description aggregate.group = JavaBasePlugin.VERIFICATION_GROUP - aggregate.configureReportsConvention(reportName) - - aggregate.onlyIf { - aggregate.testTasks.size > 1 - } + aggregate.destinationDir = testReportsDir.resolve(reportName) if (System.getProperty("idea.active") != null) { aggregate.extensions.extraProperties.set("idea.internal.test", true) } + + project.gradle.taskGraph.whenReady { graph -> + aggregate.maybeOverrideReporting(graph) + } + + parent?.addChild(aggregate) } + + parent?.dependsOn(aggregate.getTaskOrProvider()) + + configure(aggregate) + + return aggregate } private fun cleanTaskName(taskName: String): String { @@ -61,7 +79,7 @@ internal fun registerTestTask(taskHolder: TaskHolder) { internal fun registerTestTaskInAggregate( taskHolder: TaskHolder, - allTests: AggregateTestReport + allTests: KotlinTestReport ) { val project = taskHolder.project @@ -71,18 +89,6 @@ internal fun registerTestTaskInAggregate( taskHolder.configure { task -> allTests.registerTestTask(task) - - project.gradle.taskGraph.whenReady { - if (it.hasTask(allTests)) { - // when [allTestsTask] task enabled, test failure should be reported only on [allTestsTask], - // not at individual target's test tasks. To do that, we need: - // - disable all reporting in test tasks - // - enable [checkFailedTests] on [allTestsTask] - - allTests.overrideReporting(task) - } - } - ijListenTestTask(task) } } \ No newline at end of file