Gradle, js, tests: always create a jsTest task for compatibility
This requires runnable aggregate test task even if there are not tests in it. That, in turn, requires support aggregate test tasks hierarchy. Also TestReport integrated in aggregate for all gradle versions (was for 5 only). #KT-31527 Fixed
This commit is contained in:
+5
-3
@@ -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)
|
||||
|
||||
+6
-1
@@ -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<KotlinJsCompilation>) {
|
||||
// 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<*> {
|
||||
|
||||
-97
@@ -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<AbstractTestTask>()
|
||||
|
||||
/**
|
||||
* Returns the set of binary test results to include in the report.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val testResultDirs: Collection<File>
|
||||
@PathSensitive(PathSensitivity.NONE)
|
||||
@InputFiles
|
||||
@SkipWhenEmpty
|
||||
get() {
|
||||
val dirs = mutableListOf<File>()
|
||||
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
|
||||
}
|
||||
}
|
||||
-117
@@ -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<TestResultsProvider>()
|
||||
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
|
||||
}
|
||||
}
|
||||
+181
@@ -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<AbstractTestTask>()
|
||||
|
||||
@Internal
|
||||
private var parent: KotlinTestReport? = null
|
||||
|
||||
@Internal
|
||||
val children = mutableListOf<KotlinTestReport>()
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
+30
-24
@@ -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<AggregateTestReport>
|
||||
get() = getAggregatedTestTask(
|
||||
internal val Project.allTestsTask: TaskHolder<KotlinTestReport>
|
||||
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<AggregateTestReport> {
|
||||
return locateOrRegisterTask(name) { aggregate ->
|
||||
internal fun Project.getOrCreateAggregatedTestTask(
|
||||
name: String,
|
||||
description: String,
|
||||
reportName: String,
|
||||
parent: KotlinTestReport? = null,
|
||||
configure: (TaskHolder<KotlinTestReport>) -> Unit = {}
|
||||
): TaskHolder<KotlinTestReport> {
|
||||
val existed = locateTask<KotlinTestReport>(name)
|
||||
if (existed != null) return existed
|
||||
|
||||
val aggregate: TaskHolder<KotlinTestReport> = 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<AbstractTestTask>) {
|
||||
|
||||
internal fun registerTestTaskInAggregate(
|
||||
taskHolder: TaskHolder<AbstractTestTask>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user