[K/JS] Rework kotlin tests compilation to make it works with per-file granularity ^KT-61525 Fixed
This commit is contained in:
+186
@@ -0,0 +1,186 @@
|
||||
import kotlin.test.FrameworkAdapter
|
||||
import kotlin.collections.*
|
||||
|
||||
private var sortingContext = SortingContext()
|
||||
|
||||
private var bodyContext: TestBodyContext? = null
|
||||
|
||||
fun call(name: String) = bodyContext!!.call(name)
|
||||
|
||||
fun raise(name: String): Nothing {
|
||||
bodyContext!!.raised(name)
|
||||
throw Exception(name)
|
||||
}
|
||||
|
||||
// Adapter should be initialized eagerly
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "DEPRECATION")
|
||||
@OptIn(kotlin.ExperimentalStdlibApi::class)
|
||||
@EagerInitialization
|
||||
private val underscore = kotlin.test.setAdapter(object : FrameworkAdapter {
|
||||
override fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit) {
|
||||
sortingContext.suite(name, ignored) { suiteFn() }
|
||||
}
|
||||
|
||||
override fun test(name: String, ignored: Boolean, testFn: () -> Any?) {
|
||||
sortingContext.test(name, ignored) { returned(testFn()) }
|
||||
}
|
||||
})
|
||||
|
||||
interface SuiteContext {
|
||||
fun suite(name: String, ignored: Boolean = false, body: SuiteContext.() -> Unit)
|
||||
|
||||
fun test(name: String, ignored: Boolean = false, body: TestBodyContext.() -> Unit = {})
|
||||
}
|
||||
|
||||
|
||||
interface TestBodyContext {
|
||||
fun call(name: String)
|
||||
|
||||
fun raised(msg: String)
|
||||
|
||||
fun caught(msg: String)
|
||||
|
||||
fun returned(msg: Any?)
|
||||
}
|
||||
|
||||
private sealed class Entity(val name: String,
|
||||
val ignored: Boolean)
|
||||
|
||||
private class Suite(name: String, ignored: Boolean, val body: SuiteContext.() -> Unit): Entity(name, ignored)
|
||||
|
||||
private class Test(name: String, ignored: Boolean, val body: TestBodyContext.() -> Unit): Entity(name, ignored)
|
||||
|
||||
|
||||
private class SortingContext: SuiteContext {
|
||||
|
||||
val structure = mutableListOf<Entity>()
|
||||
|
||||
override fun suite(name: String, ignored: Boolean, body: SuiteContext.() -> Unit) {
|
||||
structure += Suite(name, ignored, body)
|
||||
}
|
||||
|
||||
override fun test(name: String, ignored: Boolean, body: TestBodyContext.() -> Unit) {
|
||||
structure += Test(name, ignored, body)
|
||||
}
|
||||
|
||||
fun <T: SuiteContext> replayInto(context: T): T {
|
||||
structure.sortedBy { it.name }.forEach {
|
||||
when (it) {
|
||||
is Suite -> context.suite(it.name, it.ignored) {
|
||||
val oldSorter = sortingContext
|
||||
|
||||
sortingContext = SortingContext()
|
||||
it.body(sortingContext)
|
||||
sortingContext.replayInto(this)
|
||||
|
||||
sortingContext = oldSorter
|
||||
}
|
||||
is Test -> context.test(it.name, it.ignored) {
|
||||
bodyContext = this
|
||||
it.body(this)
|
||||
bodyContext = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingContext : SuiteContext, TestBodyContext{
|
||||
val log: String
|
||||
get() = logHead + (lastRecord ?: "")
|
||||
|
||||
private var indentation = ""
|
||||
|
||||
override fun suite(name: String, ignored: Boolean, body: SuiteContext.() -> Unit) = indent {
|
||||
record("suite(\"$name\"${optionalIgnore(ignored)}) {")
|
||||
runSafely { this.body() }
|
||||
record("}")
|
||||
}
|
||||
|
||||
override fun test(name: String, ignored: Boolean, body: TestBodyContext.() -> Unit) = indent {
|
||||
val num = record("test(\"$name\"${optionalIgnore(ignored)}) {")
|
||||
|
||||
runSafely { this.body() }
|
||||
|
||||
if (!writtenSince(num)) {
|
||||
record("test(\"$name\"${optionalIgnore(ignored)})", replaceLast = true)
|
||||
}
|
||||
else {
|
||||
record("}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun call(name: String) = indent {
|
||||
record("call(\"$name\")")
|
||||
}
|
||||
|
||||
override fun raised(msg: String) = indent {
|
||||
record("raised(\"$msg\")")
|
||||
}
|
||||
|
||||
override fun caught(msg: String) = indent {
|
||||
record("caught(\"$msg\")")
|
||||
}
|
||||
|
||||
override fun returned(msg: Any?) = indent {
|
||||
if (msg is String) record("returned(\"$msg\")")
|
||||
}
|
||||
|
||||
private fun runSafely(body: () -> Unit) {
|
||||
try {
|
||||
body()
|
||||
}
|
||||
catch (t: Throwable) {
|
||||
caught(t.message ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
private fun indent(body: () -> Unit) {
|
||||
val prevIndentation = indentation
|
||||
indentation += " "
|
||||
body()
|
||||
indentation = prevIndentation
|
||||
}
|
||||
|
||||
|
||||
private var logHead: String = ""
|
||||
private var lastRecord: String? = null
|
||||
private var counter = 0
|
||||
|
||||
private fun writtenSince(num: Int) = counter > num
|
||||
|
||||
private fun record(s: String, replaceLast: Boolean = false): Int {
|
||||
if (!replaceLast && lastRecord != null) {
|
||||
logHead += lastRecord
|
||||
}
|
||||
|
||||
lastRecord = indentation + s + "\n"
|
||||
|
||||
return ++counter
|
||||
}
|
||||
|
||||
private fun optionalIgnore(ignored: Boolean) = if (ignored) ", true" else ""
|
||||
}
|
||||
|
||||
fun checkLog(wrapInEmptySuite: Boolean = true, body: SuiteContext.() -> Unit): String {
|
||||
val expectedContext = SortingContext()
|
||||
if (wrapInEmptySuite) {
|
||||
expectedContext.suite("") {
|
||||
body()
|
||||
}
|
||||
} else {
|
||||
expectedContext.body()
|
||||
}
|
||||
|
||||
val expectedLog = expectedContext.replayInto(LoggingContext()).log
|
||||
val actualLog = sortingContext.replayInto(LoggingContext()).log
|
||||
|
||||
if (actualLog != expectedLog) {
|
||||
return "Failed test structure check. Expected: \"${expectedLog}\"; actual: \"${actualLog}\"."
|
||||
}
|
||||
else {
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
fun box(stepId: Int) = when (stepId) {
|
||||
0 -> "OK"
|
||||
1 -> checkLog {
|
||||
suite("Test1") {
|
||||
test("foo") {
|
||||
call("before")
|
||||
call("foo")
|
||||
call("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
2 -> checkLog {
|
||||
suite("Test1") {
|
||||
test("foo") {
|
||||
call("before")
|
||||
call("foo")
|
||||
call("after")
|
||||
}
|
||||
test("withException") {
|
||||
call("before")
|
||||
call("withException")
|
||||
raised("some exception")
|
||||
call("after")
|
||||
caught("some exception")
|
||||
}
|
||||
}
|
||||
}
|
||||
3 -> checkLog {
|
||||
suite("Test1") {
|
||||
test("foo") {
|
||||
call("before")
|
||||
call("foo")
|
||||
call("after")
|
||||
}
|
||||
test("withException") {
|
||||
call("before")
|
||||
call("withException")
|
||||
raised("some exception")
|
||||
call("after")
|
||||
caught("some exception")
|
||||
}
|
||||
}
|
||||
suite("Test2") {
|
||||
test("foo") {
|
||||
call("before")
|
||||
call("foo")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> "Fail: unexpected step $stepId"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
STEP 0:
|
||||
added file: m.kt, common.kt
|
||||
STEP 1:
|
||||
modifications:
|
||||
U : test1.1.kt -> test1.kt
|
||||
added file: test1.kt
|
||||
updated exports: common.kt
|
||||
STEP 2:
|
||||
modifications:
|
||||
U : test1.2.kt -> test1.kt
|
||||
modified ir: test1.kt
|
||||
updated exports: common.kt
|
||||
STEP 3:
|
||||
modifications:
|
||||
U : test2.3.kt -> test2.kt
|
||||
added file: test2.kt
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.AfterTest
|
||||
|
||||
class Test1 {
|
||||
@BeforeTest
|
||||
fun before() {
|
||||
call("before")
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun after() {
|
||||
call("after")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foo() {
|
||||
call("foo")
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.AfterTest
|
||||
|
||||
class Test1 {
|
||||
@BeforeTest
|
||||
fun before() {
|
||||
call("before")
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun after() {
|
||||
call("after")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foo() {
|
||||
call("foo")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withException() {
|
||||
call("withException")
|
||||
raise("some exception")
|
||||
call("never happens")
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.BeforeTest
|
||||
import kotlin.test.AfterTest
|
||||
|
||||
class Test2 {
|
||||
@BeforeTest
|
||||
fun before() {
|
||||
call("before")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun foo() {
|
||||
call("foo")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
MODULES: main
|
||||
|
||||
STEP 0:
|
||||
libs: main
|
||||
dirty js modules: main
|
||||
dirty js files: main/common, main/m, main/m.export, main
|
||||
STEP 1:
|
||||
libs: main
|
||||
dirty js modules: main
|
||||
dirty js files: main/test1, main/common, main
|
||||
STEP 2:
|
||||
libs: main
|
||||
dirty js modules: main
|
||||
dirty js files: main/test1, main/common
|
||||
STEP 3:
|
||||
libs: main
|
||||
dirty js modules: main
|
||||
dirty js files: main/test2, main
|
||||
Reference in New Issue
Block a user