Add testing support for MPP projects

This commit is contained in:
Dmitry Savvinov
2020-01-20 16:39:01 +03:00
parent b34f091fa4
commit 36aa14ead4
15 changed files with 347 additions and 0 deletions
@@ -0,0 +1,100 @@
/*
* 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.idea.gradle.testing
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.model.task.TaskData
import com.intellij.openapi.externalSystem.util.Order
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.Consumer
import org.gradle.tooling.model.idea.IdeaModule
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel
import org.jetbrains.kotlin.gradle.KotlinMPPGradleModelBuilder
import org.jetbrains.kotlin.idea.configuration.getMppModel
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
@Order(Int.MIN_VALUE)
open class KotlinTestTasksResolver : AbstractProjectResolverExtension() {
companion object {
private const val ENABLED_REGISTRY_KEY = "kotlin.gradle.testing.enabled"
}
private val LOG by lazy { Logger.getInstance(KotlinTestTasksResolver::class.java) }
override fun getToolingExtensionsClasses(): Set<Class<out Any>> {
return setOf(KotlinMPPGradleModelBuilder::class.java, Unit::class.java)
}
override fun populateModuleTasks(
gradleModule: IdeaModule,
ideModule: DataNode<ModuleData>,
ideProject: DataNode<ProjectData>
): MutableCollection<TaskData> {
if (!Registry.`is`(ENABLED_REGISTRY_KEY))
return super.populateModuleTasks(gradleModule, ideModule, ideProject)
val mppModel = resolverCtx.getMppModel(gradleModule)
?: return super.populateModuleTasks(gradleModule, ideModule, ideProject)
return postprocessTaskData(mppModel, ideModule, nextResolver.populateModuleTasks(gradleModule, ideModule, ideProject))
}
private fun postprocessTaskData(
mppModel: KotlinMPPGradleModel,
ideModule: DataNode<ModuleData>,
originalTaskData: MutableCollection<TaskData>
): MutableCollection<TaskData> {
val testTaskNames = mutableSetOf<String>().apply {
mppModel.targets.forEach { target ->
target.testTasks.forEach { testTaskModel ->
add(testTaskModel.taskName)
}
}
}
fun buildNewTaskDataMarkedAsTest(original: TaskData): TaskData =
TaskData(original.owner, original.name, original.linkedExternalProjectPath, original.description).apply {
group = original.group
type = original.type
isInherited = original.isInherited
isTest = true
}
val replacementMap: Map<TaskData, TaskData> = mutableMapOf<TaskData, TaskData>().apply {
originalTaskData.forEach {
if (it.name in testTaskNames && !it.isTest) {
put(it, buildNewTaskDataMarkedAsTest(it))
}
}
}
ideModule.children.filter { it.data in replacementMap }.forEach { it.clear(true) }
replacementMap.values.forEach { ideModule.createChild(ProjectKeys.TASK, it) }
return originalTaskData.mapTo(arrayListOf<TaskData>()) { replacementMap[it] ?: it }
}
override fun enhanceTaskProcessing(taskNames: MutableList<String>, jvmAgentSetup: String?, initScriptConsumer: Consumer<String>) {
if (!Registry.`is`(ENABLED_REGISTRY_KEY))
return
try {
val testLoggerScript = javaClass
.getResourceAsStream("/org/jetbrains/kotlin/idea/gradle/testing/KotlinMppTestLogger.groovy")
.bufferedReader()
.readText()
initScriptConsumer.consume(testLoggerScript)
} catch (e: Exception) {
LOG.error(e)
}
}
}
@@ -0,0 +1,15 @@
/*
* 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.idea.gradle.testing.common
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.run.AbstractKotlinMultiplatformTestClassGradleConfigurationProducer
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.isCommon
class KotlinMultiplatformCommonTestClassGradleConfigurationProducer : AbstractKotlinMultiplatformTestClassGradleConfigurationProducer() {
override fun isApplicable(module: Module, platform: TargetPlatform) = platform.isCommon()
}
@@ -0,0 +1,15 @@
/*
* 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.idea.gradle.testing.common
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.run.AbstractKotlinMultiplatformTestMethodGradleConfigurationProducer
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.isCommon
class KotlinMultiplatformCommonTestMethodGradleConfigurationProducer : AbstractKotlinMultiplatformTestMethodGradleConfigurationProducer() {
override fun isApplicable(module: Module, platform: TargetPlatform) = platform.isCommon()
}
@@ -0,0 +1,23 @@
/*
* 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.idea.gradle.testing.js
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.js.KotlinJSRunConfigurationDataProvider
import org.jetbrains.kotlin.idea.run.AbstractKotlinMultiplatformTestClassGradleConfigurationProducer
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.js.isJs
class KotlinMultiplatformJsTestClassGradleConfigurationProducer
: AbstractKotlinMultiplatformTestClassGradleConfigurationProducer(), KotlinJSRunConfigurationDataProvider<Unit>
{
override val isForTests: Boolean get() = true
override fun isApplicable(module: Module, platform: TargetPlatform) = platform.isJs()
override fun getConfigurationData(context: ConfigurationContext) = Unit
override val hasTestFramework get() = true
}
@@ -0,0 +1,22 @@
/*
* 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.idea.gradle.testing.js
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.js.KotlinJSRunConfigurationDataProvider
import org.jetbrains.kotlin.idea.run.AbstractKotlinMultiplatformTestMethodGradleConfigurationProducer
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.js.isJs
class KotlinMultiplatformJsTestMethodGradleConfigurationProducer
: AbstractKotlinMultiplatformTestMethodGradleConfigurationProducer(), KotlinJSRunConfigurationDataProvider<Unit>
{
override val isForTests: Boolean get() = true
override fun isApplicable(module: Module, platform: TargetPlatform) = platform.isJs()
override fun getConfigurationData(context: ConfigurationContext) = Unit
}
@@ -0,0 +1,19 @@
/*
* 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.idea.gradle.testing.native
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.ide.konan.KotlinNativeRunConfigurationProvider
import org.jetbrains.kotlin.idea.run.AbstractKotlinMultiplatformTestClassGradleConfigurationProducer
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.konan.isNative
class KotlinMultiplatformNativeTestClassGradleConfigurationProducer
: AbstractKotlinMultiplatformTestClassGradleConfigurationProducer(), KotlinNativeRunConfigurationProvider
{
override val isForTests: Boolean get() = true
override fun isApplicable(module: Module, platform: TargetPlatform) = platform.isNative()
}
@@ -0,0 +1,19 @@
/*
* 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.idea.gradle.testing.native
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.ide.konan.KotlinNativeRunConfigurationProvider
import org.jetbrains.kotlin.idea.run.AbstractKotlinMultiplatformTestMethodGradleConfigurationProducer
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.konan.isNative
class KotlinMultiplatformNativeTestMethodGradleConfigurationProducer
: AbstractKotlinMultiplatformTestMethodGradleConfigurationProducer(), KotlinNativeRunConfigurationProvider
{
override val isForTests: Boolean get() = true
override fun isApplicable(module: Module, platform: TargetPlatform) = platform.isNative()
}
@@ -0,0 +1,22 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<registryKey key="kotlin.gradle.testing.enabled"
description="Import Kotlin Gradle test tasks and process test output"
defaultValue="false"
restartRequired="false"/>
<registryKey key="kotlin.mpp.tests.force.gradle"
description="Run multi-platform tests with Gradle runner even if the platform runner is used by default.
This setting currently affects only HMPP projects. You may need to delete existing test configurations for the change to take place."
defaultValue="true"
restartRequired="false"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.gradle.testing.js.KotlinMultiplatformJsTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.gradle.testing.js.KotlinMultiplatformJsTestMethodGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.gradle.testing.native.KotlinMultiplatformNativeTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.gradle.testing.native.KotlinMultiplatformNativeTestMethodGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.gradle.testing.common.KotlinMultiplatformCommonTestClassGradleConfigurationProducer"/>
<runConfigurationProducer implementation="org.jetbrains.kotlin.idea.gradle.testing.common.KotlinMultiplatformCommonTestMethodGradleConfigurationProducer"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
<projectResolve implementation="org.jetbrains.kotlin.idea.gradle.testing.KotlinTestTasksResolver"/>
</extensions>
</idea-plugin>
+1
View File
@@ -20,6 +20,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<depends optional="true" config-file="junit.xml">JUnit</depends>
<depends optional="true" config-file="gradle.xml">com.intellij.gradle</depends>
<depends optional="true" config-file="gradle-java.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="kotlin-gradle-testing.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="gradle-groovy.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="maven.xml">org.jetbrains.idea.maven</depends>
<depends optional="true" config-file="testng-j.xml">TestNG-J</depends>
+1
View File
@@ -20,6 +20,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<depends optional="true" config-file="junit.xml">JUnit</depends>
<depends optional="true" config-file="gradle.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="gradle-java.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="kotlin-gradle-testing.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="gradle-groovy.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="maven.xml">org.jetbrains.idea.maven</depends>
<depends optional="true" config-file="testng-j.xml">TestNG-J</depends>
+1
View File
@@ -20,6 +20,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<depends optional="true" config-file="junit.xml">JUnit</depends>
<depends optional="true" config-file="gradle.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="gradle-java.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="kotlin-gradle-testing.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="gradle-groovy.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="maven.xml">org.jetbrains.idea.maven</depends>
<depends optional="true" config-file="testng-j.xml">TestNG-J</depends>
+1
View File
@@ -21,6 +21,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<depends optional="true" config-file="junit.xml">JUnit</depends>
<depends optional="true" config-file="gradle.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="gradle-java.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="kotlin-gradle-testing.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="gradle-groovy.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="maven.xml">org.jetbrains.idea.maven</depends>
<depends optional="true" config-file="testng-j.xml">TestNG-J</depends>
+1
View File
@@ -21,6 +21,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<depends optional="true" config-file="junit.xml">JUnit</depends>
<depends optional="true" config-file="gradle.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="gradle-java.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="kotlin-gradle-testing.xml">org.jetbrains.plugins.gradle.java</depends>
<depends optional="true" config-file="gradle-groovy.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="maven.xml">org.jetbrains.idea.maven</depends>
<depends optional="true" config-file="testng-j.xml">TestNG-J</depends>
+1
View File
@@ -21,6 +21,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<depends optional="true" config-file="junit.xml">JUnit</depends>
<depends optional="true" config-file="gradle.xml">com.intellij.gradle</depends>
<depends optional="true" config-file="gradle-java.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="kotlin-gradle-testing.xml">org.jetbrains.plugins.gradle</depends>
<depends optional="true" config-file="gradle-groovy.xml">org.intellij.groovy</depends>
<depends optional="true" config-file="maven.xml">org.jetbrains.idea.maven</depends>
<depends optional="true" config-file="testng-j.xml">TestNG-J</depends>
@@ -0,0 +1,106 @@
import groovy.xml.MarkupBuilder
import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
class KotlinMppTestLogger {
static def configureTestEventLogging(def task) {
task.beforeSuite { descriptor -> logTestEvent("beforeSuite", descriptor, null, null) }
task.afterSuite { descriptor, result -> logTestEvent("afterSuite", descriptor, null, result) }
task.beforeTest { descriptor -> logTestEvent("beforeTest", descriptor, null, null) }
task.onOutput { descriptor, event -> logTestEvent("onOutput", descriptor, event, null) }
task.afterTest { descriptor, result -> logTestEvent("afterTest", descriptor, null, result) }
}
static def logTestEvent(testEventType, TestDescriptorInternal testDescriptor, testEvent, testResult) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.event(type: testEventType) {
test(id: testDescriptor.id, parentId: testDescriptor.parent?.id ?: '') {
if (testDescriptor) {
descriptor(name: testDescriptor.displayName ?: '', className: testDescriptor.classDisplayName ?: '')
}
if (testEvent) {
def message = escapeCdata(testEvent.message)
event(destination: testEvent.destination) {
xml.mkp.yieldUnescaped("$message")
}
}
if (testResult) {
def errorMsg = escapeCdata(testResult.exception?.message ?: '')
def stackTrace = escapeCdata(getStackTrace(testResult.exception))
result(resultType: testResult.resultType ?: '', startTime: testResult.startTime, endTime: testResult.endTime) {
def exception = testResult.exception
if (exception?.message?.trim()) xml.mkp.yieldUnescaped("<errorMsg>$errorMsg</errorMsg>")
if (exception) xml.mkp.yieldUnescaped("<stackTrace>$stackTrace</stackTrace>")
if ('kotlin.AssertionError'.equals(exception?.class?.name) || exception instanceof AssertionError) {
failureType('assertionFailed')
return
}
failureType('error')
}
}
}
}
writeLog(writer.toString())
}
static String escapeCdata(String s) {
return "<![CDATA[" + s?.getBytes("UTF-8")?.encodeBase64()?.toString() + "]]>";
}
static def wrap(String s) {
if(!s) return s;
s.replaceAll("\r\n|\n\r|\n|\r","<ijLogEol/>\n")
}
static def writeLog(s) {
println String.format("\n<ijLog>%s</ijLog>", wrap(s))
}
static def logTestReportLocation(def report) {
if(!report) return
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.event(type: 'reportLocation', testReport: report)
writeLog(writer.toString());
}
static def logConfigurationError(aTitle, aMessage, boolean openSettings) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.event(type: 'configurationError', openSettings: openSettings) {
title(aTitle)
message(aMessage)
}
writeLog(writer.toString());
}
static def getStackTrace(Throwable t) {
if(!t) return ''
StringWriter sw = new StringWriter()
t.printStackTrace(new PrintWriter(sw))
sw.toString()
}
}
gradle.taskGraph.beforeTask { task ->
def taskSuperClass = task.class
while (taskSuperClass != Object.class) {
if (taskSuperClass.canonicalName == "org.jetbrains.kotlin.gradle.tasks.KotlinTest") {
try {
KotlinMppTestLogger.logTestReportLocation(task.reports?.html?.entryPoint?.path)
KotlinMppTestLogger.configureTestEventLogging(task)
task.testLogging.showStandardStreams = false
}
catch (all) {
logger.error("", all)
}
return
} else {
taskSuperClass = taskSuperClass.superclass
}
}
}