From bdac47369c30ab15984561342109c357c1ec095e Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Tue, 2 Apr 2019 13:01:32 +0300 Subject: [PATCH] Gradle, Native: run tests with proper Test task, process test results #KT-30529 Fixed --- .../jetbrains/kotlin/gradle/BaseGradleIT.kt | 2 +- .../kotlin/gradle/NewMultiplatformIT.kt | 5 +- .../new-mpp-js-tests/TEST-CommonTest-2.xml | 4 +- .../new-mpp-js-tests/TEST-CommonTest.xml | 2 +- .../new-mpp-native-tests/TEST-TestKt.xml | 10 ++ .../testing/TCServiceMessagesClient.kt | 105 +++++++++----- .../testing/TCServiceMessagesTestExecutor.kt | 13 +- .../KotlinJsCompilationTestsConfigurator.kt | 22 +-- .../gradle/targets/js/NodeJsTestFailure.kt | 15 -- .../targets/js/tasks/KotlinNodeJsTestTask.kt | 60 ++------ .../native/KotlinNativeTaretConfigurator.kt | 51 ++++--- .../native/tasks/KotlinNativeTestTask.kt | 83 ++++++++++++ .../kotlin/gradle/tasks/KotlinTestTask.kt | 85 ++++++++++++ .../gradle/testing/KotlinTestFailure.kt | 24 ++++ .../testing/RecordingTestResultProcessor.kt | 2 +- .../gradle/internal/testing/tcsmc/Complex.kt | 30 ++-- .../tcsmc/TCServiceMessagesClientTest.kt | 8 +- .../internal/testing/tcsmc/TestFailureTest.kt | 128 ++++++++++++++++++ 18 files changed, 487 insertions(+), 162 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/TEST-TestKt.xml delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NodeJsTestFailure.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinTestTask.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestFailureTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index fb4f342547a..c555e04d09b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -608,7 +608,7 @@ abstract class BaseGradleIT { val testReportDir = projectDir.resolve("build/test-results/$testReportName") if (!testReportDir.isDirectory) { - error("Test report dir was not created") + error("Test report dir $testReportDir was not created") } val actualTestResults = readAndCleanupTestResults(testReportDir, projectDir) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index 3d27f068692..d9a34b596f7 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -1075,7 +1075,7 @@ class NewMultiplatformIT : BaseGradleIT() { @Test fun testNativeTests() = with(Project("new-mpp-native-tests", gradleVersion)) { val testTasks = listOf("macos64Test", "linux64Test", "mingw64Test") - val hostTestTask = ":${nativeHostTargetName}Test" + val hostTestTask = "${nativeHostTargetName}Test" build("tasks") { assertSuccessful() println(output) @@ -1086,7 +1086,8 @@ class NewMultiplatformIT : BaseGradleIT() { } build("check") { assertSuccessful() - assertTasksExecuted(hostTestTask) + assertTasksExecuted(":$hostTestTask") + assertTestResults("testProject/new-mpp-native-tests/TEST-TestKt.xml", hostTestTask) } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest-2.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest-2.xml index b70866724b4..051e07a4959 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest-2.xml +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest-2.xml @@ -3,13 +3,13 @@ - ... + ... - ... + ... diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest.xml index 1e5a796318d..f049dbf5866 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest.xml +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest.xml @@ -7,7 +7,7 @@ - ... + ... diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/TEST-TestKt.xml b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/TEST-TestKt.xml new file mode 100644 index 00000000000..e64a9e45973 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/TEST-TestKt.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt index a290ce8e5ee..4601f6338d1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt @@ -2,23 +2,25 @@ package org.jetbrains.kotlin.gradle.internal.testing import jetbrains.buildServer.messages.serviceMessages.* import org.gradle.api.internal.tasks.testing.* +import org.gradle.api.tasks.testing.TestOutputEvent import org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr import org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut import org.gradle.api.tasks.testing.TestResult import org.gradle.api.tasks.testing.TestResult.ResultType.* import org.gradle.internal.operations.OperationIdentifier import org.jetbrains.kotlin.gradle.logging.kotlinDebug -import org.jetbrains.kotlin.gradle.targets.js.NodeJsTestFailure +import org.jetbrains.kotlin.gradle.testing.KotlinTestFailure import org.slf4j.Logger import java.text.ParseException import java.lang.System.currentTimeMillis as currentTimeMillis1 data class TCServiceMessagesClientSettings( - val rootNodeName: String, - val nameOfRootSuiteToAppend: String? = null, - val nameOfRootSuiteToReplace: String? = null, - val nameOfLeafTestToAppend: String? = null, - val skipRoots: Boolean = false + val rootNodeName: String, + val nameOfRootSuiteToAppend: String? = null, + val nameOfRootSuiteToReplace: String? = null, + val nameOfLeafTestToAppend: String? = null, + val skipRoots: Boolean = false, + val treatFailedTestOutputAsStacktrace: Boolean = false ) { init { if (skipRoots) { @@ -29,9 +31,9 @@ data class TCServiceMessagesClientSettings( } internal class TCServiceMessagesClient( - private val results: TestResultProcessor, - val settings: TCServiceMessagesClientSettings, - val log: Logger + private val results: TestResultProcessor, + val settings: TCServiceMessagesClientSettings, + val log: Logger ) : ServiceMessageParserCallback { inline fun root(operation: OperationIdentifier, actions: () -> Unit) { RootNode(operation.id).open { root -> @@ -55,8 +57,8 @@ internal class TCServiceMessagesClient( when (message) { is TestSuiteStarted -> open(message.ts, SuiteNode(leaf, hookSuiteName(leaf, message.suiteName))) is TestStarted -> beginTest(message.ts, message.testName) - is TestStdOut -> results.output(requireLeafTest().descriptor, DefaultTestOutputEvent(StdOut, message.stdOut)) - is TestStdErr -> results.output(requireLeafTest().descriptor, DefaultTestOutputEvent(StdErr, message.stdErr)) + is TestStdOut -> requireLeafTest().output(StdOut, message.stdOut) + is TestStdErr -> requireLeafTest().output(StdErr, message.stdErr) is TestFailed -> requireLeafTest().failure(message) is TestFinished -> endTest(message.ts, message.testName) is TestIgnored -> { @@ -74,17 +76,18 @@ internal class TCServiceMessagesClient( } private fun hookSuiteName(parent: Node?, originalName: String) = - if (parent?.parent == null && settings.nameOfRootSuiteToReplace != null) settings.nameOfRootSuiteToReplace - else originalName + if (parent?.parent == null && settings.nameOfRootSuiteToReplace != null) settings.nameOfRootSuiteToReplace + else originalName override fun regularText(text: String) { log.kotlinDebug { "TCSM stdout captured: $text" } + val actualText = text + "\n" val test = leaf as? TestNode if (test != null) { - results.output(test.descriptor, DefaultTestOutputEvent(StdOut, text)) + test.output(StdOut, actualText) } else { - println(text) + print(actualText) } } @@ -95,19 +98,23 @@ internal class TCServiceMessagesClient( if (settings.nameOfLeafTestToAppend != null) { val group = open(ts, SuiteNode(parent, parsedName.methodName)) - open(ts, TestNode( + open( + ts, TestNode( group, parsedName.className, parsedName.classDisplayName, parsedName.methodName, displayName = "${parsedName.methodName}.${settings.nameOfLeafTestToAppend}", localId = "$testName.${settings.nameOfLeafTestToAppend}", ignored = isIgnored - )) + ) + ) } else { - open(ts, TestNode( + open( + ts, TestNode( parent, parsedName.className, parsedName.classDisplayName, parsedName.methodName, displayName = parsedName.methodName, localId = testName, ignored = isIgnored - )) + ) + ) } } @@ -122,11 +129,32 @@ internal class TCServiceMessagesClient( } } - private fun Node.failure( - message: TestFailed + private fun TestNode.failure( + message: TestFailed ) { hasFailures = true - results.failure(descriptor.id, NodeJsTestFailure(message.messageName, message.stacktrace)) + + val stacktrace = buildString { + append(message.stacktrace) + + if (settings.treatFailedTestOutputAsStacktrace) { + append(output) + output.setLength(0) + } + } + + results.failure(descriptor.id, KotlinTestFailure(message.failureMessage, stacktrace)) + } + + private fun TestNode.output( + destination: TestOutputEvent.Destination, + text: String + ) { + if (settings.treatFailedTestOutputAsStacktrace) { + output.append(text) + } + + results.output(descriptor.id, DefaultTestOutputEvent(destination, text)) } private inline fun NodeType.open(contents: (NodeType) -> Unit) = open(System.currentTimeMillis()) { @@ -154,7 +182,7 @@ internal class TCServiceMessagesClient( if (assertLocalId != null) { check(it.localId == assertLocalId) { "Bad TCSM: unexpected node to close: ${it.localId}, stack: ${ - collectParents().joinToString("") { item -> "\n - ${item.localId}" } + collectParents().joinToString("") { item -> "\n - ${item.localId}" } }\n" } } @@ -203,8 +231,8 @@ internal class TCServiceMessagesClient( * Node of tests tree */ abstract inner class Node( - var parent: Node? = null, - val localId: String + var parent: Node? = null, + val localId: String ) { val reportingParent: Node? get() = when { @@ -256,9 +284,9 @@ internal class TCServiceMessagesClient( inner class RootNode(val ownerBuildOperationId: Any) : Node(null, settings.rootNodeName) { override val descriptor = - object : DefaultTestSuiteDescriptor(settings.rootNodeName, localId) { - override fun getOwnerBuildOperationId(): Any? = this@RootNode.ownerBuildOperationId - } + object : DefaultTestSuiteDescriptor(settings.rootNodeName, localId) { + override fun getOwnerBuildOperationId(): Any? = this@RootNode.ownerBuildOperationId + } } inner class SuiteNode(parent: Node? = null, name: String) : Node(parent, name) { @@ -268,14 +296,16 @@ internal class TCServiceMessagesClient( } inner class TestNode( - parent: Node, - className: String, - classDisplayName: String, - methodName: String, - displayName: String, - localId: String, - ignored: Boolean = false + parent: Node, + className: String, + classDisplayName: String, + methodName: String, + displayName: String, + localId: String, + ignored: Boolean = false ) : Node(parent, localId) { + val output by lazy { StringBuilder() } + override val descriptor = object : DefaultTestDescriptor(id, className, methodName, classDisplayName, displayName) { override fun getParent(): TestDescriptorInternal? = this@TestNode.parent?.descriptor } @@ -303,8 +333,9 @@ internal class TCServiceMessagesClient( private fun requireLeaf() = leaf ?: error("test out of group") private fun requireLeafGroup() = requireLeaf().also { - check(it !is TestNode) { "previous test `$it` not finished"} + check(it !is TestNode) { "previous test `$it` not finished" } } + private fun requireLeafTest() = leaf as? TestNode - ?: error("no running test") + ?: error("no running test") } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesTestExecutor.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesTestExecutor.kt index 09a38da17b2..552d273d115 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesTestExecutor.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesTestExecutor.kt @@ -14,9 +14,10 @@ import java.io.PipedOutputStream import kotlin.concurrent.thread data class TCServiceMessagesTestExecutionSpec( - val forkOptions: ProcessForkOptions, - val args: List, - val clientSettings: TCServiceMessagesClientSettings + val forkOptions: ProcessForkOptions, + val args: List, + val checkExitCode: Boolean, + val clientSettings: TCServiceMessagesClientSettings ) : TestExecutionSpec private val log = LoggerFactory.getLogger("org.jetbrains.kotlin.gradle.tasks.testing") @@ -50,8 +51,8 @@ class TCServiceMessagesTestExecutor( ServiceMessage.parse(it, client) } catch (e: Exception) { log.error( - "Error while processing test process output message \"$it\"", - e + "Error while processing test process output message \"$it\"", + e ) } } @@ -73,7 +74,7 @@ class TCServiceMessagesTestExecutor( val result = execHandle!!.waitForFinish() outputReaderThread!!.join() - if (result.exitValue != 0) { + if (spec.checkExitCode && result.exitValue != 0) { error("$execHandle exited with errors (exit code: ${result.exitValue})") } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt index c026ca32899..eecac8032ac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt @@ -3,7 +3,7 @@ package org.jetbrains.kotlin.gradle.targets.js import org.gradle.api.Project import org.gradle.api.file.FileCollection import org.gradle.api.internal.plugins.DslObject -import org.gradle.api.plugins.JavaBasePlugin +import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.testing.base.plugins.TestingBasePlugin import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExtension @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsSetupTask import org.jetbrains.kotlin.gradle.targets.js.tasks.* import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinTestTask import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.utils.addIfNotNull import java.io.File @@ -48,14 +49,6 @@ internal class KotlinJsCompilationTestsConfigurator( private val nodeModulesDir get() = project.buildDir.resolve(disambiguateUnderscored("node_modules", true)) - @Suppress("UnstableApiUsage") - private val Project.testResults: File - get() = project.buildDir.resolve(TestingBasePlugin.TEST_RESULTS_DIR_NAME) - - @Suppress("UnstableApiUsage") - private val Project.testReports: File - get() = project.buildDir.resolve(TestingBasePlugin.TESTS_DIR_NAME) - private val compileTask: Kotlin2JsCompile get() = project.tasks.findByName(compileTestKotlin2Js.name) as Kotlin2JsCompile @@ -85,7 +78,7 @@ internal class KotlinJsCompilationTestsConfigurator( val projectWithNodeJsPlugin = NodeJsPlugin.ensureAppliedInHierarchy(target.project) val testTask = registerTask(project, testTaskName, KotlinNodeJsTestTask::class.java) { testJs -> - testJs.group = "verification" + testJs.group = LifecycleBasePlugin.VERIFICATION_GROUP testJs.dependsOn(nodeModulesTask.getTaskOrProvider()) @@ -106,16 +99,11 @@ internal class KotlinJsCompilationTestsConfigurator( ) testJs.nodeModulesToLoad = setOf(compileTestKotlin2Js.outputFile.name) - val htmlReport = DslObject(testJs.reports.html) - val xmlReport = DslObject(testJs.reports.junitXml) - - xmlReport.conventionMapping.map("destination") { project.testResults.resolve(testJs.name) } - htmlReport.conventionMapping.map("destination") { project.testReports.resolve(testJs.name) } - testJs.conventionMapping.map("binResultsDir") { project.testResults.resolve(testJs.name + "/binary") } + KotlinTestTask.configureConventions(testJs) } project.afterEvaluate { - project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(testTask.getTaskOrProvider()) + project.tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(testTask.getTaskOrProvider()) // defer nodeJs executable setup, as nodejs project settings may change during configuration testTask.configure { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NodeJsTestFailure.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NodeJsTestFailure.kt deleted file mode 100644 index d71da37be43..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NodeJsTestFailure.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.jetbrains.kotlin.gradle.targets.js - -/** - * Class to be shown in default Gradle tests console reporter. - * - * Example console output: - * ``` - * clientTest.CommonTest.test1 FAILED - * org.jetbrains.kotlin.gradle.targets.js.NodeJsTestFailure - * ``` - */ -class NodeJsTestFailure(message: String, val stackTrace: String?) : Throwable(message) { - override fun fillInStackTrace(): Throwable = this - override fun toString(): String = stackTrace ?: "" -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt index 694ca57c05a..dd88ff16eb1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt @@ -5,38 +5,24 @@ package org.jetbrains.kotlin.gradle.targets.js.tasks -import org.gradle.api.internal.file.FileResolver -import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter -import org.gradle.api.tasks.* -import org.gradle.api.tasks.testing.AbstractTestTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.process.ProcessForkOptions import org.gradle.process.internal.DefaultProcessForkOptions -import org.gradle.process.internal.ExecHandleFactory import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec -import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutor +import org.jetbrains.kotlin.gradle.tasks.KotlinTestTask import org.jetbrains.kotlin.gradle.testing.IgnoredTestSuites import org.jetbrains.kotlin.gradle.testing.TestsGrouping -import org.jetbrains.kotlin.gradle.utils.injected import java.io.File -import javax.inject.Inject -open class KotlinNodeJsTestTask : AbstractTestTask() { +open class KotlinNodeJsTestTask : KotlinTestTask() { @Input var ignoredTestSuites: IgnoredTestSuites = IgnoredTestSuites.showWithContents - @Input - var testsGrouping: TestsGrouping = - TestsGrouping.root - - @Input - @Optional - var targetName: String? = null - - @Input - var excludes = mutableSetOf() - @InputDirectory lateinit var nodeModulesDir: File @@ -47,25 +33,15 @@ open class KotlinNodeJsTestTask : AbstractTestTask() { @Input lateinit var testRuntimeNodeModules: Collection - @Suppress("UnstableApiUsage") - private val filterExt: DefaultTestFilter - get() = filter as DefaultTestFilter - - init { - filterExt.isFailOnNoMatchingTests = false - } - - @get:Inject - open val fileResolver: FileResolver - get() = injected - @Suppress("LeakingThis") @Internal val nodeJsProcessOptions: ProcessForkOptions = DefaultProcessForkOptions(fileResolver) + @Suppress("unused") val nodeJsExecutable: String @Input get() = nodeJsProcessOptions.executable + @Suppress("unused") val nodeJsWorkingDirCanonicalPath: String @Input get() = nodeJsProcessOptions.workingDir.canonicalPath @@ -108,23 +84,15 @@ open class KotlinNodeJsTestTask : AbstractTestTask() { return TCServiceMessagesTestExecutionSpec( extendedForkOptions, nodeJsArgs + - testRuntimeNodeModules - .map { nodeModulesDir.resolve(it) } - .filter { it.exists() } - .map { it.absolutePath } + - cliArgs.toList(), + testRuntimeNodeModules + .map { nodeModulesDir.resolve(it) } + .filter { it.exists() } + .map { it.absolutePath } + + cliArgs.toList(), + true, clientSettings ) } - - @get:Inject - open val execHandleFactory: ExecHandleFactory - get() = injected - - override fun createTestExecuter() = TCServiceMessagesTestExecutor( - execHandleFactory, - buildOperationExecutor - ) } data class KotlinNodeJsTestRunnerCliArgs( diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTaretConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTaretConfigurator.kt index 49b86e548a0..34e3e8ccf2d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTaretConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTaretConfigurator.kt @@ -14,18 +14,19 @@ import org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE import org.gradle.api.internal.artifacts.ArtifactAttributes import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet +import org.gradle.api.internal.plugins.DslObject import org.gradle.api.plugins.BasePlugin +import org.gradle.api.reporting.ReportingExtension import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Exec import org.gradle.language.base.plugins.LifecycleBasePlugin +import org.gradle.testing.base.plugins.TestingBasePlugin import org.jetbrains.kotlin.gradle.dsl.KotlinNativeBinaryContainer import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile -import org.jetbrains.kotlin.gradle.tasks.CInteropProcess -import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile -import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTestTask +import org.jetbrains.kotlin.gradle.tasks.* import java.io.File import java.util.* @@ -127,27 +128,37 @@ open class KotlinNativeTargetConfigurator( private fun Project.createRunTask(binary: Executable) { val taskName = binary.runTaskName ?: return - // TODO provide a special exec task for tests. - tasks.create(taskName, Exec::class.java).apply { - if (binary.isDefaultTestExecutable) { + + if (binary.isDefaultTestExecutable) { + tasks.create(taskName, KotlinNativeTestTask::class.java).apply { + tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(this) + group = LifecycleBasePlugin.VERIFICATION_GROUP description = "Executes Kotlin/Native unit tests for target ${binary.target.name}." - tasks.maybeCreate(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(this) - if (project.hasProperty("teamcity.version")) { - args("--ktest_logger=TEAMCITY") - } - } else { + + enabled = binary.target.konanTarget.isCurrentHost + + executable = binary.outputFile + workingDir = project.projectDir.absolutePath + + onlyIf { binary.outputFile.exists() } + dependsOn(binary.linkTaskName) + + KotlinTestTask.configureConventions(this) + } + } else { + tasks.create(taskName, Exec::class.java).apply { group = RUN_GROUP description = "Executes Kotlin/Native executable ${binary.name} for target ${binary.target.name}" + + enabled = binary.target.konanTarget.isCurrentHost + + executable = binary.outputFile.absolutePath + workingDir = project.projectDir + + onlyIf { binary.outputFile.exists() } + dependsOn(binary.linkTaskName) } - - enabled = binary.target.konanTarget.isCurrentHost - - executable = binary.outputFile.absolutePath - workingDir = project.projectDir - - onlyIf { binary.outputFile.exists() } - dependsOn(binary.linkTaskName) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt new file mode 100644 index 00000000000..a22f47ae869 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTestTask.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.targets.native.tasks + +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.process.ProcessForkOptions +import org.gradle.process.internal.DefaultProcessForkOptions +import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings +import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec +import org.jetbrains.kotlin.gradle.tasks.KotlinTestTask +import org.jetbrains.kotlin.gradle.testing.TestsGrouping +import java.io.File + +open class KotlinNativeTestTask : KotlinTestTask() { + @Suppress("LeakingThis") + @Internal + val processOptions: ProcessForkOptions = DefaultProcessForkOptions(fileResolver) + + var executable: File + @InputFile + @SkipWhenEmpty + get() = File(processOptions.executable) + set(value) { + processOptions.executable = value.absolutePath + } + + var workingDir: String + @Input get() = processOptions.workingDir.canonicalPath + set(value) { + processOptions.workingDir = File(value) + } + + @Suppress("unused") + fun processOptions(options: ProcessForkOptions.() -> Unit) { + options(processOptions) + } + + override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec { + val extendedForkOptions = DefaultProcessForkOptions(fileResolver) + processOptions.copyTo(extendedForkOptions) + + val clientSettings = when (testsGrouping) { + TestsGrouping.none -> TCServiceMessagesClientSettings(rootNodeName = name) + TestsGrouping.root -> TCServiceMessagesClientSettings(rootNodeName = name, nameOfRootSuiteToAppend = targetName) + TestsGrouping.leaf -> TCServiceMessagesClientSettings(rootNodeName = name, nameOfLeafTestToAppend = targetName) + }.copy(treatFailedTestOutputAsStacktrace = true) + + val cliArgs = CliArgs("TEAMCITY", includePatterns, excludePatterns) + + return TCServiceMessagesTestExecutionSpec( + extendedForkOptions, + cliArgs.toList(), + false, + clientSettings + ) + } + + private class CliArgs( + val testLogger: String? = null, + val testGradleFilter: Set = setOf(), + val testNegativeGradleFilter: Set = setOf() + ) { + fun toList() = mutableListOf().also { + if (testLogger != null) { + it.add("--ktest_logger=$testLogger") + } + + if (testGradleFilter.isNotEmpty()) { + it.add("--ktest_gradle_filter=${testGradleFilter.joinToString(",")}") + } + + if (testNegativeGradleFilter.isNotEmpty()) { + it.add("--ktest_negative_gradle_filter=${testNegativeGradleFilter.joinToString(",")}") + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinTestTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinTestTask.kt new file mode 100644 index 00000000000..45312dbd2a8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinTestTask.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.tasks + +import org.gradle.api.Project +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.internal.plugins.DslObject +import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter +import org.gradle.api.reporting.ReportingExtension +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.testing.AbstractTestTask +import org.gradle.process.internal.ExecHandleFactory +import org.gradle.testing.base.plugins.TestingBasePlugin +import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutor +import org.jetbrains.kotlin.gradle.testing.TestsGrouping +import org.jetbrains.kotlin.gradle.utils.injected +import java.io.File +import javax.inject.Inject + +abstract class KotlinTestTask : AbstractTestTask() { + @Input + var testsGrouping: TestsGrouping = + TestsGrouping.root + + @Input + @Optional + var targetName: String? = null + + @Input + var excludes = mutableSetOf() + + @Suppress("UnstableApiUsage") + protected val filterExt: DefaultTestFilter + @Internal get() = filter as DefaultTestFilter + + init { + filterExt.isFailOnNoMatchingTests = false + } + + protected val includePatterns: Set + @Internal get() = filterExt.includePatterns + filterExt.commandLineIncludePatterns + + protected val excludePatterns: Set + @Internal get() = excludes + + @get:Inject + open val fileResolver: FileResolver + get() = injected + + @get:Inject + open val execHandleFactory: ExecHandleFactory + get() = injected + + override fun createTestExecuter() = TCServiceMessagesTestExecutor( + execHandleFactory, + buildOperationExecutor + ) + + companion object { + @Suppress("UnstableApiUsage") + private val Project.testResults: File + get() = project.buildDir.resolve(TestingBasePlugin.TEST_RESULTS_DIR_NAME) + + private val Project.reportsDir: File + get() = project.extensions.getByType(ReportingExtension::class.java).baseDir + + @Suppress("UnstableApiUsage") + private val Project.testReports: File + get() = reportsDir.resolve(TestingBasePlugin.TESTS_DIR_NAME) + + internal fun configureConventions(task: KotlinTestTask) { + val htmlReport = DslObject(task.reports.html) + val xmlReport = DslObject(task.reports.junitXml) + + xmlReport.conventionMapping.map("destination") { task.project.testResults.resolve(task.name) } + htmlReport.conventionMapping.map("destination") { task.project.testReports.resolve(task.name) } + task.conventionMapping.map("binResultsDir") { task.project.testResults.resolve(task.name + "/binary") } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt new file mode 100644 index 00000000000..bfe09d6cdf0 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/KotlinTestFailure.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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 + +/** + * Class to be shown in default Gradle tests console reporter. + * + * Example console output: + * ``` + * clientTest.CommonTest.test1 FAILED + * org.jetbrains.kotlin.gradle.testing.KotlinTestFailure + * ``` + */ +class KotlinTestFailure(message: String?, private val stackTrace: String?) : Throwable(message) { + override fun fillInStackTrace(): Throwable = this + override fun toString(): String = + if (stackTrace != null) { + if (message != null && message !in stackTrace) message + "\n" + stackTrace + else stackTrace + } else message ?: "Test failed" +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/RecordingTestResultProcessor.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/RecordingTestResultProcessor.kt index 3914023fed8..39358595831 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/RecordingTestResultProcessor.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/RecordingTestResultProcessor.kt @@ -30,7 +30,7 @@ class RecordingTestResultProcessor : TestResultProcessor { } override fun failure(testId: Any, result: Throwable) { - line("FAILURE ${result.message} // $testId") + line("FAILURE $result // $testId") } override fun completed(testId: Any, event: TestCompleteEvent) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/Complex.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/Complex.kt index 703117bec18..80072909051 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/Complex.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/Complex.kt @@ -4,19 +4,20 @@ import jetbrains.buildServer.messages.serviceMessages.* import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClient import org.junit.Test -class Complex: TCServiceMessagesClientTest() { +class Complex : TCServiceMessagesClientTest() { @Test fun testComplexJs() { nameOfRootSuiteToReplace = "js" - assertEvents(""" + assertEvents( + """ STARTED SUITE root // root STARTED SUITE js // root.js STARTED SUITE MyTest // root.js.MyTest STARTED TEST displayName: myTest1, classDisplayName: MyTest, className: my.company.product.MyTest, name: myTest1 // root.js.MyTest.my.company.product.MyTest.myTest1 COMPLETED SUCCESS // root.js.MyTest.my.company.product.MyTest.myTest1 STARTED TEST displayName: myTest2, classDisplayName: MyTest, className: my.company.product.MyTest, name: myTest2 // root.js.MyTest.my.company.product.MyTest.myTest2 - FAILURE testFailed // root.js.MyTest.my.company.product.MyTest.myTest2 + FAILURE null // root.js.MyTest.my.company.product.MyTest.myTest2 COMPLETED FAILURE // root.js.MyTest.my.company.product.MyTest.myTest2 STARTED TEST displayName: myTest3, classDisplayName: MyTest, className: my.company.product.MyTest, name: myTest3 // root.js.MyTest.my.company.product.MyTest.myTest3 COMPLETED SKIPPED // root.js.MyTest.my.company.product.MyTest.myTest3 @@ -24,7 +25,7 @@ STARTED SUITE root // root STARTED TEST displayName: myTest4, classDisplayName: MyTestNested, className: my.company.product.MyTest.MyTestNested, name: myTest4 // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest4 COMPLETED SUCCESS // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest4 STARTED TEST displayName: myTest5, classDisplayName: MyTestNested, className: my.company.product.MyTest.MyTestNested, name: myTest5 // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 - FAILURE testFailed // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 + FAILURE null // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 COMPLETED FAILURE // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 STARTED TEST displayName: myTest6, classDisplayName: MyTestNested, className: my.company.product.MyTest.MyTestNested, name: myTest6 // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest6 COMPLETED SKIPPED // root.js.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest6 @@ -32,7 +33,7 @@ STARTED SUITE root // root STARTED TEST displayName: myTest7, classDisplayName: MyTestNestedNested, className: my.company.product.MyTest.MyTestNestedNested, name: myTest7 // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest7 COMPLETED SUCCESS // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest7 STARTED TEST displayName: myTest8, classDisplayName: MyTestNestedNested, className: my.company.product.MyTest.MyTestNestedNested, name: myTest8 // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 - FAILURE testFailed // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 + FAILURE null // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 COMPLETED FAILURE // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 STARTED TEST displayName: myTest9, classDisplayName: MyTestNestedNested, className: my.company.product.MyTest.MyTestNestedNested, name: myTest9 // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest9 COMPLETED SKIPPED // root.js.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest9 @@ -43,7 +44,7 @@ STARTED SUITE root // root STARTED TEST displayName: myTest10, classDisplayName: MyTest2, className: my.company.product.MyTest2, name: myTest10 // root.js.MyTest2.my.company.product.MyTest2.myTest10 COMPLETED SUCCESS // root.js.MyTest2.my.company.product.MyTest2.myTest10 STARTED TEST displayName: myTest11, classDisplayName: MyTest2, className: my.company.product.MyTest2, name: myTest11 // root.js.MyTest2.my.company.product.MyTest2.myTest11 - FAILURE testFailed // root.js.MyTest2.my.company.product.MyTest2.myTest11 + FAILURE null // root.js.MyTest2.my.company.product.MyTest2.myTest11 COMPLETED FAILURE // root.js.MyTest2.my.company.product.MyTest2.myTest11 STARTED TEST displayName: myTest12, classDisplayName: MyTest2, className: my.company.product.MyTest2, name: myTest12 // root.js.MyTest2.my.company.product.MyTest2.myTest12 COMPLETED SKIPPED // root.js.MyTest2.my.company.product.MyTest2.myTest12 @@ -66,7 +67,8 @@ STARTED SUITE root // root COMPLETED FAILURE // root.js.MyTest2 COMPLETED FAILURE // root.js COMPLETED FAILURE // root - """) { + """ + ) { serviceMessage(TestSuiteStarted("")) complexFixture() serviceMessage(TestSuiteFinished("")) @@ -77,14 +79,15 @@ COMPLETED FAILURE // root fun testComplexIos() { nameOfRootSuiteToAppend = "ios" - assertEvents(""" + assertEvents( + """ STARTED SUITE root // root STARTED SUITE ios // root.ios STARTED SUITE MyTest // root.ios.MyTest STARTED TEST displayName: myTest1, classDisplayName: MyTest, className: my.company.product.MyTest, name: myTest1 // root.ios.MyTest.my.company.product.MyTest.myTest1 COMPLETED SUCCESS // root.ios.MyTest.my.company.product.MyTest.myTest1 STARTED TEST displayName: myTest2, classDisplayName: MyTest, className: my.company.product.MyTest, name: myTest2 // root.ios.MyTest.my.company.product.MyTest.myTest2 - FAILURE testFailed // root.ios.MyTest.my.company.product.MyTest.myTest2 + FAILURE null // root.ios.MyTest.my.company.product.MyTest.myTest2 COMPLETED FAILURE // root.ios.MyTest.my.company.product.MyTest.myTest2 STARTED TEST displayName: myTest3, classDisplayName: MyTest, className: my.company.product.MyTest, name: myTest3 // root.ios.MyTest.my.company.product.MyTest.myTest3 COMPLETED SKIPPED // root.ios.MyTest.my.company.product.MyTest.myTest3 @@ -92,7 +95,7 @@ STARTED SUITE root // root STARTED TEST displayName: myTest4, classDisplayName: MyTestNested, className: my.company.product.MyTest.MyTestNested, name: myTest4 // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest4 COMPLETED SUCCESS // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest4 STARTED TEST displayName: myTest5, classDisplayName: MyTestNested, className: my.company.product.MyTest.MyTestNested, name: myTest5 // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 - FAILURE testFailed // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 + FAILURE null // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 COMPLETED FAILURE // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest5 STARTED TEST displayName: myTest6, classDisplayName: MyTestNested, className: my.company.product.MyTest.MyTestNested, name: myTest6 // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest6 COMPLETED SKIPPED // root.ios.MyTest.MyTestNested.my.company.product.MyTest.MyTestNested.myTest6 @@ -100,7 +103,7 @@ STARTED SUITE root // root STARTED TEST displayName: myTest7, classDisplayName: MyTestNestedNested, className: my.company.product.MyTest.MyTestNestedNested, name: myTest7 // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest7 COMPLETED SUCCESS // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest7 STARTED TEST displayName: myTest8, classDisplayName: MyTestNestedNested, className: my.company.product.MyTest.MyTestNestedNested, name: myTest8 // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 - FAILURE testFailed // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 + FAILURE null // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 COMPLETED FAILURE // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest8 STARTED TEST displayName: myTest9, classDisplayName: MyTestNestedNested, className: my.company.product.MyTest.MyTestNestedNested, name: myTest9 // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest9 COMPLETED SKIPPED // root.ios.MyTest.MyTestNested.MyTestNestedNested.my.company.product.MyTest.MyTestNestedNested.myTest9 @@ -111,7 +114,7 @@ STARTED SUITE root // root STARTED TEST displayName: myTest10, classDisplayName: MyTest2, className: my.company.product.MyTest2, name: myTest10 // root.ios.MyTest2.my.company.product.MyTest2.myTest10 COMPLETED SUCCESS // root.ios.MyTest2.my.company.product.MyTest2.myTest10 STARTED TEST displayName: myTest11, classDisplayName: MyTest2, className: my.company.product.MyTest2, name: myTest11 // root.ios.MyTest2.my.company.product.MyTest2.myTest11 - FAILURE testFailed // root.ios.MyTest2.my.company.product.MyTest2.myTest11 + FAILURE null // root.ios.MyTest2.my.company.product.MyTest2.myTest11 COMPLETED FAILURE // root.ios.MyTest2.my.company.product.MyTest2.myTest11 STARTED TEST displayName: myTest12, classDisplayName: MyTest2, className: my.company.product.MyTest2, name: myTest12 // root.ios.MyTest2.my.company.product.MyTest2.myTest12 COMPLETED SKIPPED // root.ios.MyTest2.my.company.product.MyTest2.myTest12 @@ -134,7 +137,8 @@ STARTED SUITE root // root COMPLETED FAILURE // root.ios.MyTest2 COMPLETED FAILURE // root.ios COMPLETED FAILURE // root - """) { + """ + ) { complexFixture() } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt index 1c995b71401..e61b2a765d6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt @@ -1,5 +1,6 @@ package org.jetbrains.kotlin.gradle.internal.testing.tcsmc +import jetbrains.buildServer.messages.serviceMessages.ServiceMessage import org.gradle.internal.operations.OperationIdentifier import org.jetbrains.kotlin.gradle.internal.testing.RecordingTestResultProcessor import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClient @@ -13,6 +14,7 @@ open class TCServiceMessagesClientTest { protected var nameOfRootSuiteToReplace: String? = null protected var nameOfLeafTestToAppend: String? = null protected var skipRoots: Boolean = false + protected var treatFailedTestOutputAsStacktrace: Boolean = false internal fun assertEvents(assertion: String, produceServiceMessage: TCServiceMessagesClient.() -> Unit) { val results = RecordingTestResultProcessor() @@ -36,9 +38,13 @@ open class TCServiceMessagesClientTest { nameOfRootSuiteToAppend, nameOfRootSuiteToReplace, nameOfLeafTestToAppend, - skipRoots + skipRoots, + treatFailedTestOutputAsStacktrace ), LoggerFactory.getLogger("test") ) } + + internal fun TCServiceMessagesClient.serviceMessage(name: String, attributes: Map) = + serviceMessage(ServiceMessage.parse(ServiceMessage.asString(name, attributes))!!) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestFailureTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestFailureTest.kt new file mode 100644 index 00000000000..822d8c6028d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestFailureTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.internal.testing.tcsmc + +import jetbrains.buildServer.messages.serviceMessages.TestFinished +import jetbrains.buildServer.messages.serviceMessages.TestStarted +import jetbrains.buildServer.messages.serviceMessages.TestSuiteFinished +import jetbrains.buildServer.messages.serviceMessages.TestSuiteStarted +import org.junit.Test + +class TestFailureTest : TCServiceMessagesClientTest() { + @Test + fun testJs() { + assertEvents( + """ +STARTED SUITE root // root + STARTED SUITE // root. + STARTED TEST displayName: Test, classDisplayName: , className: , name: Test // root..Test + FAILURE AssertionError: Expected value to be true. + at AssertionError_init_0 (mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt:102:37) + at DefaultJsAsserter.failWithMessage_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:80:19) + at DefaultJsAsserter.assertTrue_o10pc4${'$'} (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:60:13) + at DefaultJsAsserter.assertTrue_4mavae${'$'} (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:67:9) + at assertTrue_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt:36:21) + at SampleTestsJS.testHello (mpplib2/src/jsTest/kotlin/sample/SampleTestsJS.kt:9:9) + at mpplib2/build/js_test_node_modules/mpplib2_test.js:59:38 + at Object.fn [as test] (mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts:12:25) + at Object.test (mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts:80:28) + at test (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt:57:15) // root..Test + COMPLETED FAILURE // root..Test + COMPLETED FAILURE // root. +COMPLETED FAILURE // root + """ + ) { + serviceMessage(TestSuiteStarted("")) + serviceMessage(TestStarted("Test", false, null)) + serviceMessage( + "testFailed", + mapOf( + "name" to "Test", + "message" to "Expected value to be true", + "details" to """AssertionError: Expected value to be true. + at AssertionError_init_0 (mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt:102:37) + at DefaultJsAsserter.failWithMessage_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:80:19) + at DefaultJsAsserter.assertTrue_o10pc4$ (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:60:13) + at DefaultJsAsserter.assertTrue_4mavae$ (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:67:9) + at assertTrue_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt:36:21) + at SampleTestsJS.testHello (mpplib2/src/jsTest/kotlin/sample/SampleTestsJS.kt:9:9) + at mpplib2/build/js_test_node_modules/mpplib2_test.js:59:38 + at Object.fn [as test] (mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts:12:25) + at Object.test (mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts:80:28) + at test (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt:57:15)""" + ) + ) + serviceMessage(TestFinished("Test", 0)) + serviceMessage(TestSuiteFinished("")) + } + } + + @Test + fun testNative() { + treatFailedTestOutputAsStacktrace = true + + assertEvents( + """ +STARTED SUITE root // root + STARTED SUITE // root. + STARTED TEST displayName: Test, classDisplayName: , className: , name: Test // root..Test + StdOut[AssertionError: Expected value to be true. + at AssertionError_init_0 (mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt:102:37) + at DefaultJsAsserter.failWithMessage_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:80:19) + at DefaultJsAsserter.assertTrue_o10pc4${'$'} (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:60:13) + at DefaultJsAsserter.assertTrue_4mavae${'$'} (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:67:9) + at assertTrue_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt:36:21) + at SampleTestsJS.testHello (mpplib2/src/jsTest/kotlin/sample/SampleTestsJS.kt:9:9) + at mpplib2/build/js_test_node_modules/mpplib2_test.js:59:38 + at Object.fn [as test] (mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts:12:25) + at Object.test (mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts:80:28) + at test (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt:57:15) +] // root..Test + FAILURE Expected <7>, actual <42> +nullAssertionError: Expected value to be true. + at AssertionError_init_0 (mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt:102:37) + at DefaultJsAsserter.failWithMessage_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:80:19) + at DefaultJsAsserter.assertTrue_o10pc4${'$'} (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:60:13) + at DefaultJsAsserter.assertTrue_4mavae${'$'} (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:67:9) + at assertTrue_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt:36:21) + at SampleTestsJS.testHello (mpplib2/src/jsTest/kotlin/sample/SampleTestsJS.kt:9:9) + at mpplib2/build/js_test_node_modules/mpplib2_test.js:59:38 + at Object.fn [as test] (mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts:12:25) + at Object.test (mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts:80:28) + at test (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt:57:15) + // root..Test + COMPLETED FAILURE // root..Test + COMPLETED FAILURE // root. +COMPLETED FAILURE // root + """ + ) { + serviceMessage(TestSuiteStarted("")) + serviceMessage(TestStarted("Test", false, null)) + regularText( + """AssertionError: Expected value to be true. + at AssertionError_init_0 (mpplib2/build/tmp/expandedArchives/kotlin-stdlib-js-1.3-SNAPSHOT.jar_730a1b227513cf16a9b639e009a985fc/kotlin/exceptions.kt:102:37) + at DefaultJsAsserter.failWithMessage_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:80:19) + at DefaultJsAsserter.assertTrue_o10pc4$ (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:60:13) + at DefaultJsAsserter.assertTrue_4mavae$ (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/DefaultJsAsserter.kt:67:9) + at assertTrue_0 (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/Assertions.kt:36:21) + at SampleTestsJS.testHello (mpplib2/src/jsTest/kotlin/sample/SampleTestsJS.kt:9:9) + at mpplib2/build/js_test_node_modules/mpplib2_test.js:59:38 + at Object.fn [as test] (mpplib2/build/tmp/expandedArchives/src/KotlinTestRunner.ts:12:25) + at Object.test (mpplib2/build/tmp/expandedArchives/src/KotlinTestTeamCityReporter.ts:80:28) + at test (mpplib2/build/tmp/expandedArchives/kotlin-test-js-1.3-SNAPSHOT.jar_d60f1e6d0dd94843a03bf98a569bbb73/src/main/kotlin/kotlin/test/TestApi.kt:57:15)""" + ) + serviceMessage( + "testFailed", + mapOf( + "name" to "Test", + "message" to "Expected <7>, actual <42>" + ) + ) + serviceMessage(TestFinished("Test", 0)) + serviceMessage(TestSuiteFinished("")) + } + } +} \ No newline at end of file