Gradle: Basic Kotlin NodeJS tests runner
#KT-30531 Fixed #KT-30528 Fixed
This commit is contained in:
@@ -37,3 +37,6 @@ build/
|
||||
.idea/.name
|
||||
.idea/artifacts/dist_auto_*
|
||||
kotlin-ultimate/
|
||||
node_modules/
|
||||
.rpt2_cache/
|
||||
libraries/tools/kotlin-test-nodejs-runner/lib/
|
||||
@@ -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"))
|
||||
|
||||
+87
-1
@@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<results>\n" +
|
||||
files.joinToString("") {
|
||||
it.readText()
|
||||
.replace(projectDir.absolutePath, "/\$PROJECT_DIR$")
|
||||
.replace(projectDir.name, "\$PROJECT_NAME$")
|
||||
.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", "")
|
||||
} +
|
||||
"</results>"
|
||||
|
||||
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<out String> = arrayOf()): List<String> =
|
||||
params.toMutableList().apply {
|
||||
add("--stacktrace")
|
||||
|
||||
+79
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<results>
|
||||
<testsuite name="CommonTest" tests="3" skipped="1" failures="2" errors="0" timestamp="..." hostname="..." time="...">
|
||||
<properties />
|
||||
<testcase name="test1" classname="CommonTest" time="...">
|
||||
<failure message="..." type="org.jetbrains.kotlin.gradle.targets.js.NodeJsTestFailure">...</failure>
|
||||
</testcase>
|
||||
<testcase name="test2" classname="CommonTest" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<testcase name="test3" classname="CommonTest" time="...">
|
||||
<failure message="..." type="org.jetbrains.kotlin.gradle.targets.js.NodeJsTestFailure">...</failure>
|
||||
</testcase>
|
||||
<system-out />
|
||||
<system-err />
|
||||
</testsuite>
|
||||
<testsuite name="InnerIgnored" tests="3" skipped="3" failures="0" errors="0" timestamp="..." hostname="..." time="...">
|
||||
<properties />
|
||||
<testcase name="test4" classname="InnerIgnored" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<testcase name="test5" classname="InnerIgnored" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<testcase name="test6" classname="InnerIgnored" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<system-out />
|
||||
<system-err />
|
||||
</testsuite>
|
||||
</results>
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<results>
|
||||
<testsuite name="CommonTest" tests="3" skipped="1" failures="1" errors="0" timestamp="..." hostname="..." time="...">
|
||||
<properties />
|
||||
<testcase name="test1" classname="CommonTest" time="..." />
|
||||
<testcase name="test2" classname="CommonTest" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<testcase name="test3" classname="CommonTest" time="...">
|
||||
<failure message="..." type="org.jetbrains.kotlin.gradle.targets.js.NodeJsTestFailure">...</failure>
|
||||
</testcase>
|
||||
<system-out />
|
||||
<system-err />
|
||||
</testsuite>
|
||||
<testsuite name="InnerIgnored" tests="3" skipped="3" failures="0" errors="0" timestamp="..." hostname="..." time="...">
|
||||
<properties />
|
||||
<testcase name="test4" classname="InnerIgnored" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<testcase name="test5" classname="InnerIgnored" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<testcase name="test6" classname="InnerIgnored" time="...">
|
||||
<skipped />
|
||||
</testcase>
|
||||
<system-out />
|
||||
<system-err />
|
||||
</testsuite>
|
||||
</results>
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import org.jetbrains.kotlin.gradle.targets.js.tasks.KotlinNodeJsTestTask
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform").version("<pluginMarkerVersion>")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "new-mpp-js-tests"
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun common() = 1
|
||||
+31
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<KotlinCompile> {
|
||||
kotlinOptions.jdkHome = rootProject.extra["JDK_18"] as String
|
||||
|
||||
+310
@@ -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 : Node> NodeType.open(contents: (NodeType) -> Unit) = open(System.currentTimeMillis()) {
|
||||
contents(it)
|
||||
System.currentTimeMillis()
|
||||
}
|
||||
|
||||
|
||||
private inline fun <NodeType : Node> 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 <NodeType : Node> 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<Node> {
|
||||
var i = leaf
|
||||
val items = mutableListOf<Node>()
|
||||
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")
|
||||
}
|
||||
+86
@@ -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<String>,
|
||||
val clientSettings: TCServiceMessagesClientSettings
|
||||
) : TestExecutionSpec
|
||||
|
||||
private val log = LoggerFactory.getLogger("org.jetbrains.kotlin.gradle.tasks.testing")
|
||||
|
||||
class TCServiceMessagesTestExecutor(
|
||||
val execHandleFactory: ExecHandleFactory,
|
||||
val buildOperationExecutor: BuildOperationExecutor
|
||||
) : TestExecuter<TCServiceMessagesTestExecutionSpec> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
+118
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-62
@@ -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<KotlinJsCompilation>(true, true, kotlinPluginVersion) {
|
||||
KotlinTargetConfigurator<KotlinJsCompilation>(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<KotlinJsCompilation>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -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 ?: ""
|
||||
}
|
||||
+17
@@ -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"
|
||||
}
|
||||
+68
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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<Project> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
@@ -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<String>
|
||||
@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"
|
||||
}
|
||||
}
|
||||
+50
@@ -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")
|
||||
}
|
||||
+67
@@ -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<String>,
|
||||
val include: Collection<String> = listOf(),
|
||||
val exclude: Collection<String> = listOf(),
|
||||
val ignoredTestSuites: IgnoredTestSuitesReporting = IgnoredTestSuitesReporting.reportAllInnerTestsAsIgnored
|
||||
) {
|
||||
fun toList(): List<String> = mutableListOf<String>().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
|
||||
}
|
||||
}
|
||||
|
||||
+125
@@ -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<String>()
|
||||
|
||||
@InputDirectory
|
||||
var nodeModulesDir: File? = null
|
||||
|
||||
@Input
|
||||
@SkipWhenEmpty
|
||||
var nodeModulesToLoad: Set<String> = 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<String, Any>.addPath(key: String, path: String) {
|
||||
val prev = get(key)
|
||||
if (prev == null) set(key, path)
|
||||
else set(key, prev as String + File.pathSeparator + path)
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
|
||||
+11
@@ -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");
|
||||
+1
@@ -0,0 +1 @@
|
||||
implementation-class=org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
|
||||
+40
@@ -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")
|
||||
}
|
||||
}
|
||||
+116
@@ -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(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+197
@@ -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"))
|
||||
}
|
||||
}
|
||||
+66
@@ -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(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -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(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -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")
|
||||
)
|
||||
}
|
||||
}
|
||||
+65
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
Kotlin/JS TeamCity tests results reporter.
|
||||
|
||||
### Usage
|
||||
|
||||
`yarn add @kotlin/js-tests-teamcity --dev`
|
||||
`yarn run kotlin-js-tests`
|
||||
@@ -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<YarnTask>("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<Delete>("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")
|
||||
}
|
||||
}
|
||||
+65
@@ -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] <module_name1>, <module_name2>, ..",
|
||||
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);
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
declare const DEBUG: boolean;
|
||||
declare const VERSION: string;
|
||||
declare const BIN: string;
|
||||
declare const DESCRIPTION: string;
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
};
|
||||
@@ -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<any> | 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 ") + ")"
|
||||
};
|
||||
}
|
||||
@@ -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}]`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface Timer<T> {
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 <https://js.foundation/>
|
||||
*/
|
||||
export function startsWith(string: string, target: string) {
|
||||
return string.slice(0, target.length) == target;
|
||||
}
|
||||
|
||||
/**
|
||||
* From lodash.
|
||||
* Copyright JS Foundation and other contributors <https://js.foundation/>
|
||||
*/
|
||||
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 <https://js.foundation/>
|
||||
*/
|
||||
export function escapeRegExp(string: string) {
|
||||
return (string && reHasRegExpChar.test(string))
|
||||
? string.replace(reRegExpChar, '\\$&')
|
||||
: string;
|
||||
}
|
||||
|
||||
export function pushIfNotNull<T>(list: T[], value: T) {
|
||||
if (value !== null) list.push(value)
|
||||
}
|
||||
|
||||
export function flatMap<T>(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)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "ESNext",
|
||||
// "declaration": true,
|
||||
// "outDir": "./lib",
|
||||
"strict": true,
|
||||
// "sourceMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"inlineSourceMap": true
|
||||
},
|
||||
"exclude": [
|
||||
"**/*Debug.ts"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user