diff --git a/.gitignore b/.gitignore
index badd5c65115..117f6dccdfb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,3 +37,6 @@ build/
.idea/.name
.idea/artifacts/dist_auto_*
kotlin-ultimate/
+node_modules/
+.rpt2_cache/
+libraries/tools/kotlin-test-nodejs-runner/lib/
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts
index d9ac9c0b303..4c601a111fe 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts
@@ -23,6 +23,7 @@ dependencies {
testCompile(project(":kotlin-test:kotlin-test-jvm"))
testCompile(projectRuntimeJar(":kotlin-compiler-embeddable"))
+ testCompile(intellijCoreDep()) { includeJars("jdom") }
// testCompileOnly dependency on non-shaded artifacts is needed for IDE support
// testRuntime on shaded artifact is needed for running tests with shaded compiler
testCompileOnly(project(path = ":kotlin-gradle-plugin-test-utils-embeddable", configuration = "compile"))
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 bf424e676e8..e3b1e1913bb 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
@@ -1,9 +1,13 @@
package org.jetbrains.kotlin.gradle
+import com.intellij.testFramework.TestDataFile
import org.gradle.api.logging.LogLevel
import org.gradle.tooling.GradleConnector
import org.gradle.util.GradleVersion
-import org.gradle.util.VersionNumber
+import org.jdom.Element
+import org.jdom.input.SAXBuilder
+import org.jdom.output.Format
+import org.jdom.output.XMLOutputter
import org.jetbrains.kotlin.gradle.model.ModelContainer
import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction
import org.jetbrains.kotlin.gradle.util.*
@@ -495,6 +499,23 @@ abstract class BaseGradleIT {
}
}
+ fun CompiledProject.assertTasksRegistered(vararg tasks: String) {
+ for (task in tasks) {
+ assertContains("'Register task $task'")
+ }
+ }
+
+ fun CompiledProject.assertTasksNotRealized(vararg tasks: String) {
+ for (task in tasks) {
+ assertNotContains("'Realize task $task'")
+ }
+ }
+
+ fun CompiledProject.assertTasksRegisteredAndNotRealized(vararg tasks: String) {
+ assertTasksRegistered(*tasks)
+ assertTasksNotRealized(*tasks)
+ }
+
fun CompiledProject.getOutputForTask(taskName: String): String {
val taskOutputRegex = ("(?:\\[LIFECYCLE] \\[class org\\.gradle(?:\\.internal\\.buildevents)?\\.TaskExecutionLogger] :$taskName|" +
"\\[org\\.gradle\\.execution\\.plan\\.DefaultPlanExecutor\\] :$taskName.*?started)" +
@@ -575,6 +596,71 @@ abstract class BaseGradleIT {
File(projectDir, it).takeIf(File::exists)
}.single()
+ /**
+ * @param assertionFileName path to xml with expected test results, relative to test resources root
+ */
+ fun CompiledProject.assertTestResults(
+ @TestDataFile assertionFileName: String,
+ testReportName: String
+ ) {
+ val projectDir = project.projectDir
+ val testReportDir = projectDir.resolve("build/test-results/$testReportName")
+
+ if (!testReportDir.isDirectory) {
+ error("Test report dir was not created")
+ }
+
+ val actualTestResults = readAndCleanupTestResults(testReportDir, projectDir)
+ val expectedTestResults = resourcesRootFile.resolve(assertionFileName).readText()
+
+ assertEquals(
+ prettyPrintXml(expectedTestResults),
+ prettyPrintXml(actualTestResults)
+ )
+ }
+
+ private fun readAndCleanupTestResults(testReportDir: File, projectDir: File): String {
+ val files = testReportDir
+ .listFiles()
+ .filter { it.isFile && it.name.endsWith(".xml") }
+ .sortedBy {
+ // let containing test suite be first
+ it.name.replace(".xml", ".A.xml")
+ }
+
+ val xmlString = "\n\n" +
+ files.joinToString("") {
+ it.readText()
+ .replace(projectDir.absolutePath, "/\$PROJECT_DIR$")
+ .replace(projectDir.name, "\$PROJECT_NAME$")
+ .replace("\n", "")
+ } +
+ ""
+
+ val doc = SAXBuilder().build(xmlString.reader())
+ val skipAttrs = setOf("timestamp", "hostname", "time", "message")
+ val skipContentsOf = setOf("failure")
+
+ fun cleanup(e: Element) {
+ if (e.name in skipContentsOf) e.text = "..."
+ e.attributes.forEach {
+ if (it.name in skipAttrs) {
+ it.value = "..."
+ }
+ }
+
+ e.children.forEach {
+ cleanup(it)
+ }
+ }
+
+ cleanup(doc.rootElement)
+ return XMLOutputter(Format.getPrettyFormat()).outputString(doc)
+ }
+
+ private fun prettyPrintXml(uglyXml: String): String =
+ XMLOutputter(Format.getPrettyFormat()).outputString(SAXBuilder().build(uglyXml.reader()))
+
private fun Project.createGradleTailParameters(options: BuildOptions, params: Array = arrayOf()): List =
params.toMutableList().apply {
add("--stacktrace")
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt
index dd0d55fef9b..ae00024774f 100644
--- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MultiplatformGradleIT.kt
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.gradle
+import com.intellij.testFramework.TestDataPath
import org.jetbrains.kotlin.gradle.plugin.EXPECTED_BY_CONFIG_NAME
import org.jetbrains.kotlin.gradle.plugin.IMPLEMENT_CONFIG_NAME
import org.jetbrains.kotlin.gradle.plugin.IMPLEMENT_DEPRECATION_WARNING
@@ -25,6 +26,7 @@ import org.junit.Test
import java.io.File
import kotlin.test.assertTrue
+@TestDataPath("\$CONTENT_ROOT/resources")
class MultiplatformGradleIT : BaseGradleIT() {
@Test
@@ -293,4 +295,81 @@ class MultiplatformGradleIT : BaseGradleIT() {
assertTasksExecuted(customSourceSetCompileTasks)
}
}
+
+ @Test
+ fun testMppNodeJsTestRun() = with(Project("new-mpp-js-tests", GradleVersionRequired.AtLeast("4.10.2"))) {
+ setupWorkingDir()
+ gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl)
+ gradleSettingsScript().modify(::transformBuildScriptWithPluginsDsl)
+
+ // just build without running tests to check configuration avoidance
+ build("assemble") {
+ assertSuccessful()
+
+ assertTasksRegisteredAndNotRealized(
+ ":clientKotlinJsNodeModules",
+ ":clientKotlinJsNodeModulesTestRuntime",
+ ":clientTest",
+
+ ":serverKotlinJsNodeModules",
+ ":serverKotlinJsNodeModulesTestRuntime",
+ ":serverTest"
+ )
+ }
+
+ build("check") {
+ assertSuccessful()
+
+ assertTasksExecuted(
+ ":clientKotlinJsNodeModules",
+ ":clientKotlinJsNodeModulesTestRuntime",
+ ":clientTest",
+
+ ":serverKotlinJsNodeModules",
+ ":serverKotlinJsNodeModulesTestRuntime",
+ ":serverTest"
+ )
+
+ assertTestResults("testProject/new-mpp-js-tests/TEST-CommonTest.xml", "clientTest")
+ assertTestResults("testProject/new-mpp-js-tests/TEST-CommonTest.xml", "serverTest")
+ }
+
+ // test all is up-to-date when no changes
+ build("check") {
+ assertSuccessful()
+
+ assertTasksUpToDate(
+ ":clientKotlinJsNodeModules",
+ ":clientKotlinJsNodeModulesTestRuntime",
+ ":clientTest",
+
+ ":serverKotlinJsNodeModules",
+ ":serverKotlinJsNodeModulesTestRuntime",
+ ":serverTest"
+ )
+ }
+
+ // change common file and check that all tasks executed
+ projectDir.resolve("src/commonMain/kotlin/common.kt").writeText("fun common() = 777")
+
+ build("check") {
+ assertSuccessful()
+
+ assertTasksUpToDate(
+ ":clientKotlinJsNodeModulesTestRuntime",
+ ":serverKotlinJsNodeModulesTestRuntime"
+ )
+
+ assertTasksExecuted(
+ ":clientKotlinJsNodeModules",
+ ":clientTest",
+
+ ":serverKotlinJsNodeModules",
+ ":serverTest"
+ )
+
+ assertTestResults("testProject/new-mpp-js-tests/TEST-CommonTest-2.xml", "clientTest")
+ assertTestResults("testProject/new-mpp-js-tests/TEST-CommonTest-2.xml", "serverTest")
+ }
+ }
}
\ No newline at end of file
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
new file mode 100644
index 00000000000..b70866724b4
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest-2.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ ...
+
+
+
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 00000000000..1e5a796318d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/TEST-CommonTest.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/build.gradle.kts
new file mode 100644
index 00000000000..cc0819b599c
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/build.gradle.kts
@@ -0,0 +1,42 @@
+import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestTask
+
+plugins {
+ kotlin("multiplatform").version("")
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ jcenter()
+}
+
+kotlin {
+ dependencies {
+ commonMainImplementation(kotlin("stdlib-common"))
+ commonTestApi(kotlin("test-common"))
+ }
+
+ val jsCommon = js("jsCommon") {
+ dependencies {
+ commonMainImplementation(kotlin("stdlib-js"))
+ commonTestApi(kotlin("test-js"))
+ }
+ }
+
+ js("server")
+ js("client")
+}
+
+tasks {
+ "jsCommonTest"(KotlinNodeJsTestTask::class) {
+ ignoreFailures = true
+ }
+
+ "clientTest"(KotlinNodeJsTestTask::class) {
+ ignoreFailures = true
+ }
+
+ "serverTest"(KotlinNodeJsTestTask::class) {
+ ignoreFailures = true
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/settings.gradle.kts
new file mode 100644
index 00000000000..93b60c43717
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/settings.gradle.kts
@@ -0,0 +1,8 @@
+pluginManagement {
+ repositories {
+ mavenLocal()
+ gradlePluginPortal()
+ }
+}
+
+rootProject.name = "new-mpp-js-tests"
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/src/commonMain/kotlin/common.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/src/commonMain/kotlin/common.kt
new file mode 100644
index 00000000000..57ebd30cce6
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/src/commonMain/kotlin/common.kt
@@ -0,0 +1 @@
+fun common() = 1
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/src/commonTest/kotlin/CommonTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/src/commonTest/kotlin/CommonTest.kt
new file mode 100644
index 00000000000..651c855d4f9
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-js-tests/src/commonTest/kotlin/CommonTest.kt
@@ -0,0 +1,31 @@
+package my.pack.name
+
+import common
+import kotlin.test.Ignore
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class CommonTest {
+ @Test
+ fun test1() = assertEquals(common(), 1)
+
+ @Test
+ @Ignore
+ fun test2() = assertEquals(common(), 2)
+
+ @Test
+ fun test3() = assertEquals(common(), 3)
+
+ @Ignore
+ class InnerIgnored {
+ @Test
+ fun test4() = assertEquals(common(), 1)
+
+ @Test
+ @Ignore
+ fun test5() = assertEquals(common(), 2)
+
+ @Test
+ fun test6() = assertEquals(common(), 3)
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts
index 1d2086abacb..8d95877c241 100644
--- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts
+++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts
@@ -15,13 +15,14 @@ publish()
// todo: make lazy
val jar: Jar by tasks
-runtimeJar(rewriteDepsToShadedCompiler(jar))
+val jarContents by configurations.creating
sourcesJar()
javadocJar()
repositories {
google()
+ maven(url = "https://plugins.gradle.org/m2/")
}
pill {
@@ -61,6 +62,9 @@ dependencies {
runtime(projectRuntimeJar(":kotlin-scripting-compiler-embeddable"))
runtime(project(":kotlin-reflect"))
+ jarContents(compileOnly(intellijDep()) { includeJars("serviceMessages") })
+ jarContents(projectArchives(":kotlin-test-nodejs-runner"))
+
// com.android.tools.build:gradle has ~50 unneeded transitive dependencies
compileOnly("com.android.tools.build:gradle:3.0.0") { isTransitive = false }
compileOnly("com.android.tools.build:gradle-core:3.0.0") { isTransitive = false }
@@ -77,6 +81,17 @@ dependencies {
testCompileOnly(project(":kotlin-annotation-processing-gradle"))
}
+runtimeJar(rewriteDepsToShadedCompiler(jar)) {
+ dependsOn(jarContents)
+
+ from {
+ jarContents.asFileTree.map {
+ if (it.endsWith(".jar")) zipTree(it)
+ else it
+ }
+ }
+}
+
tasks {
withType {
kotlinOptions.jdkHome = rootProject.extra["JDK_18"] as String
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
new file mode 100644
index 00000000000..a290ce8e5ee
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesClient.kt
@@ -0,0 +1,310 @@
+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.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.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
+) {
+ init {
+ if (skipRoots) {
+ check(nameOfRootSuiteToReplace == null) { "nameOfRootSuiteToReplace makes no sense when skipRoots is set" }
+ check(nameOfRootSuiteToAppend == null) { "nameOfRootSuiteToAppend cannot work with skipRoots" }
+ }
+ }
+}
+
+internal class TCServiceMessagesClient(
+ private val results: TestResultProcessor,
+ val settings: TCServiceMessagesClientSettings,
+ val log: Logger
+) : ServiceMessageParserCallback {
+ inline fun root(operation: OperationIdentifier, actions: () -> Unit) {
+ RootNode(operation.id).open { root ->
+ if (settings.nameOfRootSuiteToAppend != null) {
+ SuiteNode(root, settings.nameOfRootSuiteToAppend).open {
+ actions()
+ }
+ } else {
+ actions()
+ }
+ }
+ }
+
+ override fun parseException(e: ParseException, text: String) {
+ log.error("Failed to parse test process messages: \"$text\"", e)
+ }
+
+ override fun serviceMessage(message: ServiceMessage) {
+ log.kotlinDebug { "TCSM: $message" }
+
+ 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 TestFailed -> requireLeafTest().failure(message)
+ is TestFinished -> endTest(message.ts, message.testName)
+ is TestIgnored -> {
+ if (message.attributes["suite"] == "true") {
+ // non standard property for dealing with ignored test suites without visiting all inner tests
+ SuiteNode(requireLeaf(), message.testName).open(message.ts) { message.ts }
+ } else {
+ beginTest(message.ts, message.testName, isIgnored = true)
+ endTest(message.ts, message.testName)
+ }
+ }
+ is TestSuiteFinished -> close(message.ts, hookSuiteName(leaf?.parent, message.suiteName))
+ else -> Unit
+ }
+ }
+
+ private fun hookSuiteName(parent: Node?, originalName: String) =
+ if (parent?.parent == null && settings.nameOfRootSuiteToReplace != null) settings.nameOfRootSuiteToReplace
+ else originalName
+
+ override fun regularText(text: String) {
+ log.kotlinDebug { "TCSM stdout captured: $text" }
+
+ val test = leaf as? TestNode
+ if (test != null) {
+ results.output(test.descriptor, DefaultTestOutputEvent(StdOut, text))
+ } else {
+ println(text)
+ }
+ }
+
+ private fun beginTest(ts: Long, testName: String, isIgnored: Boolean = false) {
+ val parent = requireLeafGroup()
+
+ val parsedName = ParsedTestName(testName, parent.localId)
+
+ if (settings.nameOfLeafTestToAppend != null) {
+ val group = open(ts, SuiteNode(parent, parsedName.methodName))
+ 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(
+ parent, parsedName.className, parsedName.classDisplayName, parsedName.methodName,
+ displayName = parsedName.methodName,
+ localId = testName,
+ ignored = isIgnored
+ ))
+ }
+ }
+
+ private fun endTest(ts: Long, testName: String) {
+ val parsedName = ParsedTestName(testName, leaf!!.parent!!.localId)
+
+ if (settings.nameOfLeafTestToAppend != null) {
+ close(ts, "$testName.${settings.nameOfLeafTestToAppend}")
+ close(ts, parsedName.methodName)
+ } else {
+ close(ts, testName)
+ }
+ }
+
+ private fun Node.failure(
+ message: TestFailed
+ ) {
+ hasFailures = true
+ results.failure(descriptor.id, NodeJsTestFailure(message.messageName, message.stacktrace))
+ }
+
+ private inline fun NodeType.open(contents: (NodeType) -> Unit) = open(System.currentTimeMillis()) {
+ contents(it)
+ System.currentTimeMillis()
+ }
+
+
+ private inline fun NodeType.open(tsStart: Long, contents: (NodeType) -> Long) {
+ val child = open(tsStart, this@open)
+ val tsEnd = contents(child)
+ assert(close(tsEnd, child.localId) === child)
+ }
+
+ private fun open(ts: Long, new: NodeType): NodeType = new.also {
+ log.kotlinDebug { "Test node opened: $it" }
+
+ if (!it.isSkippedRoot) {
+ results.started(it.descriptor, TestStartEvent(ts, it.reportingParent?.descriptor?.id))
+ }
+ push(it)
+ }
+
+ private fun close(ts: Long, assertLocalId: String?) = pop().also {
+ if (assertLocalId != null) {
+ check(it.localId == assertLocalId) {
+ "Bad TCSM: unexpected node to close: ${it.localId}, stack: ${
+ collectParents().joinToString("") { item -> "\n - ${item.localId}" }
+ }\n"
+ }
+ }
+
+ log.kotlinDebug { "Test node closed: $it" }
+
+ if (!it.isSkippedRoot) {
+ results.completed(it.descriptor.id, TestCompleteEvent(ts, it.resultType))
+ }
+ }
+
+ private fun collectParents(): MutableList {
+ var i = leaf
+ val items = mutableListOf()
+ while (i != null) {
+ items.add(i)
+ i = i.parent
+ }
+ return items
+ }
+
+ class ParsedTestName(testName: String, parentName: String) {
+ val hasClassName: Boolean
+ val className: String
+ val classDisplayName: String
+ val methodName: String
+
+ init {
+ val methodNameCut = testName.lastIndexOf('.')
+ hasClassName = methodNameCut != -1
+
+ if (hasClassName) {
+ className = testName.substring(0, methodNameCut)
+ classDisplayName = className.substringAfterLast('.')
+ methodName = testName.substring(methodNameCut + 1)
+ } else {
+ className = parentName
+ classDisplayName = parentName
+ methodName = testName
+ }
+ }
+ }
+
+
+ /**
+ * Node of tests tree
+ */
+ abstract inner class Node(
+ var parent: Node? = null,
+ val localId: String
+ ) {
+ val reportingParent: Node?
+ get() = when {
+ parent == null -> null
+ parent!!.isSkippedRoot -> parent!!.reportingParent
+ else -> parent
+ }
+
+ val isSkippedRoot: Boolean
+ get() = settings.skipRoots && parent != null && parent!!.parent == null
+
+ val id: String = if (parent != null) "${reportingParent?.descriptor?.id}.$localId" else localId
+
+ abstract val descriptor: TestDescriptorInternal
+
+ var hasFailures: Boolean = false
+ set(value) {
+ // traverse parents only on first failure
+ if (!field) {
+ field = value
+ parent?.hasFailures = true
+ }
+ }
+
+ /**
+ * If all tests in group are ignored, then group marked as skipped.
+ * This is workaround for absence of ignored test suite flag in TC service messages protocol.
+ */
+ var containsNotIgnored: Boolean = false
+ set(value) {
+ // traverse parents only on first test
+ if (!field) {
+ field = value
+ parent?.containsNotIgnored = true
+ }
+ }
+
+ val resultType: TestResult.ResultType
+ get() = when {
+ containsNotIgnored -> when {
+ hasFailures -> FAILURE
+ else -> SUCCESS
+ }
+ else -> SKIPPED
+ }
+
+ override fun toString(): String = descriptor.toString()
+ }
+
+ 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
+ }
+ }
+
+ inner class SuiteNode(parent: Node? = null, name: String) : Node(parent, name) {
+ override val descriptor = object : DefaultTestSuiteDescriptor(id, name) {
+ override fun getParent(): TestDescriptorInternal? = this@SuiteNode.parent?.descriptor
+ }
+ }
+
+ inner class TestNode(
+ parent: Node,
+ className: String,
+ classDisplayName: String,
+ methodName: String,
+ displayName: String,
+ localId: String,
+ ignored: Boolean = false
+ ) : Node(parent, localId) {
+ override val descriptor = object : DefaultTestDescriptor(id, className, methodName, classDisplayName, displayName) {
+ override fun getParent(): TestDescriptorInternal? = this@TestNode.parent?.descriptor
+ }
+
+ init {
+ if (!ignored) containsNotIgnored = true
+ }
+ }
+
+ private var leaf: Node? = null
+
+ private val ServiceMessage.ts: Long
+ get() = creationTimestamp?.timestamp?.time ?: System.currentTimeMillis()
+
+ private fun push(node: Node) = node.also { leaf = node }
+ private fun pop() = leaf!!.also { leaf = it.parent }
+
+ fun closeAll() {
+ val ts = System.currentTimeMillis()
+
+ while (leaf != null) {
+ close(ts, leaf!!.localId)
+ }
+ }
+
+ private fun requireLeaf() = leaf ?: error("test out of group")
+ private fun requireLeafGroup() = requireLeaf().also {
+ check(it !is TestNode) { "previous test `$it` not finished"}
+ }
+ private fun requireLeafTest() = leaf as? TestNode
+ ?: 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
new file mode 100644
index 00000000000..09a38da17b2
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/testing/TCServiceMessagesTestExecutor.kt
@@ -0,0 +1,86 @@
+package org.jetbrains.kotlin.gradle.internal.testing
+
+import jetbrains.buildServer.messages.serviceMessages.ServiceMessage
+import org.gradle.api.internal.tasks.testing.TestExecuter
+import org.gradle.api.internal.tasks.testing.TestExecutionSpec
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.internal.operations.BuildOperationExecutor
+import org.gradle.process.ProcessForkOptions
+import org.gradle.process.internal.ExecHandle
+import org.gradle.process.internal.ExecHandleFactory
+import org.slf4j.LoggerFactory
+import java.io.PipedInputStream
+import java.io.PipedOutputStream
+import kotlin.concurrent.thread
+
+data class TCServiceMessagesTestExecutionSpec(
+ val forkOptions: ProcessForkOptions,
+ val args: List,
+ val clientSettings: TCServiceMessagesClientSettings
+) : TestExecutionSpec
+
+private val log = LoggerFactory.getLogger("org.jetbrains.kotlin.gradle.tasks.testing")
+
+class TCServiceMessagesTestExecutor(
+ val execHandleFactory: ExecHandleFactory,
+ val buildOperationExecutor: BuildOperationExecutor
+) : TestExecuter {
+ var execHandle: ExecHandle? = null
+ var outputReaderThread: Thread? = null
+ var shouldStop = false
+
+ override fun execute(spec: TCServiceMessagesTestExecutionSpec, testResultProcessor: TestResultProcessor) {
+ val stdInPipe = PipedInputStream()
+
+ val rootOperation = buildOperationExecutor.currentOperation.parentId
+
+ outputReaderThread = thread(name = "${spec.forkOptions} output reader") {
+ try {
+ val client = TCServiceMessagesClient(testResultProcessor, spec.clientSettings, log)
+
+ client.root(rootOperation) {
+ stdInPipe.reader().useLines { lines ->
+ lines.forEach {
+ if (shouldStop) {
+ client.closeAll()
+ return@thread
+ }
+
+ try {
+ ServiceMessage.parse(it, client)
+ } catch (e: Exception) {
+ log.error(
+ "Error while processing test process output message \"$it\"",
+ e
+ )
+ }
+ }
+ }
+ }
+ } catch (t: Throwable) {
+ log.error("Error creating TCServiceMessagesClient", t)
+ }
+ }
+
+ val exec = execHandleFactory.newExec()
+ spec.forkOptions.copyTo(exec)
+ exec.args = spec.args
+ exec.standardOutput = PipedOutputStream(stdInPipe)
+
+ execHandle = exec.build()
+
+ execHandle!!.start()
+ val result = execHandle!!.waitForFinish()
+ outputReaderThread!!.join()
+
+ if (result.exitValue != 0) {
+ error("$execHandle exited with errors (exit code: ${result.exitValue})")
+ }
+ }
+
+ override fun stopNow() {
+ shouldStop = true
+ execHandle?.abort()
+ outputReaderThread?.join()
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 00000000000..ecc8554773e
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt
@@ -0,0 +1,118 @@
+package org.jetbrains.kotlin.gradle.targets.js
+
+import org.gradle.api.Project
+import org.gradle.api.internal.plugins.DslObject
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.testing.base.plugins.TestingBasePlugin
+import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
+import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExtension
+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.KotlinJsNodeModulesTask
+import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestRuntimeToNodeModulesTask
+import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestTask
+import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
+import org.jetbrains.kotlin.gradle.tasks.registerTask
+import java.io.File
+
+internal class KotlinJsCompilationTestsConfigurator(
+ val compilation: KotlinCompilationToRunnableFiles<*>
+) {
+ private val target get() = compilation.target
+ private val project get() = target.project
+ private val compileTestKotlin2Js get() = compilation.compileKotlinTask as Kotlin2JsCompile
+ private val isSinglePlatformProject get() = target is KotlinWithJavaTarget<*>
+ private val testTaskName: String
+ get() = if (isSinglePlatformProject) "testJs" else camelCaseTargetName("test")
+
+ private fun camelCaseTargetName(prefix: String): String {
+ return if (isSinglePlatformProject) prefix
+ else target.name + prefix.capitalize()
+ }
+
+ @Suppress("SameParameterValue")
+ private fun underscoredCompilationName(prefix: String): String {
+ return if (isSinglePlatformProject) prefix
+ else "${target.name}_${compilation.name}_$prefix"
+ }
+
+ private val nodeModulesDir
+ get() = project.buildDir.resolve(underscoredCompilationName("node_modules"))
+
+ @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
+
+ private val Kotlin2JsCompile.jsRuntimeClasspath: FileCollection
+ get() = classpath.plus(project.files(destinationDir))
+
+ fun configure() {
+ val nodeModulesTask = registerTask(
+ project,
+ camelCaseTargetName("kotlinJsNodeModules"),
+ KotlinJsNodeModulesTask::class.java
+ ) {
+ it.dependsOn(compileTestKotlin2Js)
+
+ it.nodeModulesDir = nodeModulesDir
+ it.classpath = compileTask.jsRuntimeClasspath
+ }
+
+ val nodeModulesTestRuntimeTask = registerTask(
+ project,
+ camelCaseTargetName("kotlinJsNodeModulesTestRuntime"),
+ KotlinNodeJsTestRuntimeToNodeModulesTask::class.java
+ ) {
+ it.nodeModulesDir = nodeModulesDir
+ }
+
+ val projectWithNodeJsPlugin = NodeJsPlugin.ensureAppliedInHierarchy(target.project)
+
+ val testTask = registerTask(project, testTaskName, KotlinNodeJsTestTask::class.java) { testJs ->
+ testJs.group = "verification"
+
+ testJs.dependsOn(
+ nodeModulesTask.getTaskOrProvider(),
+ nodeModulesTestRuntimeTask.getTaskOrProvider()
+ )
+
+ if (!isSinglePlatformProject) {
+ testJs.targetName = target.name
+ }
+
+ testJs.nodeJsProcessOptions.workingDir = project.projectDir
+
+ testJs.nodeModulesDir = nodeModulesDir
+ 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") }
+ }
+
+ project.afterEvaluate {
+ project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(testTask.getTaskOrProvider())
+
+ // defer nodeJs executable setup, as nodejs project settings may change during configuration
+ testTask.configure {
+ val nodeJsSetupTask = projectWithNodeJsPlugin.tasks.findByName(NodeJsSetupTask.NAME)
+ it.dependsOn(nodeJsSetupTask)
+
+ if (it.nodeJsProcessOptions.executable == null) {
+ it.nodeJsProcessOptions.executable = NodeJsExtension[projectWithNodeJsPlugin].buildEnv().nodeExec
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt
index 2ccfa15e0f9..4cbfc278876 100644
--- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsTargetConfigurator.kt
@@ -5,21 +5,14 @@
package org.jetbrains.kotlin.gradle.targets.js
-import org.gradle.api.plugins.JavaBasePlugin
-import org.jetbrains.kotlin.gradle.plugin.Kotlin2JsSourceSetProcessor
-import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
-import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor
-import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator
+import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
-import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinJsNodeModulesTask
-import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestRuntimeToNodeModulesTask
-import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestTask
-import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
- KotlinTargetConfigurator(true, true, kotlinPluginVersion) {
+ KotlinTargetConfigurator(true, true, kotlinPluginVersion) {
override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> {
val tasksProvider = KotlinTasksProvider(compilation.target.targetName)
@@ -28,59 +21,11 @@ class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
override fun configureTest(target: KotlinOnlyTarget) {
target.compilations.all {
- configureTest(it)
- }
- }
+ it.compileKotlinTask.kotlinOptions.moduleKind = "umd"
- private fun configureTest(compilation: KotlinCompilationToRunnableFiles<*>) {
- val target = compilation.target
- val project = target.project
- val name = compilation.name
- val compileTestKotlin2Js = compilation.compileKotlinTask as Kotlin2JsCompile
- val isDefaultTarget = compilation.name.isBlank()
-
- fun camelCaseName(prefix: String): String {
- return if (isDefaultTarget) prefix
- else name + prefix.capitalize()
- }
-
- fun underscoredName(prefix: String): String {
- return if (isDefaultTarget) prefix
- else "${name}_$prefix"
- }
-
- val nodeModulesDir = project.buildDir.resolve(underscoredName("node_modules"))
- val nodeModulesTask = project.tasks.create(
- camelCaseName("kotlinJsNodeModules"),
- KotlinJsNodeModulesTask::class.java
- ) {
- it.dependsOn(compileTestKotlin2Js)
-
- it.nodeModulesDir = nodeModulesDir
- it.compile = compileTestKotlin2Js
- }
-
- val nodeModulesTestRuntimeTask = project.tasks.create(
- camelCaseName("kotlinJsNodeModulesTestRuntime"),
- KotlinNodeJsTestRuntimeToNodeModulesTask::class.java
- ) {
- it.nodeModulesDir = nodeModulesDir
- }
-
- val testJs: KotlinNodeJsTestTask =
- if (isDefaultTarget) project.tasks.create("testJs", KotlinNodeJsTestTask::class.java)
- else project.tasks.replace(camelCaseName("test"), KotlinNodeJsTestTask::class.java)
-
- testJs.also {
- it.group = "verification"
- it.dependsOn(nodeModulesTask, nodeModulesTestRuntimeTask)
-
- it.nodeModulesDir = nodeModulesDir
- it.nodeModulesToLoad = setOf(compileTestKotlin2Js.outputFile.name)
- }
-
- project.afterEvaluate {
- project.tasks.findByName(JavaBasePlugin.CHECK_TASK_NAME)?.dependsOn(testJs)
+ if (it.name == KotlinCompilation.TEST_COMPILATION_NAME) {
+ KotlinJsCompilationTestsConfigurator(it).configure()
+ }
}
}
}
\ No newline at end of file
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
new file mode 100644
index 00000000000..d71da37be43
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/NodeJsTestFailure.kt
@@ -0,0 +1,15 @@
+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/nodejs/NodeJsEnv.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsEnv.kt
new file mode 100644
index 00000000000..223f097d5ac
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsEnv.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.kotlin.gradle.targets.js.nodejs
+
+import java.io.*
+
+internal data class NodeJsEnv(
+ val nodeDir: File,
+ val nodeBinDir: File,
+ val nodeExec: String,
+ val npmExec: String,
+
+ val platformName: String,
+ val architectureName: String,
+ val ivyDependency: String
+) {
+ val isWindows: Boolean
+ get() = platformName == "win"
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt
new file mode 100644
index 00000000000..c40c04ed88a
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt
@@ -0,0 +1,68 @@
+package org.jetbrains.kotlin.gradle.targets.js.nodejs
+
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.logging.kotlinInfo
+import java.io.File
+
+open class NodeJsExtension(project: Project) {
+ private val gradleHome = project.gradle.gradleUserHomeDir.also {
+ project.logger.kotlinInfo("Storing cached files in $it")
+ }
+
+ var installationDir = gradleHome.resolve("nodejs")
+
+ var distBaseUrl = "https://nodejs.org/dist"
+ var version = "10.15.3"
+ var npmVersion = ""
+
+ var nodeCommand = "node"
+ var npmCommand = "npm"
+
+ var download = true
+
+ internal fun buildEnv(): NodeJsEnv {
+ val platform = NodeJsPlatform.name
+ val architecture = NodeJsPlatform.architecture
+
+ val nodeDir = installationDir.resolve("node-v$version-$platform-$architecture")
+ val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN
+ val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
+
+ fun getExecutable(command: String, customCommand: String, windowsExtension: String): String {
+ val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand
+ return if (download) File(nodeBinDir, finalCommand).absolutePath else finalCommand
+ }
+
+ fun getIvyDependency(): String {
+ val type = if (isWindows) "zip" else "tar.gz"
+ return "org.nodejs:node:$version:$platform-$architecture@$type"
+ }
+
+ return NodeJsEnv(
+ nodeDir = nodeDir,
+ nodeBinDir = nodeBinDir,
+ nodeExec = getExecutable("node", nodeCommand, "exe"),
+ npmExec = getExecutable("npm", npmCommand, "cmd"),
+ platformName = platform,
+ architectureName = architecture,
+ ivyDependency = getIvyDependency()
+ )
+ }
+
+ companion object {
+ const val NODE_JS: String = "nodeJs"
+
+ operator fun get(project: Project): NodeJsExtension {
+ val extension = project.extensions.findByType(NodeJsExtension::class.java)
+ if (extension != null)
+ return extension
+
+ val parentProject = project.parent
+ if (parentProject != null)
+ return get(parentProject)
+
+ throw GradleException("NodeJsExtension is not installed")
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlatform.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlatform.kt
new file mode 100644
index 00000000000..8d25659b6b0
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlatform.kt
@@ -0,0 +1,49 @@
+package org.jetbrains.kotlin.gradle.targets.js.nodejs
+
+/**
+ * Provides platform and architecture names that is used to download NodeJs.
+ * See [NodeJsEnv.ivyDependency] that is filled in [NodeJsExtension.buildEnv].
+ */
+internal object NodeJsPlatform {
+ private val props = System.getProperties()
+ private fun property(name: String) = props.getProperty(name) ?: System.getProperty(name)
+
+ const val WIN = "win"
+ const val DARWIN = "darwin"
+ const val LINUX = "linux"
+ const val SUNOS = "sunos"
+
+ val name: String = run {
+ val name = property("os.name").toLowerCase()
+ when {
+ name.contains("windows") -> WIN
+ name.contains("mac") -> DARWIN
+ name.contains("linux") -> LINUX
+ name.contains("freebsd") -> LINUX
+ name.contains("sunos") -> SUNOS
+ else -> throw IllegalArgumentException("Unsupported OS: $name")
+ }
+ }
+
+ const val X64 = "x64"
+ const val X86 = "x86"
+ const val ARM64 = "arm64"
+
+ val architecture: String = run {
+ val arch = property("os.arch").toLowerCase()
+ when {
+ arch.contains("64") -> X64
+ arch == "arm" -> {
+ // as Java just returns "arm" on all ARM variants, we need a system call to determine the exact arch
+ // the node binaries for 'armv8l' are called 'arm64', so we need to distinguish here
+ val process = Runtime.getRuntime().exec("uname -m")
+ val systemArch = process.inputStream.bufferedReader().use { it.readText() }
+ when (systemArch.trim()) {
+ "armv8l" -> ARM64
+ else -> systemArch
+ }
+ }
+ else -> X86
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt
new file mode 100644
index 00000000000..02cbced2834
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt
@@ -0,0 +1,29 @@
+package org.jetbrains.kotlin.gradle.targets.js.nodejs
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExtension.Companion.NODE_JS
+
+open class NodeJsPlugin : Plugin {
+ override fun apply(project: Project): Unit = project.run {
+ this.extensions.create(NODE_JS, NodeJsExtension::class.java, this)
+ tasks.create(NodeJsSetupTask.NAME, NodeJsSetupTask::class.java)
+ }
+
+ companion object {
+ fun ensureAppliedInHierarchy(myProject: Project): Project =
+ findInHeirarchy(myProject) ?: myProject.also {
+ it.pluginManager.apply(NodeJsPlugin::class.java)
+ }
+
+ fun findInHeirarchy(myProject: Project): Project? {
+ var project: Project? = myProject
+ while (project != null) {
+ if (myProject.plugins.hasPlugin(NodeJsPlugin::class.java)) return project
+ project = project.parent
+ }
+
+ return null
+ }
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt
new file mode 100644
index 00000000000..9a9c0fec74d
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt
@@ -0,0 +1,85 @@
+package org.jetbrains.kotlin.gradle.targets.js.nodejs
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.jetbrains.kotlin.gradle.logging.kotlinInfo
+import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
+import java.io.File
+import java.net.URI
+
+open class NodeJsSetupTask : DefaultTask() {
+ private val settings = NodeJsExtension[project]
+ private val env by lazy { settings.buildEnv() }
+
+ init {
+ group = NodeJsExtension.NODE_JS
+ description = "Download and install a local node/npm version."
+ }
+
+ val input: Set
+ @Input get() = setOf(settings.download.toString(), env.ivyDependency)
+
+ val destination: File
+ @OutputDirectory get() = env.nodeDir
+
+ @Suppress("unused")
+ @TaskAction
+ fun exec() {
+ if (!settings.download)
+ return
+
+ @Suppress("UnstableApiUsage", "DEPRECATION")
+ val repo = project.repositories.ivy { repo ->
+ repo.name = "Node Distributions at ${settings.distBaseUrl}"
+ repo.url = URI(settings.distBaseUrl)
+ repo.layout("pattern") { layout ->
+ layout as IvyPatternRepositoryLayout
+ layout.artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]")
+ layout.ivy("v[revision]/ivy.xml")
+ }
+ repo.metadataSources { it.artifact() }
+
+ if (isGradleVersionAtLeast(5, 1)) {
+ repo.content { it.includeModule("org.nodejs", "node") }
+ }
+ }
+
+ val dep = this.project.dependencies.create(env.ivyDependency)
+ val conf = this.project.configurations.detachedConfiguration(dep)
+ conf.isTransitive = false
+ val result = conf.resolve().single()
+ project.repositories.remove(repo)
+
+ project.logger.kotlinInfo("Using node distribution from '$result'")
+
+ unpackNodeArchive(result, destination.parentFile) // parent because archive contains name already
+
+ if (!env.isWindows) {
+ File(env.nodeExec).setExecutable(true)
+ }
+ }
+
+ private fun unpackNodeArchive(archive: File, destination: File) {
+ project.logger.kotlinInfo("Unpacking $archive to $destination")
+
+ when {
+ archive.name.endsWith("zip") -> project.copy {
+ it.from(project.zipTree(archive))
+ it.into(destination)
+ }
+ else -> {
+ project.copy {
+ it.from(project.tarTree(archive))
+ it.into(destination)
+ }
+ }
+ }
+ }
+
+ companion object {
+ const val NAME: String = "nodeJsSetup"
+ }
+}
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinJsNodeModulesTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinJsNodeModulesTask.kt
new file mode 100644
index 00000000000..ee40faa172e
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinJsNodeModulesTask.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.js.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.*
+import java.io.File
+
+open class KotlinJsNodeModulesTask : DefaultTask() {
+ @OutputDirectory
+ @SkipWhenEmpty
+ lateinit var nodeModulesDir: File
+
+ @Classpath
+ @InputFiles
+ @SkipWhenEmpty
+ lateinit var classpath: FileCollection
+
+ @TaskAction
+ fun copyFromRuntimeClasspath() {
+ project.copy { copy ->
+ copy.includeEmptyDirs = false
+
+ classpath.forEach {
+ if (it.isZip) copy.from(project.zipTree(it))
+ else copy.from(it)
+ }
+
+ copy.include { fileTreeElement ->
+ isKotlinJsRuntimeFile(fileTreeElement.file)
+ }
+
+ copy.into(nodeModulesDir)
+ }
+ }
+}
+
+val File.isZip
+ get() = isFile && name.endsWith(".jar")
+
+fun isKotlinJsRuntimeFile(file: File): Boolean {
+ if (!file.isFile) return false
+ val name = file.name
+ return (name.endsWith(".js") && !name.endsWith(".meta.js"))
+ || name.endsWith(".js.map")
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestRuntime.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestRuntime.kt
new file mode 100644
index 00000000000..a4ce1042c22
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestRuntime.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.js.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.*
+import java.io.File
+import java.nio.file.Files
+import java.nio.file.StandardCopyOption
+
+const val kotlinNodeJsTestRuntimeBin: String = "kotlin-js-test"
+
+open class KotlinNodeJsTestRuntimeToNodeModulesTask : DefaultTask() {
+ @Input
+ public var resourceName: String = "/kotlin-js-test.js"
+
+ @OutputDirectory
+ @SkipWhenEmpty
+ public lateinit var nodeModulesDir: File
+
+ @TaskAction
+ fun copyRuntime() {
+ val testsRuntime = javaClass.getResourceAsStream(resourceName) ?: error("Cannot find `$resourceName` in resources")
+ val bin = nodeModulesDir.resolve(".bin").resolve(kotlinNodeJsTestRuntimeBin)
+ bin.parentFile.mkdirs()
+ Files.copy(
+ testsRuntime,
+ bin.toPath(),
+ StandardCopyOption.REPLACE_EXISTING
+ )
+ }
+}
+
+data class KotlinNodeJsTestRunnerCliArgs(
+ val moduleNames: List,
+ val include: Collection = listOf(),
+ val exclude: Collection = listOf(),
+ val ignoredTestSuites: IgnoredTestSuitesReporting = IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored
+) {
+ fun toList(): List = mutableListOf().also { args ->
+ if (include.isNotEmpty()) {
+ args.add("--include")
+ args.add(include.joinToString(","))
+ }
+
+ if (exclude.isNotEmpty()) {
+ args.add("--exclude")
+ args.add(exclude.joinToString(","))
+ }
+
+ if (ignoredTestSuites !== IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored) {
+ args.add("--ignoredTestSuites")
+ args.add(ignoredTestSuites.name)
+ }
+
+ args.addAll(moduleNames)
+ }
+
+ @Suppress("EnumEntryName")
+ enum class IgnoredTestSuitesReporting {
+ skip, reportAsIgnoredTest, reportAllInnerTestsAsIgnored
+ }
+}
+
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
new file mode 100644
index 00000000000..41383717e4b
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/tasks/KotlinNodeJsTestTask.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.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.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.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() {
+ @Input
+ var ignoredTestSuites: IgnoredTestSuites =
+ IgnoredTestSuites.showWithContents
+
+ @Input
+ var testsGrouping: TestsGrouping =
+ TestsGrouping.root
+
+ @Input
+ @Optional
+ var targetName: String? = null
+
+ @Input
+ var excludes = mutableSetOf()
+
+ @InputDirectory
+ var nodeModulesDir: File? = null
+
+ @Input
+ @SkipWhenEmpty
+ var nodeModulesToLoad: Set = setOf()
+
+ @InputFile
+ @Optional
+ var testRuntimeNodeModule: File? = null
+
+ @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)
+
+ val nodeJsExecutable: String
+ @Input get() = nodeJsProcessOptions.executable
+
+ val nodeJsWorkingDirCanonicalPath: String
+ @Input get() = nodeJsProcessOptions.workingDir.canonicalPath
+
+ fun nodeJs(options: ProcessForkOptions.() -> Unit) {
+ options(nodeJsProcessOptions)
+ }
+
+ override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec {
+ val extendedForkOptions = DefaultProcessForkOptions(fileResolver)
+ nodeJsProcessOptions.copyTo(extendedForkOptions)
+
+ extendedForkOptions.environment.addPath("NODE_PATH", nodeModulesDir!!.canonicalPath)
+
+ val cliArgs = KotlinNodeJsTestRunnerCliArgs(
+ nodeModulesToLoad.toList(),
+ filterExt.includePatterns + filterExt.commandLineIncludePatterns,
+ excludes,
+ ignoredTestSuites.cli
+ )
+
+ val clientSettings = when (testsGrouping) {
+ TestsGrouping.none -> TCServiceMessagesClientSettings(rootNodeName = name, skipRoots = true)
+ TestsGrouping.root -> TCServiceMessagesClientSettings(rootNodeName = name, nameOfRootSuiteToReplace = targetName)
+ TestsGrouping.leaf -> TCServiceMessagesClientSettings(
+ rootNodeName = name,
+ skipRoots = true,
+ nameOfLeafTestToAppend = targetName
+ )
+ }
+
+ return TCServiceMessagesTestExecutionSpec(
+ extendedForkOptions,
+ listOf(finalTestRuntimeNodeModule.absolutePath) + cliArgs.toList(),
+ clientSettings
+ )
+ }
+
+ @get:Inject
+ open val execHandleFactory: ExecHandleFactory
+ get() = injected
+
+ private val finalTestRuntimeNodeModule: File
+ get() = testRuntimeNodeModule
+ ?: nodeModulesDir!!.resolve(".bin").resolve(kotlinNodeJsTestRuntimeBin)
+
+ override fun createTestExecuter() = TCServiceMessagesTestExecutor(
+ execHandleFactory,
+ buildOperationExecutor
+ )
+}
+
+private fun MutableMap.addPath(key: String, path: String) {
+ val prev = get(key)
+ if (prev == null) set(key, path)
+ else set(key, prev as String + File.pathSeparator + path)
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/options.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/options.kt
new file mode 100644
index 00000000000..22790140ce1
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/testing/options.kt
@@ -0,0 +1,18 @@
+package org.jetbrains.kotlin.gradle.testing
+
+import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestRunnerCliArgs
+
+@Suppress("EnumEntryName")
+enum class IgnoredTestSuites(val cli: KotlinNodeJsTestRunnerCliArgs.IgnoredTestSuitesReporting) {
+ hide(KotlinNodeJsTestRunnerCliArgs.IgnoredTestSuitesReporting.skip),
+ showWithContents(KotlinNodeJsTestRunnerCliArgs.IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored),
+ showWithoutContents(KotlinNodeJsTestRunnerCliArgs.IgnoredTestSuitesReporting.reportAsIgnoredTest)
+}
+
+@Suppress("EnumEntryName")
+enum class TestsGrouping {
+ none,
+ root,
+ leaf;
+}
+
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/injected.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/injected.kt
new file mode 100644
index 00000000000..c23f4b9ec02
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/injected.kt
@@ -0,0 +1,11 @@
+/*
+ * 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.utils
+
+/**
+ * Utility for functions annotated with @Inject
+ */
+val injected: Nothing get() = error("Should be injected");
diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.platform.nodejs.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.platform.nodejs.properties
new file mode 100644
index 00000000000..c5b1feac01b
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/org.jetbrains.kotlin.platform.nodejs.properties
@@ -0,0 +1 @@
+implementation-class=org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
\ 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
new file mode 100644
index 00000000000..3914023fed8
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/RecordingTestResultProcessor.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.kotlin.gradle.internal.testing
+
+import org.gradle.api.internal.tasks.testing.*
+import org.gradle.api.tasks.testing.TestOutputEvent
+
+class RecordingTestResultProcessor : TestResultProcessor {
+ val output = StringBuilder()
+ var indent = 0
+
+ fun line(line: String) {
+ repeat(indent) { output.append(" ") }
+ output.appendln(line)
+ }
+
+ override fun started(test: TestDescriptorInternal, event: TestStartEvent) {
+ val description = when (test) {
+ is DefaultTestSuiteDescriptor -> "SUITE ${test.displayName}"
+ is DefaultTestDescriptor -> "TEST displayName: ${test.displayName}, " +
+ "classDisplayName: ${test.classDisplayName}, " +
+ "className: ${test.className}, " +
+ "name: ${test.name}"
+ else -> error("Unknown test descriptor $test")
+ }
+ line("STARTED $description // ${test.id}")
+ indent++
+ }
+
+ override fun output(testId: Any, event: TestOutputEvent) {
+ line("${event.destination}[${event.message}] // $testId")
+ }
+
+ override fun failure(testId: Any, result: Throwable) {
+ line("FAILURE ${result.message} // $testId")
+ }
+
+ override fun completed(testId: Any, event: TestCompleteEvent) {
+ indent--
+ line("COMPLETED ${event.resultType} // $testId")
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/AppendLeaf.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/AppendLeaf.kt
new file mode 100644
index 00000000000..e2aa4de4f86
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/AppendLeaf.kt
@@ -0,0 +1,116 @@
+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 AppendLeaf : TCServiceMessagesClientTest() {
+ @Test
+ fun appendLeafWithFqn() {
+ skipRoots = true
+ nameOfLeafTestToAppend = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE myMethod // root.myMethod
+ STARTED TEST displayName: myMethod.js, classDisplayName: MyTest, className: my.company.product.MyTest, name: myMethod // root.myMethod.my.company.product.MyTest.myMethod.js
+ COMPLETED SUCCESS // root.myMethod.my.company.product.MyTest.myMethod.js
+ COMPLETED SUCCESS // root.myMethod
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestStarted("my.company.product.MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.myMethod", 1))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun appendLeafWithClassName() {
+ skipRoots = true
+ nameOfLeafTestToAppend = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE myMethod // root.myMethod
+ STARTED TEST displayName: myMethod.js, classDisplayName: MyTest, className: MyTest, name: myMethod // root.myMethod.MyTest.myMethod.js
+ COMPLETED SUCCESS // root.myMethod.MyTest.myMethod.js
+ COMPLETED SUCCESS // root.myMethod
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestStarted("MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("MyTest.myMethod", 1))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun replaceRootWithSuite() {
+ nameOfRootSuiteToReplace = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE js // root.js
+ STARTED SUITE MyTest // root.js.MyTest
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: MyTest, name: myMethod // root.js.MyTest.myMethod
+ COMPLETED SUCCESS // root.js.MyTest.myMethod
+ COMPLETED SUCCESS // root.js.MyTest
+ COMPLETED SUCCESS // root.js
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestSuiteStarted("MyTest"))
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ serviceMessage(TestSuiteFinished("MyTest"))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun appendLeaf() {
+ skipRoots = true
+ nameOfLeafTestToAppend = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE myMethod // root.myMethod
+ STARTED TEST displayName: myMethod.js, classDisplayName: , className: , name: myMethod // root.myMethod.myMethod.js
+ COMPLETED SUCCESS // root.myMethod.myMethod.js
+ COMPLETED SUCCESS // root.myMethod
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun appendLeafWithSuite() {
+ skipRoots = true
+ nameOfLeafTestToAppend = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE MyTest // root.MyTest
+ STARTED SUITE myMethod // root.MyTest.myMethod
+ STARTED TEST displayName: myMethod.js, classDisplayName: MyTest, className: MyTest, name: myMethod // root.MyTest.myMethod.myMethod.js
+ COMPLETED SUCCESS // root.MyTest.myMethod.myMethod.js
+ COMPLETED SUCCESS // root.MyTest.myMethod
+ COMPLETED SUCCESS // root.MyTest
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestSuiteStarted("MyTest"))
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ serviceMessage(TestSuiteFinished("MyTest"))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/AppendRoot.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/AppendRoot.kt
new file mode 100644
index 00000000000..a0e7bf25140
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/AppendRoot.kt
@@ -0,0 +1,58 @@
+package org.jetbrains.kotlin.gradle.internal.testing.tcsmc
+
+import jetbrains.buildServer.messages.serviceMessages.TestFinished
+import jetbrains.buildServer.messages.serviceMessages.TestStarted
+import org.junit.Test
+
+class AppendRoot : TCServiceMessagesClientTest() {
+ @Test
+ fun testWithFqn() {
+ nameOfRootSuiteToAppend = "ios"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE ios // root.ios
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: my.company.product.MyTest, name: myMethod // root.ios.my.company.product.MyTest.myMethod
+ COMPLETED SUCCESS // root.ios.my.company.product.MyTest.myMethod
+ COMPLETED SUCCESS // root.ios
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestStarted("my.company.product.MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.myMethod", 1))
+ }
+ }
+
+ @Test
+ fun testWithClassName() {
+ nameOfRootSuiteToAppend = "ios"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE ios // root.ios
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: MyTest, name: myMethod // root.ios.MyTest.myMethod
+ COMPLETED SUCCESS // root.ios.MyTest.myMethod
+ COMPLETED SUCCESS // root.ios
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestStarted("MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("MyTest.myMethod", 1))
+ }
+ }
+
+ @Test
+ fun testRoot() {
+ nameOfRootSuiteToAppend = "ios"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE ios // root.ios
+ STARTED TEST displayName: myMethod, classDisplayName: ios, className: ios, name: myMethod // root.ios.myMethod
+ COMPLETED SUCCESS // root.ios.myMethod
+ COMPLETED SUCCESS // root.ios
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ }
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 00000000000..703117bec18
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/Complex.kt
@@ -0,0 +1,197 @@
+package org.jetbrains.kotlin.gradle.internal.testing.tcsmc
+
+import jetbrains.buildServer.messages.serviceMessages.*
+import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClient
+import org.junit.Test
+
+class Complex: TCServiceMessagesClientTest() {
+ @Test
+ fun testComplexJs() {
+ nameOfRootSuiteToReplace = "js"
+
+ 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
+ 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
+ STARTED SUITE MyTestNested // root.js.MyTest.MyTestNested
+ 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
+ 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
+ STARTED SUITE MyTestNestedNested // root.js.MyTest.MyTestNested.MyTestNestedNested
+ 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
+ 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
+ COMPLETED FAILURE // root.js.MyTest.MyTestNested.MyTestNestedNested
+ COMPLETED FAILURE // root.js.MyTest.MyTestNested
+ COMPLETED FAILURE // root.js.MyTest
+ STARTED SUITE MyTest2 // root.js.MyTest2
+ 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
+ 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
+ STARTED SUITE MyTest2Nested // root.js.MyTest2.MyTest2Nested
+ STARTED TEST displayName: myTest13, classDisplayName: MyTestNested, className: my.company.product.MyTest2.MyTestNested, name: myTest13 // root.js.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest13
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest13
+ STARTED TEST displayName: myTest14, classDisplayName: MyTestNested, className: my.company.product.MyTest2.MyTestNested, name: myTest14 // root.js.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest14
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest14
+ STARTED TEST displayName: myTest15, classDisplayName: MyTestNested, className: my.company.product.MyTest2.MyTestNested, name: myTest15 // root.js.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest15
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest15
+ STARTED SUITE MyTest2NestedNested // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested
+ STARTED TEST displayName: myTest16, classDisplayName: MyTest2NestedNested, className: my.company.product.MyTest2.MyTest2NestedNested, name: myTest16 // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest16
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest16
+ STARTED TEST displayName: myTest17, classDisplayName: MyTest2NestedNested, className: my.company.product.MyTest2.MyTest2NestedNested, name: myTest17 // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest17
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest17
+ STARTED TEST displayName: myTest18, classDisplayName: MyTest2NestedNested, className: my.company.product.MyTest2.MyTest2NestedNested, name: myTest18 // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest18
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest18
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested.MyTest2NestedNested
+ COMPLETED SKIPPED // root.js.MyTest2.MyTest2Nested
+ COMPLETED FAILURE // root.js.MyTest2
+ COMPLETED FAILURE // root.js
+COMPLETED FAILURE // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ complexFixture()
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun testComplexIos() {
+ nameOfRootSuiteToAppend = "ios"
+
+ 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
+ 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
+ STARTED SUITE MyTestNested // root.ios.MyTest.MyTestNested
+ 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
+ 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
+ STARTED SUITE MyTestNestedNested // root.ios.MyTest.MyTestNested.MyTestNestedNested
+ 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
+ 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
+ COMPLETED FAILURE // root.ios.MyTest.MyTestNested.MyTestNestedNested
+ COMPLETED FAILURE // root.ios.MyTest.MyTestNested
+ COMPLETED FAILURE // root.ios.MyTest
+ STARTED SUITE MyTest2 // root.ios.MyTest2
+ 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
+ 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
+ STARTED SUITE MyTest2Nested // root.ios.MyTest2.MyTest2Nested
+ STARTED TEST displayName: myTest13, classDisplayName: MyTestNested, className: my.company.product.MyTest2.MyTestNested, name: myTest13 // root.ios.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest13
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest13
+ STARTED TEST displayName: myTest14, classDisplayName: MyTestNested, className: my.company.product.MyTest2.MyTestNested, name: myTest14 // root.ios.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest14
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest14
+ STARTED TEST displayName: myTest15, classDisplayName: MyTestNested, className: my.company.product.MyTest2.MyTestNested, name: myTest15 // root.ios.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest15
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.my.company.product.MyTest2.MyTestNested.myTest15
+ STARTED SUITE MyTest2NestedNested // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested
+ STARTED TEST displayName: myTest16, classDisplayName: MyTest2NestedNested, className: my.company.product.MyTest2.MyTest2NestedNested, name: myTest16 // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest16
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest16
+ STARTED TEST displayName: myTest17, classDisplayName: MyTest2NestedNested, className: my.company.product.MyTest2.MyTest2NestedNested, name: myTest17 // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest17
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest17
+ STARTED TEST displayName: myTest18, classDisplayName: MyTest2NestedNested, className: my.company.product.MyTest2.MyTest2NestedNested, name: myTest18 // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest18
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested.my.company.product.MyTest2.MyTest2NestedNested.myTest18
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested.MyTest2NestedNested
+ COMPLETED SKIPPED // root.ios.MyTest2.MyTest2Nested
+ COMPLETED FAILURE // root.ios.MyTest2
+ COMPLETED FAILURE // root.ios
+COMPLETED FAILURE // root
+ """) {
+ complexFixture()
+ }
+ }
+
+ private fun TCServiceMessagesClient.complexFixture() {
+ serviceMessage(TestSuiteStarted("MyTest"))
+
+ serviceMessage(TestStarted("my.company.product.MyTest.myTest1", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.myTest1", 1))
+ serviceMessage(TestStarted("my.company.product.MyTest.myTest2", false, null))
+ serviceMessage(TestFailed("my.company.product.MyTest.myTest2", null))
+ serviceMessage(TestFinished("my.company.product.MyTest.myTest2", 1))
+ serviceMessage(TestIgnored("my.company.product.MyTest.myTest3", ""))
+
+ serviceMessage(TestSuiteStarted("MyTestNested"))
+ serviceMessage(TestStarted("my.company.product.MyTest.MyTestNested.myTest4", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.MyTestNested.myTest4", 1))
+ serviceMessage(TestStarted("my.company.product.MyTest.MyTestNested.myTest5", false, null))
+ serviceMessage(TestFailed("my.company.product.MyTest.MyTestNested.myTest5", null))
+ serviceMessage(TestFinished("my.company.product.MyTest.MyTestNested.myTest5", 1))
+ serviceMessage(TestIgnored("my.company.product.MyTest.MyTestNested.myTest6", ""))
+
+ serviceMessage(TestSuiteStarted("MyTestNestedNested"))
+ serviceMessage(TestStarted("my.company.product.MyTest.MyTestNestedNested.myTest7", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.MyTestNestedNested.myTest7", 1))
+ serviceMessage(TestStarted("my.company.product.MyTest.MyTestNestedNested.myTest8", false, null))
+ serviceMessage(TestFailed("my.company.product.MyTest.MyTestNestedNested.myTest8", null))
+ serviceMessage(TestFinished("my.company.product.MyTest.MyTestNestedNested.myTest8", 1))
+ serviceMessage(TestIgnored("my.company.product.MyTest.MyTestNestedNested.myTest9", ""))
+ serviceMessage(TestSuiteFinished("MyTestNestedNested"))
+
+ serviceMessage(TestSuiteFinished("MyTestNested"))
+
+ serviceMessage(TestSuiteFinished("MyTest"))
+
+ serviceMessage(TestSuiteStarted("MyTest2"))
+
+ serviceMessage(TestStarted("my.company.product.MyTest2.myTest10", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest2.myTest10", 1))
+ serviceMessage(TestStarted("my.company.product.MyTest2.myTest11", false, null))
+ serviceMessage(TestFailed("my.company.product.MyTest2.myTest11", null))
+ serviceMessage(TestFinished("my.company.product.MyTest2.myTest11", 1))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.myTest12", ""))
+
+ serviceMessage(TestSuiteStarted("MyTest2Nested"))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.MyTestNested.myTest13", ""))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.MyTestNested.myTest14", ""))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.MyTestNested.myTest15", ""))
+
+ serviceMessage(TestSuiteStarted("MyTest2NestedNested"))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.MyTest2NestedNested.myTest16", ""))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.MyTest2NestedNested.myTest17", ""))
+ serviceMessage(TestIgnored("my.company.product.MyTest2.MyTest2NestedNested.myTest18", ""))
+ serviceMessage(TestSuiteFinished("MyTest2NestedNested"))
+
+ serviceMessage(TestSuiteFinished("MyTest2Nested"))
+
+ serviceMessage(TestSuiteFinished("MyTest2"))
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/ReplaceRoot.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/ReplaceRoot.kt
new file mode 100644
index 00000000000..2b7362f0731
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/ReplaceRoot.kt
@@ -0,0 +1,66 @@
+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 ReplaceRoot : TCServiceMessagesClientTest() {
+ @Test
+ fun replaceRootWithFqn() {
+ nameOfRootSuiteToReplace = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE js // root.js
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: my.company.product.MyTest, name: myMethod // root.js.my.company.product.MyTest.myMethod
+ COMPLETED SUCCESS // root.js.my.company.product.MyTest.myMethod
+ COMPLETED SUCCESS // root.js
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestStarted("my.company.product.MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.myMethod", 1))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun replaceRootWithClassName() {
+ nameOfRootSuiteToReplace = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE js // root.js
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: MyTest, name: myMethod // root.js.MyTest.myMethod
+ COMPLETED SUCCESS // root.js.MyTest.myMethod
+ COMPLETED SUCCESS // root.js
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestStarted("MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("MyTest.myMethod", 1))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+
+ @Test
+ fun replaceRoot() {
+ nameOfRootSuiteToReplace = "js"
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE js // root.js
+ STARTED TEST displayName: myMethod, classDisplayName: js, className: js, name: myMethod // root.js.myMethod
+ COMPLETED SUCCESS // root.js.myMethod
+ COMPLETED SUCCESS // root.js
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/SkipRoot.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/SkipRoot.kt
new file mode 100644
index 00000000000..2ad631cea44
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/SkipRoot.kt
@@ -0,0 +1,30 @@
+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 SkipRoot: TCServiceMessagesClientTest() {
+ @Test
+ fun testSkipRoot() {
+ skipRoots = true
+
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE MyTest // root.MyTest
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: MyTest, name: myMethod // root.MyTest.myMethod
+ COMPLETED SUCCESS // root.MyTest.myMethod
+ COMPLETED SUCCESS // root.MyTest
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted(""))
+ serviceMessage(TestSuiteStarted("MyTest"))
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ serviceMessage(TestSuiteFinished("MyTest"))
+ serviceMessage(TestSuiteFinished(""))
+ }
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 00000000000..f3abd228ba8
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TCServiceMessagesClientTest.kt
@@ -0,0 +1,43 @@
+package org.jetbrains.kotlin.gradle.internal.testing.tcsmc
+
+import org.gradle.internal.operations.OperationIdentifier
+import org.jetbrains.kotlin.gradle.internal.testing.RecordingTestResultProcessor
+import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClient
+import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesClientSettings
+import org.slf4j.LoggerFactory
+import kotlin.test.assertEquals
+
+open class TCServiceMessagesClientTest {
+ protected var nameOfRootSuiteToAppend: String? = null
+ protected var nameOfRootSuiteToReplace: String? = null
+ protected var nameOfLeafTestToAppend: String? = null
+ protected var skipRoots: Boolean = false
+
+ internal fun assertEvents(assertion: String, produceServiceMessage: TCServiceMessagesClient.() -> Unit) {
+ val results = RecordingTestResultProcessor()
+ val client = createClient(results)
+
+ client.root(OperationIdentifier(1)) {
+ client.produceServiceMessage()
+ }
+
+ assertEquals(
+ assertion.trim(),
+ results.output.toString().trim()
+ )
+ }
+
+ internal open fun createClient(results: RecordingTestResultProcessor): TCServiceMessagesClient {
+ return TCServiceMessagesClient(
+ results,
+ TCServiceMessagesClientSettings(
+ "root",
+ nameOfRootSuiteToAppend,
+ nameOfRootSuiteToReplace,
+ nameOfLeafTestToAppend,
+ skipRoots
+ ),
+ LoggerFactory.getLogger("test")
+ )
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestNameParsing.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestNameParsing.kt
new file mode 100644
index 00000000000..19afc3d8a6c
--- /dev/null
+++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/internal/testing/tcsmc/TestNameParsing.kt
@@ -0,0 +1,65 @@
+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 TestNameParsing: TCServiceMessagesClientTest() {
+ @Test
+ fun testFqn() {
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: my.company.product.MyTest, name: myMethod // root.my.company.product.MyTest.myMethod
+ COMPLETED SUCCESS // root.my.company.product.MyTest.myMethod
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestStarted("my.company.product.MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("my.company.product.MyTest.myMethod", 1))
+ }
+ }
+
+ @Test
+ fun testSimpleClassName() {
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: MyTest, name: myMethod // root.MyTest.myMethod
+ COMPLETED SUCCESS // root.MyTest.myMethod
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestStarted("MyTest.myMethod", false, null))
+ serviceMessage(TestFinished("MyTest.myMethod", 1))
+ }
+ }
+
+ @Test
+ fun testParentSuite() {
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED SUITE MyTest // root.MyTest
+ STARTED TEST displayName: myMethod, classDisplayName: MyTest, className: MyTest, name: myMethod // root.MyTest.myMethod
+ COMPLETED SUCCESS // root.MyTest.myMethod
+ COMPLETED SUCCESS // root.MyTest
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestSuiteStarted("MyTest"))
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ serviceMessage(TestSuiteFinished("MyTest"))
+ }
+ }
+
+ @Test
+ fun testAlone() {
+ assertEvents("""
+STARTED SUITE root // root
+ STARTED TEST displayName: myMethod, classDisplayName: root, className: root, name: myMethod // root.myMethod
+ COMPLETED SUCCESS // root.myMethod
+COMPLETED SUCCESS // root
+ """) {
+ serviceMessage(TestStarted("myMethod", false, null))
+ serviceMessage(TestFinished("myMethod", 1))
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/.yarnclean b/libraries/tools/kotlin-test-nodejs-runner/.yarnclean
new file mode 100644
index 00000000000..b591611ea7a
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/.yarnclean
@@ -0,0 +1,45 @@
+# test directories
+__tests__
+test
+tests
+powered-test
+
+# asset directories
+docs
+doc
+website
+images
+assets
+
+# examples
+example
+examples
+
+# code coverage directories
+coverage
+.nyc_output
+
+# build scripts
+Makefile
+Gulpfile.js
+Gruntfile.js
+
+# configs
+appveyor.yml
+circle.yml
+codeship-services.yml
+codeship-steps.yml
+wercker.yml
+.tern-project
+.gitattributes
+.editorconfig
+.*ignore
+.eslintrc
+.jshintrc
+.flowconfig
+.documentup.json
+.yarn-metadata.json
+.travis.yml
+
+# misc
+*.md
diff --git a/libraries/tools/kotlin-test-nodejs-runner/README.md b/libraries/tools/kotlin-test-nodejs-runner/README.md
new file mode 100644
index 00000000000..023c5242bd4
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/README.md
@@ -0,0 +1,6 @@
+Kotlin/JS TeamCity tests results reporter.
+
+### Usage
+
+`yarn add @kotlin/js-tests-teamcity --dev`
+`yarn run kotlin-js-tests`
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/build.gradle.kts b/libraries/tools/kotlin-test-nodejs-runner/build.gradle.kts
new file mode 100644
index 00000000000..22dcb85571b
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/build.gradle.kts
@@ -0,0 +1,49 @@
+import com.moowork.gradle.node.yarn.YarnTask
+
+plugins {
+ id("base")
+ id("com.moowork.node") version "1.2.0"
+}
+
+node {
+ version = "11.9.0"
+ download = true
+ nodeModulesDir = projectDir
+}
+
+tasks {
+ "yarn" {
+ outputs.upToDateWhen {
+ projectDir.resolve("node_modules").isDirectory
+ }
+ }
+
+ create("yarnBuild") {
+ group = "build"
+
+ dependsOn("yarn")
+ setWorkingDir(projectDir)
+ args = listOf("build")
+
+ inputs.dir(projectDir.resolve("src"))
+ outputs.file(projectDir.resolve("lib/kotlin-js-test.js"))
+ }
+
+ create("cleanYarn") {
+ group = "build"
+
+ delete = setOf(
+ projectDir.resolve("node_modules"),
+ projectDir.resolve("lib"),
+ projectDir.resolve(".rpt2_cache")
+ )
+ }
+
+ getByName("clean").dependsOn("cleanYarn")
+}
+
+artifacts {
+ add("archives", projectDir.resolve("lib/kotlin-js-test.js")) {
+ builtBy("yarnBuild")
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/cli.ts b/libraries/tools/kotlin-test-nodejs-runner/cli.ts
new file mode 100755
index 00000000000..c6909f3c41e
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/cli.ts
@@ -0,0 +1,65 @@
+import {TeamCityMessagesFlow} from "./src/TeamCityMessagesFlow";
+import {directRunner, KotlinTestRunner} from "./src/KotlinTestRunner";
+import {IgnoredTestSuitesReporting, runWithTeamCityReporter} from "./src/KotlinTestTeamCityReporter";
+import {CliArgsParser} from "./src/CliArgsParser";
+import {configureFiltering} from "./src/CliFiltertingConfiguration";
+import {hrTimer} from "./src/Timer";
+
+const kotlin_test = require('kotlin-test');
+
+const parser = new CliArgsParser({
+ version: VERSION,
+ bin: BIN,
+ description: DESCRIPTION,
+ usage: "[-t --tests] [-e --exclude] , , ..",
+ args: {
+ include: {
+ keys: ['--tests', '--include'],
+ help: "Tests to include. Example: MySuite.test1,MySuite.MySubSuite.*,*unix*,!*windows*",
+ default: "*"
+
+ },
+ exclude: {
+ keys: ['--exclude'],
+ help: "Tests to exclude. Example: MySuite.test1,MySuite.MySubSuite.*,*unix*"
+ },
+ ignoredTestSuites: {
+ keys: ['--ignoredTestSuites'],
+ help: "How to deal with ignored test suites",
+ single: true,
+ values: [
+ IgnoredTestSuitesReporting.skip,
+ IgnoredTestSuitesReporting.reportAsIgnoredTest,
+ IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored
+ ],
+ valuesHelp: [
+ "don't report ignored test suites",
+ "useful to speedup large ignored test suites",
+ "will cause visiting all inner tests",
+ ],
+ default: IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored
+ }
+ },
+ freeArgsTitle: "module_name"
+});
+
+const processArgs = process.argv.slice(2);
+const untypedArgs = parser.parse(processArgs);
+const args = {
+ onIgnoredTestSuites: (untypedArgs.ignoredTestSuites
+ || IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored) as IgnoredTestSuitesReporting,
+ include: untypedArgs.include as string[],
+ exclude: untypedArgs.exclude as string[],
+};
+
+const teamCity = new TeamCityMessagesFlow(null, (payload) => console.log(payload));
+
+let runner: KotlinTestRunner = directRunner;
+runner = runWithTeamCityReporter(runner, args.onIgnoredTestSuites, teamCity, hrTimer);
+runner = configureFiltering(runner, args.include, args.exclude);
+
+kotlin_test.setAdapter(runner);
+
+untypedArgs.free.forEach((arg: string) => {
+ require(arg);
+});
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/env.ts b/libraries/tools/kotlin-test-nodejs-runner/env.ts
new file mode 100644
index 00000000000..7040622143f
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/env.ts
@@ -0,0 +1,4 @@
+declare const DEBUG: boolean;
+declare const VERSION: string;
+declare const BIN: string;
+declare const DESCRIPTION: string;
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/package.json b/libraries/tools/kotlin-test-nodejs-runner/package.json
new file mode 100644
index 00000000000..0cb73042e2c
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@kotlin/js-tests-teamcity",
+ "version": "0.0.1",
+ "description": "Simple Kotlin/JS tests runner with TeamCity reporter",
+ "main": "lib/index.js",
+ "types": "lib/index.d.ts",
+ "bin": {
+ "kotlin-js-tests": "lib/kotlin-js-test.js"
+ },
+ "files": [
+ "lib/**/*"
+ ],
+ "scripts": {
+ "build": "rimraf lib/* && rollup -c rollup.config.js"
+ },
+ "dependencies": {
+ "@types/node": "^10.12.21"
+ },
+ "devDependencies": {
+ "copyfiles": "^2.1.0",
+ "rimraf": "^2.6.3",
+ "rollup": "^1.1.2",
+ "rollup-plugin-commonjs": "^9.2.0",
+ "rollup-plugin-node-resolve": "^4.0.0",
+ "rollup-plugin-sourcemaps": "^0.4.2",
+ "rollup-plugin-typescript2": "^0.19.2",
+ "rollup-plugin-uglify": "^6.0.2",
+ "typescript": "^3.3.1"
+ }
+}
diff --git a/libraries/tools/kotlin-test-nodejs-runner/rollup.config.js b/libraries/tools/kotlin-test-nodejs-runner/rollup.config.js
new file mode 100644
index 00000000000..559b32e320d
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/rollup.config.js
@@ -0,0 +1,59 @@
+import typescript from 'rollup-plugin-typescript2';
+import {uglify} from "rollup-plugin-uglify";
+import nodeResolve from 'rollup-plugin-node-resolve';
+import commonjs from 'rollup-plugin-commonjs';
+
+const pckg = require('./package.json');
+
+export default [
+ {
+ input: './cli.ts',
+ output: {
+ file: 'lib/kotlin-js-test.js',
+ format: 'cjs',
+ banner: '#!/usr/bin/env node'
+ },
+ plugins: [
+ nodeResolve({
+ jsnext: true,
+ main: true
+ }),
+ commonjs(),
+ typescript({
+ tsconfig: "tsconfig.json"
+ }),
+ uglify({
+ sourcemap: true,
+ compress: {
+ // hoist_funs: true,
+ // hoist_vars: true,
+ toplevel: true,
+ unsafe: true,
+ dead_code: true,
+ global_defs: {
+ DEBUG: false,
+ VERSION: pckg.version,
+ BIN: Object.keys(pckg.bin)[0],
+ DESCRIPTION: pckg.description
+ }
+ },
+ mangle: {
+ properties: {
+ keep_quoted: true,
+ reserved: [
+ "argv", "hrtime",
+ "kotlin_test", "kotlin", "setAdapter", "setAssertHook_4duqou$",
+ "suite", "test",
+ "stack"
+ ]
+ },
+ toplevel: true,
+ },
+ // output: {
+ // beautify: true
+ // }
+ }),
+ // sourceMaps()
+ ]
+ }
+]
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/CliArgsParser.ts b/libraries/tools/kotlin-test-nodejs-runner/src/CliArgsParser.ts
new file mode 100644
index 00000000000..367cb66a15e
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/CliArgsParser.ts
@@ -0,0 +1,115 @@
+import {println, startsWith} from "./utils";
+
+export type CliDescription = {
+ version: string,
+ bin: string,
+ description: string,
+ usage: string,
+ args: {
+ [k: string]: CliArgDescription,
+ },
+ freeArgsTitle: string
+}
+
+export type CliArgValues = {
+ [k: string]: string[] | string,
+ free: string[]
+}
+
+export type CliArgDescription = {
+ keys: string[],
+ help: string,
+ values?: string[],
+ valuesHelp?: string[],
+ default?: string,
+ single?: true
+}
+
+export class CliArgsParser {
+ constructor(private description: CliDescription) {
+ }
+
+ printUsage() {
+ const description = this.description;
+
+ println(`${description.bin} v${description.version} - ${description.description}`);
+ println();
+ println(`Usage: ${description.bin} ${description.usage}`);
+ println();
+ for (let key in description.args) {
+ const data = description.args[key];
+ println(' ' + data.keys.join(', '));
+ const indent = ' ';
+ println(`${indent}${data.help}`);
+ if (data.values && data.valuesHelp) {
+ println(`${indent}Possible values:`);
+ for (let i = 0; i < data.values.length; i++) {
+ const value = data.values[i];
+ const help = data.valuesHelp[i];
+ println(`${indent} - "${value}": ${help}`)
+ }
+ }
+ if (data.default) println(`${indent}By default: ${data.default}`);
+ println('')
+ }
+ }
+
+ badArgsExit(message: string) {
+ println(message);
+ println();
+ this.printUsage();
+ process.exit(1)
+ }
+
+ parse(args: string[]): CliArgValues {
+ const description = this.description;
+
+ const result: CliArgValues = {
+ free: []
+ };
+ for (let key in description.args) {
+ if (!description.args[key].single) {
+ result[key] = [];
+ }
+ }
+
+ // process all arguments from left to right
+ args: while (args.length != 0) {
+ const arg = args.shift() as string;
+
+ if (startsWith(arg, '--')) {
+ for (let argName in description.args) {
+ const argDescription = description.args[argName];
+ if (argDescription.keys.indexOf(arg) != -1) {
+ if (args.length == 0) {
+ this.badArgsExit("Missed value after option " + arg);
+ }
+
+ const value = args.shift() as string;
+ if (argDescription.values && argDescription.values.indexOf(value) == -1) {
+ this.badArgsExit("Unsupported value for option " + arg);
+ }
+
+ if (argDescription.single) {
+ result[argName] = value;
+ } else {
+ (result[argName] as string[]).push(value);
+ }
+
+ continue args;
+ }
+ }
+
+ this.badArgsExit("Unknown option: " + arg);
+ } else {
+ result.free.push(arg)
+ }
+ }
+
+ if (result.free.length == 0) {
+ this.badArgsExit(`At least one ${description.freeArgsTitle} should be provided`)
+ }
+
+ return result
+ }
+}
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/CliFiltertingConfiguration.ts b/libraries/tools/kotlin-test-nodejs-runner/src/CliFiltertingConfiguration.ts
new file mode 100644
index 00000000000..3aba5daa009
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/CliFiltertingConfiguration.ts
@@ -0,0 +1,52 @@
+import {KotlinTestRunner} from "./KotlinTestRunner";
+import {
+ allTest,
+ CompositeTestFilter,
+ KotlinTestsFilter,
+ newKotlinTestsFilter,
+ runWithFilter
+} from "./KotlinTestsFilter";
+import {flatMap, println, pushIfNotNull} from "./utils";
+
+export function configureFiltering(
+ runner: KotlinTestRunner,
+ includeWildcards: string[],
+ excludeWildcards: string[]
+): KotlinTestRunner {
+ const include: KotlinTestsFilter[] = [];
+ const exclude: KotlinTestsFilter[] = [];
+
+ function collectWildcards(
+ value: string[],
+ positive: KotlinTestsFilter[],
+ negative: KotlinTestsFilter[]
+ ) {
+ flatMap(value, (t: string) => t.split(','))
+ .map(t => {
+ if (t.length && t[0] == '!') {
+ pushIfNotNull(negative, newKotlinTestsFilter(t.substring(1)))
+ } else {
+ pushIfNotNull(positive, newKotlinTestsFilter(t))
+ }
+ })
+ }
+
+ collectWildcards(includeWildcards, include, exclude);
+ collectWildcards(excludeWildcards, exclude, include);
+
+ if (include.length == 0 && exclude.length == 0) {
+ return runner
+ } else {
+ if (include.length == 0) {
+ include.push(allTest)
+ }
+
+ const filter = new CompositeTestFilter(include, exclude);
+
+ if (DEBUG) {
+ println(filter.toString());
+ }
+
+ return runWithFilter(runner, filter)
+ }
+}
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestRunner.ts b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestRunner.ts
new file mode 100644
index 00000000000..46f58e1aa85
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestRunner.ts
@@ -0,0 +1,14 @@
+export interface KotlinTestRunner {
+ suite(name: string, isIgnored: boolean, fn: () => void): void
+
+ test(name: string, isIgnored: boolean, fn: () => void): void
+}
+
+export const directRunner: KotlinTestRunner = {
+ suite(name: string, isIgnored: boolean, fn: () => void): void {
+ if (!isIgnored) fn()
+ },
+ test(name: string, isIgnored: boolean, fn: () => void): void {
+ if (!isIgnored) fn()
+ }
+};
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestTeamCityReporter.ts b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestTeamCityReporter.ts
new file mode 100644
index 00000000000..155128092d5
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestTeamCityReporter.ts
@@ -0,0 +1,104 @@
+import {KotlinTestRunner} from "./KotlinTestRunner";
+import {TeamCityMessageData, TeamCityMessagesFlow} from "./TeamCityMessagesFlow";
+import {Timer} from "./Timer";
+
+const kotlin_test = require('kotlin-test');
+
+// don't use enum as it is not minified by uglify
+export type IgnoredTestSuitesReporting
+ = "skip" | "reportAsIgnoredTest" | "reportAllInnerTestsAsIgnored"
+export const IgnoredTestSuitesReporting: { [key: string]: IgnoredTestSuitesReporting } = {
+ skip: "skip",
+ reportAsIgnoredTest: "reportAsIgnoredTest",
+ reportAllInnerTestsAsIgnored: "reportAllInnerTestsAsIgnored"
+};
+
+// to reduce minified code size
+function withName(name: string, data?: TeamCityMessageData): TeamCityMessageData {
+ data = data || {};
+ data["name"] = name;
+ return data
+}
+
+export function runWithTeamCityReporter(
+ runner: KotlinTestRunner,
+ ignoredTestSuites: IgnoredTestSuitesReporting,
+ teamCity: TeamCityMessagesFlow,
+ timer: Timer | null
+): KotlinTestRunner {
+ let inIgnoredSuite = false;
+ let currentAssertionResult: { expected: any, actual: any } | null = null;
+
+ kotlin_test.kotlin.test.setAssertHook_4duqou$(function (assertionResult: { expected: any, actual: any }) {
+ currentAssertionResult = assertionResult;
+ });
+
+ return {
+ suite: function (name: string, isIgnored: boolean, fn: () => void) {
+ if (isIgnored) {
+ if (ignoredTestSuites == IgnoredTestSuitesReporting.skip) return;
+ else if (ignoredTestSuites == IgnoredTestSuitesReporting.reportAsIgnoredTest) {
+ teamCity.sendMessage('testIgnored', withName(name, {"suite": true}));
+ return
+ }
+ }
+
+ teamCity.sendMessage('testSuiteStarted', withName(name));
+
+ // noinspection UnnecessaryLocalVariableJS
+ const alreadyInIgnoredSuite = inIgnoredSuite;
+ if (!alreadyInIgnoredSuite && isIgnored) {
+ inIgnoredSuite = true;
+ }
+
+ try {
+ if (isIgnored && ignoredTestSuites == IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored) {
+ fn();
+ } else {
+ runner.suite(name, isIgnored, fn)
+ }
+ } finally {
+ if (isIgnored && !alreadyInIgnoredSuite) {
+ inIgnoredSuite = false;
+ }
+
+ const data: TeamCityMessageData = withName(name);
+
+ // extension only for Gradle
+ if (isIgnored) data["ignored"] = true;
+
+ teamCity.sendMessage('testSuiteFinished', data);
+ }
+ },
+ test: function (name: string, isIgnored: boolean, fn: () => void) {
+ if (inIgnoredSuite || isIgnored) {
+ teamCity.sendMessage('testIgnored', withName(name));
+ } else {
+ const startTime = timer ? timer.start() : null;
+ teamCity.sendMessage('testStarted', withName(name));
+ try {
+ runner.test(name, isIgnored, fn);
+ } catch (e) {
+ const data: TeamCityMessageData = withName(name, {
+ "message": e.message,
+ "details": e.stack
+ });
+
+ if (currentAssertionResult) {
+ data["type"] = 'comparisonFailure';
+ data["expected"] = currentAssertionResult.expected;
+ data["actual"] = currentAssertionResult.actual;
+ }
+ teamCity.sendMessage('testFailed', data);
+ } finally {
+ currentAssertionResult = null;
+ const data: TeamCityMessageData = withName(name);
+ if (startTime) {
+ data["duration"] = timer!!.end(startTime); // ns to ms
+ }
+ teamCity.sendMessage('testFinished', data);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestsFilter.ts b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestsFilter.ts
new file mode 100644
index 00000000000..e755c164259
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestsFilter.ts
@@ -0,0 +1,168 @@
+import {escapeRegExp, startsWith, trim} from "./utils";
+import {KotlinTestRunner} from "./KotlinTestRunner";
+
+export interface KotlinTestsFilter {
+ mayContainTestsFromSuite(fqn: string): boolean;
+
+ containsTest(fqn: string): boolean;
+}
+
+export function runWithFilter(
+ runner: KotlinTestRunner,
+ filter: KotlinTestsFilter,
+): KotlinTestRunner {
+ let path: string[] = [];
+
+ function pathString() {
+ // skip root
+ return path.slice(1).join('.')
+ }
+
+ return {
+ suite: function (name: string, isIgnored: boolean, fn: () => void) {
+ path.push(name);
+
+ try {
+ if (path.length > 1 && !filter.mayContainTestsFromSuite(pathString())) return;
+
+ runner.suite(name, isIgnored, fn);
+ } finally {
+ path.pop()
+ }
+ },
+
+ test: function (name: string, isIgnored: boolean, fn: () => void) {
+ path.push(name);
+
+ try {
+ if (!filter.containsTest(pathString())) return;
+
+ runner.test(name, isIgnored, fn);
+ } finally {
+ path.pop()
+ }
+ }
+ };
+}
+
+export function newKotlinTestsFilter(wildcard: string | null): KotlinTestsFilter | null {
+ if (wildcard == null) return null;
+ wildcard = trim(wildcard);
+ wildcard = wildcard.replace(/\*+/, '*'); // ** => *
+ if (wildcard.length == 0) return null;
+ else if (wildcard == '*') return allTest;
+ else if (wildcard.indexOf('*') == -1) return new ExactFilter(wildcard);
+ else if (startsWith(wildcard, '*')) return new RegExpKotlinTestsFilter(wildcard);
+ else {
+ // optimize for cases like "Something*", "Something*a*b" and so on.
+ // by adding explicit prefix matcher to not visit unneeded suites
+ // (RegExpKotlinTestsFilter doesn't support suites matching)
+ const [prefix, rest] = wildcard.split('*', 2);
+ return new StartsWithFilter(prefix, rest ? new RegExpKotlinTestsFilter(wildcard) : null)
+ }
+}
+
+export const allTest = new class implements KotlinTestsFilter {
+ mayContainTestsFromSuite(fqn: string): boolean {
+ return true;
+ }
+
+ containsTest(fqn: string): boolean {
+ return true;
+ }
+};
+
+export class StartsWithFilter implements KotlinTestsFilter {
+ constructor(
+ public readonly prefix: string,
+ public readonly filter: RegExpKotlinTestsFilter | null
+ ) {
+ }
+
+ isPrefixMatched(fqn: string): boolean {
+ return startsWith(fqn + ".", this.prefix);
+ }
+
+ mayContainTestsFromSuite(fqn: string): boolean {
+ return this.isPrefixMatched(fqn);
+ }
+
+ containsAllTestsFromSuite(fqn: string): boolean {
+ return this.filter == null && this.isPrefixMatched(fqn);
+ }
+
+ containsTest(fqn: string): boolean {
+ return startsWith(fqn, this.prefix)
+ && (this.filter == null || this.filter.containsTest(fqn));
+ }
+}
+
+export class ExactFilter implements KotlinTestsFilter {
+ constructor(public fqn: string) {
+ }
+
+ mayContainTestsFromSuite(fqn: string): boolean {
+ return startsWith(fqn, this.fqn);
+ }
+
+ containsTest(fqn: string): boolean {
+ return fqn === this.fqn;
+ }
+}
+
+export class RegExpKotlinTestsFilter implements KotlinTestsFilter {
+ public readonly regexp: RegExp;
+
+ constructor(wildcard: string) {
+ this.regexp = RegExp(wildcard
+ .split('*')
+ .map(it => escapeRegExp(it))
+ .join('.*'));
+ }
+
+ mayContainTestsFromSuite(fqn: string): boolean {
+ return true
+ }
+
+ containsTest(fqn: string): boolean {
+ return this.regexp!.test(fqn)
+ }
+
+ toString(): string {
+ return this.regexp.toString()
+ }
+}
+
+export class CompositeTestFilter implements KotlinTestsFilter {
+ private readonly excludePrefix: StartsWithFilter[] = [];
+
+ constructor(
+ public include: KotlinTestsFilter[],
+ public exclude: KotlinTestsFilter[]
+ ) {
+ this.exclude.forEach(it => {
+ if (it instanceof StartsWithFilter && it.filter == null)
+ this.excludePrefix.push(it)
+ })
+ }
+
+ mayContainTestsFromSuite(fqn: string): boolean {
+ for (const excl of this.excludePrefix) {
+ if (excl.containsAllTestsFromSuite(fqn)) return false
+ }
+ for (const incl of this.include) {
+ if (incl.mayContainTestsFromSuite(fqn)) return true
+ }
+ return false;
+ }
+
+ containsTest(fqn: string): boolean {
+ for (const excl of this.exclude) {
+ if (excl.containsTest(fqn)) return false
+ }
+ for (const incl of this.include) {
+ if (incl.containsTest(fqn)) return true
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestsFilterDebug.ts b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestsFilterDebug.ts
new file mode 100644
index 00000000000..738fcc39074
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/KotlinTestsFilterDebug.ts
@@ -0,0 +1,32 @@
+import {
+ allTest,
+ CompositeTestFilter,
+ ExactFilter,
+ RegExpKotlinTestsFilter,
+ StartsWithFilter
+} from "./KotlinTestsFilter";
+
+declare const DEBUG: boolean;
+
+if (DEBUG) {
+ allTest.toString = function () {
+ return "(ALL)"
+ };
+
+ StartsWithFilter.prototype.toString = function (): string {
+ return "(STARTS WITH " + this.prefix + (this.filter ? (" AND " + this.filter) : "") + ")"
+ };
+
+ RegExpKotlinTestsFilter.prototype.toString = function (): string {
+ return "(REGEXP " + this.regexp + ")"
+ };
+
+ ExactFilter.prototype.toString = function (): string {
+ return "(EXACT " + this.fqn + ")"
+ };
+
+ CompositeTestFilter.prototype.toString = function (): string {
+ return this.include.map(it => it.toString()).join(" OR ")
+ + "\n NOT (" + this.exclude.map(it => it.toString()).join(" OR ") + ")"
+ };
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/TeamCityMessagesFlow.ts b/libraries/tools/kotlin-test-nodejs-runner/src/TeamCityMessagesFlow.ts
new file mode 100644
index 00000000000..fdeb9d7248d
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/TeamCityMessagesFlow.ts
@@ -0,0 +1,23 @@
+import {dateTimeWithoutTimeZone, newFlowId, tcEscape} from "./utils"
+
+export type TeamCityMessageData = { [key: string]: any }
+
+export class TeamCityMessagesFlow {
+ public readonly id: number;
+
+ constructor(id: number | null, private readonly send: (payload: string) => void) {
+ this.id = id || newFlowId()
+ }
+
+ sendMessage(type: string, args: TeamCityMessageData) {
+ args['flowId'] = this.id;
+ args['timestamp'] = dateTimeWithoutTimeZone();
+
+ const serializedArgs = Object
+ .keys(args)
+ .map((key) => `${key}='${tcEscape(args[key])}'`)
+ .join(' ');
+
+ this.send(`##teamcity[${type} ${serializedArgs}]`)
+ }
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/Timer.ts b/libraries/tools/kotlin-test-nodejs-runner/src/Timer.ts
new file mode 100644
index 00000000000..fd577604ed7
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/Timer.ts
@@ -0,0 +1,16 @@
+export interface Timer {
+ start(): T
+
+ end(start: T): number
+}
+
+export const hrTimer: Timer<[number, number]> = {
+ start(): [number, number] {
+ return process.hrtime();
+ },
+ end(start: [number, number]): number {
+ const elapsedHr = process.hrtime(start);
+ return elapsedHr[0] + (elapsedHr[1] / 1e6); // ns to ms
+ }
+};
+
diff --git a/libraries/tools/kotlin-test-nodejs-runner/src/utils.ts b/libraries/tools/kotlin-test-nodejs-runner/src/utils.ts
new file mode 100644
index 00000000000..1714c914b21
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/src/utils.ts
@@ -0,0 +1,96 @@
+
+/**
+ * from teamcity-service-messages
+ * Copyright (c) 2013 Aaron Forsander
+ *
+ * Escape string for TeamCity output.
+ * @see https://confluence.jetbrains.com/display/TCD65/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-servMsgsServiceMessages
+ */
+export function tcEscape(str: string): string {
+ if (!str) {
+ return '';
+ }
+
+ return str
+ .toString()
+ .replace(/\|/g, '||')
+ .replace(/\n/g, '|n')
+ .replace(/\r/g, '|r')
+ .replace(/\[/g, '|[')
+ .replace(/\]/g, '|]')
+ .replace(/\u0085/g, '|x') // next line
+ .replace(/\u2028/g, '|l') // line separator
+ .replace(/\u2029/g, '|p') // paragraph separator
+ .replace(/'/g, '|\'');
+}
+
+/**
+ * From teamcity-service-messages.
+ * Copyright 2013 Aaron Forsander
+ */
+export function newFlowId(): number {
+ return Math.floor(Math.random() * (1e10 - 1e6 + 1)) + 1e6;
+}
+
+/**
+ * From teamcity-service-messages.
+ * Copyright 2013 Aaron Forsander
+ */
+export function dateTimeWithoutTimeZone(): string {
+ // TeamCity not fully support ISO 8601 (see TW-36173) so we need to cut off 'Z' at the end.
+ return new Date().toISOString().slice(0, -1);
+}
+
+/**
+ * From lodash.
+ * Copyright JS Foundation and other contributors
+ */
+export function startsWith(string: string, target: string) {
+ return string.slice(0, target.length) == target;
+}
+
+/**
+ * From lodash.
+ * Copyright JS Foundation and other contributors
+ */
+export function trim(str: string): string {
+ return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
+}
+
+/**
+ * Used to match `RegExp`
+ * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+ */
+const reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+ reHasRegExpChar = RegExp(reRegExpChar.source);
+
+/**
+ * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+ * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+ *
+ * From lodash.
+ * Copyright JS Foundation and other contributors
+ */
+export function escapeRegExp(string: string) {
+ return (string && reHasRegExpChar.test(string))
+ ? string.replace(reRegExpChar, '\\$&')
+ : string;
+}
+
+export function pushIfNotNull(list: T[], value: T) {
+ if (value !== null) list.push(value)
+}
+
+export function flatMap(arr: T[], f: (item: T) => T[]): T[] {
+ const result: T[] = [];
+ arr.forEach(item => {
+ f(item).forEach(x => {
+ result.push(x)
+ })
+ });
+ return result;
+}
+
+export function println(message ?: string) {
+ console.log(message)
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/tsconfig.json b/libraries/tools/kotlin-test-nodejs-runner/tsconfig.json
new file mode 100644
index 00000000000..19433724709
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "ESNext",
+// "declaration": true,
+// "outDir": "./lib",
+ "strict": true,
+// "sourceMap": true,
+ "allowSyntheticDefaultImports": true,
+ "inlineSourceMap": true
+ },
+ "exclude": [
+ "**/*Debug.ts"
+ ]
+}
\ No newline at end of file
diff --git a/libraries/tools/kotlin-test-nodejs-runner/yarn.lock b/libraries/tools/kotlin-test-nodejs-runner/yarn.lock
new file mode 100644
index 00000000000..e0db42584cc
--- /dev/null
+++ b/libraries/tools/kotlin-test-nodejs-runner/yarn.lock
@@ -0,0 +1,1073 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
+ integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
+ dependencies:
+ "@babel/highlight" "^7.0.0"
+
+"@babel/highlight@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
+ integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==
+ dependencies:
+ chalk "^2.0.0"
+ esutils "^2.0.2"
+ js-tokens "^4.0.0"
+
+"@types/estree@0.0.39":
+ version "0.0.39"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+ integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+
+"@types/node@*", "@types/node@^10.12.21":
+ version "10.12.21"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e"
+ integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ==
+
+acorn@^6.0.5:
+ version "6.0.6"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.6.tgz#cd75181670d5b99bdb1b1c993941d3a239ab1f56"
+ integrity sha512-5M3G/A4uBSMIlfJ+h9W125vJvPFH/zirISsW5qfxF5YzEvXJCtolLoQvM5yZft0DvMcUrPGKPOlgEu55I6iUtA==
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+ integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
+
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+ integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+arr-diff@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
+ integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=
+ dependencies:
+ arr-flatten "^1.0.1"
+
+arr-flatten@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
+ integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
+
+array-unique@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+ integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=
+
+atob@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
+ integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+braces@^1.8.2:
+ version "1.8.5"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
+ integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=
+ dependencies:
+ expand-range "^1.8.1"
+ preserve "^0.2.0"
+ repeat-element "^1.1.2"
+
+builtin-modules@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.0.0.tgz#1e587d44b006620d90286cc7a9238bbc6129cab1"
+ integrity sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg==
+
+camelcase@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+ integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
+
+chalk@^2.0.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+cliui@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
+ integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==
+ dependencies:
+ string-width "^2.1.1"
+ strip-ansi "^4.0.0"
+ wrap-ansi "^2.0.0"
+
+code-point-at@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+ integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
+commander@~2.17.1:
+ version "2.17.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
+ integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+copyfiles@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.1.0.tgz#0e2a4188162d6b2f3c5adfe34e9c0bd564d23164"
+ integrity sha512-cAeDE0vL/koE9WSEGxqPpSyvU638Kgfu6wfrnj7kqp9FWa1CWsU54Coo6sdYZP4GstWa39tL/wIVJWfXcujgNA==
+ dependencies:
+ glob "^7.0.5"
+ minimatch "^3.0.3"
+ mkdirp "^0.5.1"
+ noms "0.0.0"
+ through2 "^2.0.1"
+ yargs "^11.0.0"
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+ integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+decamelize@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+ integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+
+decode-uri-component@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
+ integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+estree-walker@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
+ integrity sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==
+
+esutils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
+ integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
+
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+expand-brackets@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
+ integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=
+ dependencies:
+ is-posix-bracket "^0.1.0"
+
+expand-range@^1.8.1:
+ version "1.8.2"
+ resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
+ integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=
+ dependencies:
+ fill-range "^2.1.0"
+
+extglob@^0.3.1:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
+ integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=
+ dependencies:
+ is-extglob "^1.0.0"
+
+filename-regex@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
+ integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=
+
+fill-range@^2.1.0:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
+ integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==
+ dependencies:
+ is-number "^2.1.0"
+ isobject "^2.0.0"
+ randomatic "^3.0.0"
+ repeat-element "^1.1.2"
+ repeat-string "^1.5.2"
+
+find-up@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
+ integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
+ dependencies:
+ locate-path "^2.0.0"
+
+for-in@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+ integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
+
+for-own@^0.1.4:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
+ integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=
+ dependencies:
+ for-in "^1.0.1"
+
+fs-extra@7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+ integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+ dependencies:
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+get-caller-file@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
+ integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+ integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+
+glob-base@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
+ integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=
+ dependencies:
+ glob-parent "^2.0.0"
+ is-glob "^2.0.0"
+
+glob-parent@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
+ integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=
+ dependencies:
+ is-glob "^2.0.0"
+
+glob@^7.0.5, glob@^7.1.3:
+ version "7.1.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
+ integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+ version "4.1.15"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
+ integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+invert-kv@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
+ integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
+
+is-buffer@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+ integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+
+is-dotfile@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
+ integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=
+
+is-equal-shallow@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
+ integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=
+ dependencies:
+ is-primitive "^2.0.0"
+
+is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+ integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=
+
+is-extglob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
+ integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=
+
+is-fullwidth-code-point@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+ integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
+ dependencies:
+ number-is-nan "^1.0.0"
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+ integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+
+is-glob@^2.0.0, is-glob@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
+ integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=
+ dependencies:
+ is-extglob "^1.0.0"
+
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+ integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
+
+is-number@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
+ integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=
+ dependencies:
+ kind-of "^3.0.2"
+
+is-number@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
+ integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==
+
+is-posix-bracket@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
+ integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=
+
+is-primitive@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
+ integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU=
+
+is-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+ integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+ integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+
+isarray@1.0.0, isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
+ dependencies:
+ isarray "1.0.0"
+
+jest-worker@^24.0.0:
+ version "24.0.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0.tgz#3d3483b077bf04f412f47654a27bba7e947f8b6d"
+ integrity sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==
+ dependencies:
+ merge-stream "^1.0.1"
+ supports-color "^6.1.0"
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+kind-of@^3.0.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
+ integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^6.0.0:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
+ integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
+
+lcid@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
+ integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
+ dependencies:
+ invert-kv "^1.0.0"
+
+locate-path@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
+ integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
+ dependencies:
+ p-locate "^2.0.0"
+ path-exists "^3.0.0"
+
+lru-cache@^4.0.1:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+magic-string@^0.25.1:
+ version "0.25.2"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.2.tgz#139c3a729515ec55e96e69e82a11fe890a293ad9"
+ integrity sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==
+ dependencies:
+ sourcemap-codec "^1.4.4"
+
+math-random@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"
+ integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==
+
+mem@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
+ integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
+ dependencies:
+ mimic-fn "^1.0.0"
+
+merge-stream@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1"
+ integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=
+ dependencies:
+ readable-stream "^2.0.1"
+
+micromatch@^2.3.11:
+ version "2.3.11"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
+ integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=
+ dependencies:
+ arr-diff "^2.0.0"
+ array-unique "^0.2.1"
+ braces "^1.8.2"
+ expand-brackets "^0.1.4"
+ extglob "^0.3.1"
+ filename-regex "^2.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.1"
+ kind-of "^3.0.2"
+ normalize-path "^2.0.1"
+ object.omit "^2.0.0"
+ parse-glob "^3.0.4"
+ regex-cache "^0.4.2"
+
+mimic-fn@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
+ integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
+
+minimatch@^3.0.3, minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+ integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+
+mkdirp@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
+ dependencies:
+ minimist "0.0.8"
+
+noms@0.0.0:
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
+ integrity sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=
+ dependencies:
+ inherits "^2.0.1"
+ readable-stream "~1.0.31"
+
+normalize-path@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+ integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
+ dependencies:
+ remove-trailing-separator "^1.0.1"
+
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
+ dependencies:
+ path-key "^2.0.0"
+
+number-is-nan@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+ integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+
+object.omit@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
+ integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=
+ dependencies:
+ for-own "^0.1.4"
+ is-extendable "^0.1.1"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+os-locale@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
+ integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
+ dependencies:
+ execa "^0.7.0"
+ lcid "^1.0.0"
+ mem "^1.1.0"
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+ integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+
+p-limit@^1.1.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
+ integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
+ dependencies:
+ p-try "^1.0.0"
+
+p-locate@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
+ integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
+ dependencies:
+ p-limit "^1.1.0"
+
+p-try@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
+ integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
+
+parse-glob@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
+ integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw=
+ dependencies:
+ glob-base "^0.3.0"
+ is-dotfile "^1.0.0"
+ is-extglob "^1.0.0"
+ is-glob "^2.0.0"
+
+path-exists@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
+ integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-key@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+ integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
+
+path-parse@^1.0.5, path-parse@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+ integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+
+preserve@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+ integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
+
+process-nextick-args@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
+ integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+ integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
+
+randomatic@^3.0.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
+ integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==
+ dependencies:
+ is-number "^4.0.0"
+ kind-of "^6.0.0"
+ math-random "^1.0.1"
+
+readable-stream@^2.0.1, readable-stream@~2.3.6:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
+ integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readable-stream@~1.0.31:
+ version "1.0.34"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+ integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+regex-cache@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
+ integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==
+ dependencies:
+ is-equal-shallow "^0.1.3"
+
+remove-trailing-separator@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
+ integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
+
+repeat-element@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+ integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
+
+repeat-string@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+ integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+
+require-main-filename@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+ integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
+
+resolve-url@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
+ integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
+
+resolve@1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
+ integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
+ dependencies:
+ path-parse "^1.0.5"
+
+resolve@^1.8.1:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
+ integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
+ dependencies:
+ path-parse "^1.0.6"
+
+rimraf@^2.6.3:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
+ integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
+ dependencies:
+ glob "^7.1.3"
+
+rollup-plugin-commonjs@^9.2.0:
+ version "9.2.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.0.tgz#4604e25069e0c78a09e08faa95dc32dec27f7c89"
+ integrity sha512-0RM5U4Vd6iHjL6rLvr3lKBwnPsaVml+qxOGaaNUWN1lSq6S33KhITOfHmvxV3z2vy9Mk4t0g4rNlVaJJsNQPWA==
+ dependencies:
+ estree-walker "^0.5.2"
+ magic-string "^0.25.1"
+ resolve "^1.8.1"
+ rollup-pluginutils "^2.3.3"
+
+rollup-plugin-node-resolve@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.0.tgz#9bc6b8205e9936cc0e26bba2415f1ecf1e64d9b2"
+ integrity sha512-7Ni+/M5RPSUBfUaP9alwYQiIKnKeXCOHiqBpKUl9kwp3jX5ZJtgXAait1cne6pGEVUUztPD6skIKH9Kq9sNtfw==
+ dependencies:
+ builtin-modules "^3.0.0"
+ is-module "^1.0.0"
+ resolve "^1.8.1"
+
+rollup-plugin-sourcemaps@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.4.2.tgz#62125aa94087aadf7b83ef4dfaf629b473135e87"
+ integrity sha1-YhJaqUCHqt97g+9N+vYptHMTXoc=
+ dependencies:
+ rollup-pluginutils "^2.0.1"
+ source-map-resolve "^0.5.0"
+
+rollup-plugin-typescript2@^0.19.2:
+ version "0.19.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.19.2.tgz#87d9c799cd6e02efbedbba25af12753a1e92b6c2"
+ integrity sha512-DRG7SaYX0QzBIz6rII5nm1UkiceS95r8mJjujugybyIueNF3auvzGTHMK62O7As/0q5RHjXsOguWOUv+KJKLFA==
+ dependencies:
+ fs-extra "7.0.1"
+ resolve "1.8.1"
+ rollup-pluginutils "2.3.3"
+ tslib "1.9.3"
+
+rollup-plugin-uglify@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.2.tgz#681042cfdf7ea4e514971946344e1a95bc2772fe"
+ integrity sha512-qwz2Tryspn5QGtPUowq5oumKSxANKdrnfz7C0jm4lKxvRDsNe/hSGsB9FntUul7UeC4TsZEWKErVgE1qWSO0gw==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ jest-worker "^24.0.0"
+ serialize-javascript "^1.6.1"
+ uglify-js "^3.4.9"
+
+rollup-pluginutils@2.3.3, rollup-pluginutils@^2.0.1, rollup-pluginutils@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794"
+ integrity sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==
+ dependencies:
+ estree-walker "^0.5.2"
+ micromatch "^2.3.11"
+
+rollup@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.1.2.tgz#8d094b85683b810d0c05a16bd7618cf70d48eba7"
+ integrity sha512-OkdMxqMl8pWoQc5D8y1cIinYQPPLV8ZkfLgCzL6SytXeNA2P7UHynEQXI9tYxuAjAMsSyvRaWnyJDLHMxq0XAg==
+ dependencies:
+ "@types/estree" "0.0.39"
+ "@types/node" "*"
+ acorn "^6.0.5"
+
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+serialize-javascript@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879"
+ integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==
+
+set-blocking@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+ integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+ integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+
+signal-exit@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
+ integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+
+source-map-resolve@^0.5.0:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
+ integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==
+ dependencies:
+ atob "^2.1.1"
+ decode-uri-component "^0.2.0"
+ resolve-url "^0.2.1"
+ source-map-url "^0.4.0"
+ urix "^0.1.0"
+
+source-map-url@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
+ integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
+
+source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+sourcemap-codec@^1.4.4:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f"
+ integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==
+
+string-width@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+ integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ strip-ansi "^3.0.0"
+
+string-width@^2.0.0, string-width@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+ integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
+ dependencies:
+ ansi-regex "^2.0.0"
+
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+ dependencies:
+ ansi-regex "^3.0.0"
+
+strip-eof@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+ integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+supports-color@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
+ integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
+ dependencies:
+ has-flag "^3.0.0"
+
+through2@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+ integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
+ dependencies:
+ readable-stream "~2.3.6"
+ xtend "~4.0.1"
+
+tslib@1.9.3:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
+ integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
+
+typescript@^3.3.1:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b"
+ integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA==
+
+uglify-js@^3.4.9:
+ version "3.4.9"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
+ integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==
+ dependencies:
+ commander "~2.17.1"
+ source-map "~0.6.1"
+
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
+urix@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
+ integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+ integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
+
+which@^1.2.9:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+wrap-ansi@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
+ integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+xtend@~4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
+ integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
+
+y18n@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
+ integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
+
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+ integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+
+yargs-parser@^9.0.2:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
+ integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=
+ dependencies:
+ camelcase "^4.1.0"
+
+yargs@^11.0.0:
+ version "11.1.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77"
+ integrity sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==
+ dependencies:
+ cliui "^4.0.0"
+ decamelize "^1.1.1"
+ find-up "^2.1.0"
+ get-caller-file "^1.0.1"
+ os-locale "^2.0.0"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^2.0.0"
+ which-module "^2.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^9.0.2"
diff --git a/settings.gradle b/settings.gradle
index 37de5db713e..bb40211b124 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -122,6 +122,7 @@ include ":kotlin-build-common",
":kotlin-test:kotlin-test-testng",
":kotlin-test:kotlin-test-js",
":kotlin-test:kotlin-test-js:kotlin-test-js-it",
+ ":kotlin-test-nodejs-runner",
":kotlinx-metadata",
":kotlinx-metadata-jvm",
":prepare:build.version",
@@ -263,6 +264,7 @@ project(':kotlin-test:kotlin-test-junit5').projectDir = "$rootDir/libraries/kotl
project(':kotlin-test:kotlin-test-testng').projectDir = "$rootDir/libraries/kotlin.test/testng" as File
project(':kotlin-test:kotlin-test-js').projectDir = "$rootDir/libraries/kotlin.test/js" as File
project(':kotlin-test:kotlin-test-js:kotlin-test-js-it').projectDir = "$rootDir/libraries/kotlin.test/js/it" as File
+project(':kotlin-test-nodejs-runner').projectDir = "$rootDir/libraries/tools/kotlin-test-nodejs-runner" as File
project(':kotlin-reflect').projectDir = "$rootDir/libraries/reflect" as File
project(':kotlin-reflect-api').projectDir = "$rootDir/libraries/reflect/api" as File
project(':kotlinx-metadata').projectDir = "$rootDir/libraries/kotlinx-metadata" as File