[K/JS] Rework kotlin tests compilation to make it works with per-file granularity ^KT-61525 Fixed

This commit is contained in:
Artem Kobzar
2023-11-16 19:02:04 +00:00
committed by Space Team
parent a4b3b08e59
commit ff50d40b32
48 changed files with 2290 additions and 135 deletions
+3
View File
@@ -269,6 +269,9 @@ fun Test.setUpJsBoxTests(jsEnabled: Boolean, jsIrEnabled: Boolean, firEnabled: B
dependsOn(":kotlin-test:kotlin-test-js-ir:compileKotlinJs")
systemProperty("kotlin.js.kotlin.test.path", "libraries/kotlin.test/js-ir/build/classes/kotlin/js/main")
inputs.dir(rootDir.resolve("libraries/kotlin.test/js-ir/build/classes/kotlin/js/main"))
systemProperty("kotlin.js.kotlin.test.klib.path", "libraries/kotlin.test/js-ir/build/libs/kotlin-test-js-$version.klib")
inputs.file(rootDir.resolve("libraries/kotlin.test/js-ir/build/libs/kotlin-test-js-$version.klib"))
}
exclude("org/jetbrains/kotlin/js/testOld/api/*")
@@ -61,9 +61,11 @@ abstract class AbstractInvalidationTest(
companion object {
private val OUT_DIR_PATH = System.getProperty("kotlin.js.test.root.out.dir") ?: error("'kotlin.js.test.root.out.dir' is not set")
private val STDLIB_KLIB = File(System.getProperty("kotlin.js.stdlib.klib.path") ?: error("Please set stdlib path")).canonicalPath
private val KOTLIN_TEST_KLIB = File(System.getProperty("kotlin.js.kotlin.test.klib.path") ?: error("Please set kotlin.test path")).canonicalPath
private const val BOX_FUNCTION_NAME = "box"
private const val STDLIB_MODULE_NAME = "kotlin-kotlin-stdlib"
private const val KOTLIN_TEST_MODULE_NAME = "kotlin-kotlin-test"
private val TEST_FILE_IGNORE_PATTERN = Regex("^.*\\..+\\.\\w\\w$")
@@ -202,7 +204,7 @@ abstract class AbstractInvalidationTest(
val friends = mutableListOf<File>()
if (moduleStep.rebuildKlib) {
val dependencies = mutableListOf(File(STDLIB_KLIB))
val dependencies = mutableListOf(File(STDLIB_KLIB), File(KOTLIN_TEST_KLIB))
for (dep in moduleStep.dependencies) {
val klibFile = resolveModuleArtifact(dep.moduleName, buildDir)
dependencies += klibFile
@@ -229,7 +231,7 @@ abstract class AbstractInvalidationTest(
}
private fun verifyCacheUpdateStats(stepId: Int, stats: KotlinSourceFileMap<EnumSet<DirtyFileState>>, testInfo: List<TestStepInfo>) {
val gotStats = stats.filter { it.key.path != STDLIB_KLIB }
val gotStats = stats.filter { it.key.path != STDLIB_KLIB && it.key.path != KOTLIN_TEST_KLIB }
val checkedLibs = mutableSetOf<KotlinLibraryFile>()
@@ -260,7 +262,7 @@ abstract class AbstractInvalidationTest(
}
private fun verifyJsExecutableProducerBuildModules(stepId: Int, gotRebuilt: List<String>, expectedRebuilt: List<String>) {
val got = gotRebuilt.filter { !it.startsWith(STDLIB_MODULE_NAME) }
val got = gotRebuilt.filter { !it.startsWith(STDLIB_MODULE_NAME) && !it.startsWith(KOTLIN_TEST_MODULE_NAME) }
JUnit4Assertions.assertSameElements(got, expectedRebuilt) {
"Mismatched rebuilt modules at step $stepId"
}
@@ -388,7 +390,7 @@ abstract class AbstractInvalidationTest(
val cacheUpdater = CacheUpdater(
mainModule = mainModuleInfo.modulePath,
allModules = testInfo.mapTo(mutableListOf(STDLIB_KLIB)) { it.modulePath },
allModules = testInfo.mapTo(mutableListOf(STDLIB_KLIB, KOTLIN_TEST_KLIB)) { it.modulePath },
mainModuleFriends = mainModuleInfo.friends,
cacheDir = buildDir.resolve("incremental-cache").absolutePath,
compilerConfiguration = configuration,
@@ -349,6 +349,12 @@ public class JsFirInvalidationPerFileTestGenerated extends AbstractJsFirInvalida
runTest("js/js.translator/testData/incremental/invalidation/jsModuleAnnotationOnObjectWithUsage/");
}
@Test
@TestMetadata("kotlinTest")
public void testKotlinTest() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/kotlinTest/");
}
@Test
@TestMetadata("languageVersionSettings")
public void testLanguageVersionSettings() throws Exception {
@@ -349,6 +349,12 @@ public class JsFirInvalidationPerModuleTestGenerated extends AbstractJsFirInvali
runTest("js/js.translator/testData/incremental/invalidation/jsModuleAnnotationOnObjectWithUsage/");
}
@Test
@TestMetadata("kotlinTest")
public void testKotlinTest() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/kotlinTest/");
}
@Test
@TestMetadata("languageVersionSettings")
public void testLanguageVersionSettings() throws Exception {
@@ -349,6 +349,12 @@ public class JsIrES6InvalidationPerFileTestGenerated extends AbstractJsIrES6Inva
runTest("js/js.translator/testData/incremental/invalidation/jsModuleAnnotationOnObjectWithUsage/");
}
@Test
@TestMetadata("kotlinTest")
public void testKotlinTest() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/kotlinTest/");
}
@Test
@TestMetadata("languageVersionSettings")
public void testLanguageVersionSettings() throws Exception {
@@ -349,6 +349,12 @@ public class JsIrES6InvalidationPerModuleTestGenerated extends AbstractJsIrES6In
runTest("js/js.translator/testData/incremental/invalidation/jsModuleAnnotationOnObjectWithUsage/");
}
@Test
@TestMetadata("kotlinTest")
public void testKotlinTest() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/kotlinTest/");
}
@Test
@TestMetadata("languageVersionSettings")
public void testLanguageVersionSettings() throws Exception {
@@ -349,6 +349,12 @@ public class JsIrInvalidationPerFileTestGenerated extends AbstractJsIrInvalidati
runTest("js/js.translator/testData/incremental/invalidation/jsModuleAnnotationOnObjectWithUsage/");
}
@Test
@TestMetadata("kotlinTest")
public void testKotlinTest() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/kotlinTest/");
}
@Test
@TestMetadata("languageVersionSettings")
public void testLanguageVersionSettings() throws Exception {
@@ -349,6 +349,12 @@ public class JsIrInvalidationPerModuleTestGenerated extends AbstractJsIrInvalida
runTest("js/js.translator/testData/incremental/invalidation/jsModuleAnnotationOnObjectWithUsage/");
}
@Test
@TestMetadata("kotlinTest")
public void testKotlinTest() throws Exception {
runTest("js/js.translator/testData/incremental/invalidation/kotlinTest/");
}
@Test
@TestMetadata("languageVersionSettings")
public void testLanguageVersionSettings() throws Exception {
@@ -2616,6 +2616,70 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
public class Kotlin_test {
@Test
public void testAllFilesPresentInKotlin_test() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/kotlin.test"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("beforeAfter.kt")
public void testBeforeAfter() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt");
}
@Test
@TestMetadata("ignore.kt")
public void testIgnore() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/ignore.kt");
}
@Test
@TestMetadata("illegalParameters.kt")
public void testIllegalParameters() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt");
}
@Test
@TestMetadata("incremental.kt")
public void testIncremental() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/incremental.kt");
}
@Test
@TestMetadata("inherited.kt")
public void testInherited() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/inherited.kt");
}
@Test
@TestMetadata("mpp.kt")
public void testMpp() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/mpp.kt");
}
@Test
@TestMetadata("nested.kt")
public void testNested() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/nested.kt");
}
@Test
@TestMetadata("returnTestResult.kt")
public void testReturnTestResult() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/simple.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/main")
@TestDataPath("$PROJECT_ROOT")
@@ -2722,6 +2722,70 @@ public class FirJsES6BoxTestGenerated extends AbstractFirJsES6BoxTest {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
public class Kotlin_test {
@Test
public void testAllFilesPresentInKotlin_test() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/kotlin.test"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@Test
@TestMetadata("beforeAfter.kt")
public void testBeforeAfter() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt");
}
@Test
@TestMetadata("ignore.kt")
public void testIgnore() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/ignore.kt");
}
@Test
@TestMetadata("illegalParameters.kt")
public void testIllegalParameters() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt");
}
@Test
@TestMetadata("incremental.kt")
public void testIncremental() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/incremental.kt");
}
@Test
@TestMetadata("inherited.kt")
public void testInherited() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/inherited.kt");
}
@Test
@TestMetadata("mpp.kt")
public void testMpp() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/mpp.kt");
}
@Test
@TestMetadata("nested.kt")
public void testNested() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/nested.kt");
}
@Test
@TestMetadata("returnTestResult.kt")
public void testReturnTestResult() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/simple.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/main")
@TestDataPath("$PROJECT_ROOT")
@@ -2722,6 +2722,70 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
public class Kotlin_test {
@Test
public void testAllFilesPresentInKotlin_test() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/kotlin.test"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
@Test
@TestMetadata("beforeAfter.kt")
public void testBeforeAfter() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt");
}
@Test
@TestMetadata("ignore.kt")
public void testIgnore() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/ignore.kt");
}
@Test
@TestMetadata("illegalParameters.kt")
public void testIllegalParameters() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt");
}
@Test
@TestMetadata("incremental.kt")
public void testIncremental() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/incremental.kt");
}
@Test
@TestMetadata("inherited.kt")
public void testInherited() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/inherited.kt");
}
@Test
@TestMetadata("mpp.kt")
public void testMpp() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/mpp.kt");
}
@Test
@TestMetadata("nested.kt")
public void testNested() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/nested.kt");
}
@Test
@TestMetadata("returnTestResult.kt")
public void testReturnTestResult() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/simple.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/main")
@TestDataPath("$PROJECT_ROOT")
@@ -2616,6 +2616,70 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
public class Kotlin_test {
@Test
public void testAllFilesPresentInKotlin_test() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/esModules/kotlin.test"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("beforeAfter.kt")
public void testBeforeAfter() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt");
}
@Test
@TestMetadata("ignore.kt")
public void testIgnore() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/ignore.kt");
}
@Test
@TestMetadata("illegalParameters.kt")
public void testIllegalParameters() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt");
}
@Test
@TestMetadata("incremental.kt")
public void testIncremental() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/incremental.kt");
}
@Test
@TestMetadata("inherited.kt")
public void testInherited() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/inherited.kt");
}
@Test
@TestMetadata("mpp.kt")
public void testMpp() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/mpp.kt");
}
@Test
@TestMetadata("nested.kt")
public void testNested() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/nested.kt");
}
@Test
@TestMetadata("returnTestResult.kt")
public void testReturnTestResult() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt");
}
@Test
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("js/js.translator/testData/box/esModules/kotlin.test/simple.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/esModules/main")
@TestDataPath("$PROJECT_ROOT")
@@ -0,0 +1,188 @@
package common
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")
@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,65 @@
// EXPECTED_REACHABLE_NODES: 1706
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import common.*
import kotlin.test.Test
import kotlin.test.BeforeTest
import kotlin.test.AfterTest
class Simple {
@BeforeTest
fun before() {
call("before")
}
@AfterTest
fun after() {
call("after")
}
@Test
fun foo() {
call("foo")
}
@Test
fun bar() {
call("bar")
}
@Test
fun withException() {
call("withException")
raise("some exception")
call("never happens")
}
}
// FILE: box.kt
import common.*
fun box() = checkLog {
suite("Simple") {
test("foo") {
call("before")
call("foo")
call("after")
}
test("bar") {
call("before")
call("bar")
call("after")
}
test("withException") {
call("before")
call("withException")
raised("some exception")
call("after")
caught("some exception")
}
}
}
@@ -0,0 +1,62 @@
// EXPECTED_REACHABLE_NODES: 1709
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import kotlin.test.Test
import kotlin.test.Ignore
class A {
@Test
fun foo() {
}
@Ignore
@Test
fun bar() {
}
@Ignore
class B {
@Test
fun foo() {
}
@Ignore
@Test
fun bar() {
}
}
}
@Ignore
class C {
@Test
fun foo() {
}
@Ignore
@Test
fun bar() {
}
}
// FILE: box.kt
import common.*
fun box() = checkLog {
suite("A") {
test("foo")
test("bar", true)
suite("B", true) {
test("foo")
test("bar", true)
}
}
suite("C", true) {
test("foo")
test("bar", true)
}
}
@@ -0,0 +1,201 @@
// IGNORE_BACKEND: JS
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class BadClass(id: Int) {
@Test
fun foo() {}
}
private class BadPrivateClass {
@Test
fun foo() {}
}
class BadProtectedMethodClass {
@Test
protected fun foo() {}
}
class BadPrimaryGoodSecondary(private val id: Int) {
constructor(): this(3)
@Test
fun foo() {
assertEquals(id, 3)
}
}
class GoodSecondaryOnly {
constructor() {
triggered = 3
}
constructor(id: Int) {
triggered = id
}
companion object {
private var triggered = 0
}
@Test
fun foo() {
assertEquals(triggered, 3)
}
}
class BadSecondaryOnly {
private constructor() {}
constructor(id: Int) {}
@Test
fun foo() {}
}
class BadConstructorClass private constructor() {
@Test
fun foo() {}
}
class BadProtectedConstructorClass protected constructor() {
constructor(flag: Boolean): this()
@Test
fun foo() {}
}
class GoodClass() {
constructor(id: Int): this()
@Test
fun foo() {}
}
class GoodNestedClass {
class NestedTestClass {
@Test
fun foo() {}
fun helperMethod(param: String) {}
}
}
class BadNestedClass {
class NestedTestClass(id: Int) {
@Test
fun foo() {}
}
}
class BadMethodClass() {
@Test
fun foo(id: Int) {}
@Test
private fun ping() {}
}
// non-reachable scenarios are tested in nested.kt
class OuterWithPrivateCompanion {
private companion object {
object InnerCompanion {
@Test
fun innerCompanionTest() {
}
}
}
}
class OuterWithPrivateMethod {
companion object {
object InnerCompanion {
@Test
private fun innerCompanionTest() {
}
}
}
}
// FILE: box.kt
import common.*
fun box() = checkLog {
suite("BadClass") {
test("foo") {
caught("Test class BadClass must declare a public or internal constructor with no explicit parameters")
}
}
suite("BadPrivateClass") {
test("foo") {
caught("Test method BadPrivateClass::foo should have public or internal visibility, can not have parameters")
}
}
suite("BadProtectedMethodClass") {
test("foo") {
caught("Test method BadProtectedMethodClass::foo should have public or internal visibility, can not have parameters")
}
}
suite("BadPrimaryGoodSecondary") {
test("foo")
}
suite("GoodSecondaryOnly") {
test("foo")
}
suite("BadSecondaryOnly") {
test("foo") {
caught("Test class BadSecondaryOnly must declare a public or internal constructor with no explicit parameters")
}
}
suite("BadConstructorClass") {
test("foo") {
caught("Test class BadConstructorClass must declare a public or internal constructor with no explicit parameters")
}
}
suite("BadProtectedConstructorClass") {
test("foo") {
caught("Test class BadProtectedConstructorClass must declare a public or internal constructor with no explicit parameters")
}
}
suite("GoodClass") {
test("foo")
}
suite("GoodNestedClass") {
suite("NestedTestClass") {
test("foo")
}
}
suite("BadNestedClass") {
suite("NestedTestClass") {
test("foo") {
caught("Test class BadNestedClass.NestedTestClass must declare a public or internal constructor with no explicit parameters")
}
}
}
suite("BadMethodClass") {
test("foo") {
caught("Test method BadMethodClass::foo should have public or internal visibility, can not have parameters")
}
test("ping") {
caught("Test method BadMethodClass::ping should have public or internal visibility, can not have parameters")
}
}
suite("OuterWithPrivateCompanion") {
suite("Companion") {
suite("InnerCompanion") {
test("innerCompanionTest") {
caught("Test method OuterWithPrivateCompanion.Companion.InnerCompanion::innerCompanionTest should have public or internal visibility, can not have parameters")
}
}
}
}
suite("OuterWithPrivateMethod") {
suite("Companion") {
suite("InnerCompanion") {
test("innerCompanionTest") {
caught("Test method OuterWithPrivateMethod.Companion.InnerCompanion::innerCompanionTest should have public or internal visibility, can not have parameters")
}
}
}
}
}
@@ -0,0 +1,384 @@
// EXPECTED_REACHABLE_NODES: 1829
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: a.kt
package a
import common.*
import kotlin.test.*
class A {
@BeforeTest
fun before() {
call("a.A.before")
}
@AfterTest
fun after() {
call("a.A.after")
}
@Test
fun passing() {
call("a.A.passing")
}
@Test
fun failing() {
call("a.A.failing")
raise("a.A.failing.exception")
call("never happens")
}
@Ignore
@Test
fun ignored() {
call("a.A.ignored")
}
@Test
fun withException() {
call("withException")
raise("some exception")
call("never happens")
}
inner class Inner {
@Test
fun innerTest() {
call("a.A.Inner.innerTest")
}
}
class Nested {
@Test
fun nestedTest() {
call("a.A.Nested.nestedTest")
}
}
companion object {
@Test
fun companionTest() {
call("a.A.companionTest")
}
}
}
object O {
@Test
fun test() {
call("a.O.test")
}
}
// FILE: a_a.kt
package a.a
import common.*
import kotlin.test.*
class A {
@BeforeTest
fun before() {
call("a.a.A.before")
}
@AfterTest
fun after() {
call("a.a.A.after")
}
@Test
fun passing() {
call("a.a.A.passing")
}
@Test
fun failing() {
call("a.a.A.failing")
raise("a.a.A.failing.exception")
call("never happens")
}
@Ignore
@Test
fun ignored() {
call("a.a.A.ignored")
}
@Test
fun withException() {
call("withException")
raise("some exception")
call("never happens")
}
inner class Inner {
@Test
fun innerTest() {
call("a.a.A.Inner.innerTest")
}
}
class Nested {
@Test
fun nestedTest() {
call("a.a.A.Nested.nestedTest")
}
}
companion object {
@Test
fun companionTest() {
call("a.a.A.companionTest")
}
}
}
object O {
@Test
fun test() {
call("a.a.O.test")
}
}
// FILE: a_a2.kt
// RECOMPILE
package a.a
import common.*
import kotlin.test.*
class B {
@BeforeTest
fun before() {
call("a.a.B.before")
}
@AfterTest
fun after() {
call("a.a.B.after")
}
@Test
fun passing() {
call("a.a.B.passing")
}
@Test
fun failing() {
call("a.a.B.failing")
raise("a.a.B.failing.exception")
call("never happens")
}
@Ignore
@Test
fun ignored() {
call("a.a.B.ignored")
}
@Test
fun withException() {
call("withException")
raise("some exception")
call("never happens")
}
inner class Inner {
@Test
fun innerTest() {
call("a.a.B.Inner.innerTest")
}
}
class Nested {
@Test
fun nestedTest() {
call("a.a.B.Nested.nestedTest")
}
}
companion object {
@Test
fun companionTest() {
call("a.a.B.companionTest")
}
}
}
object O2 {
@Test
fun test() {
call("a.a.O2.test")
}
}
// FILE: main.kt
import common.*
import kotlin.test.Test
class Simple {
@Test fun foo() {
call("foo")
}
}
fun box() = checkLog(false) {
suite("a") {
suite("A") {
test("passing") {
call("a.A.before")
call("a.A.passing")
call("a.A.after")
}
test("failing") {
call("a.A.before")
call("a.A.failing")
raised("a.A.failing.exception")
call("a.A.after")
caught("a.A.failing.exception")
}
test("ignored", true) {
call("a.A.before")
call("a.A.ignored")
call("a.A.after")
}
test("withException") {
call("a.A.before")
call("withException")
raised("some exception")
call("a.A.after")
caught("some exception")
}
suite("Inner") {
test("innerTest") {
call("a.A.Inner.innerTest")
}
}
suite("Nested") {
test("nestedTest") {
call("a.A.Nested.nestedTest")
}
}
suite("Companion") {
test("companionTest") {
call("a.A.companionTest")
}
}
}
suite("O") {
test("test") {
call("a.O.test")
}
}
}
suite("a.a") {
suite("A") {
test("passing") {
call("a.a.A.before")
call("a.a.A.passing")
call("a.a.A.after")
}
test("failing") {
call("a.a.A.before")
call("a.a.A.failing")
raised("a.a.A.failing.exception")
call("a.a.A.after")
caught("a.a.A.failing.exception")
}
test("ignored", true) {
call("a.a.A.before")
call("a.a.A.ignored")
call("a.a.A.after")
}
test("withException") {
call("a.a.A.before")
call("withException")
raised("some exception")
call("a.a.A.after")
caught("some exception")
}
suite("Inner") {
test("innerTest") {
call("a.a.A.Inner.innerTest")
}
}
suite("Nested") {
test("nestedTest") {
call("a.a.A.Nested.nestedTest")
}
}
suite("Companion") {
test("companionTest") {
call("a.a.A.companionTest")
}
}
}
suite("O") {
test("test") {
call("a.a.O.test")
}
}
suite("B") {
test("passing") {
call("a.a.B.before")
call("a.a.B.passing")
call("a.a.B.after")
}
test("failing") {
call("a.a.B.before")
call("a.a.B.failing")
raised("a.a.B.failing.exception")
call("a.a.B.after")
caught("a.a.B.failing.exception")
}
test("ignored", true) {
call("a.a.B.before")
call("a.a.B.ignored")
call("a.a.B.after")
}
test("withException") {
call("a.a.B.before")
call("withException")
raised("some exception")
call("a.a.B.after")
caught("some exception")
}
suite("Inner") {
test("innerTest") {
call("a.a.B.Inner.innerTest")
}
}
suite("Nested") {
test("nestedTest") {
call("a.a.B.Nested.nestedTest")
}
}
suite("Companion") {
test("companionTest") {
call("a.a.B.companionTest")
}
}
}
suite("O2") {
test("test") {
call("a.a.O2.test")
}
}
}
suite("") {
suite("Simple") {
test("foo") {
call("foo")
}
}
}
}
@@ -0,0 +1,65 @@
// EXPECTED_REACHABLE_NODES: 1719
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import common.call
import kotlin.test.Test
import kotlin.test.BeforeTest
import kotlin.test.AfterTest
interface TestyInterface {
@Test
fun someVarTest() {
call("TestyInterface.someVarTest")
}
}
abstract class AbstractTest : TestyInterface {
@Test abstract fun abstractTest()
@Test
fun someTest() {
call("AbstractTest.someTest")
}
}
interface BeforeAfterInterface {
@BeforeTest
@AfterTest
fun beforeAfter() {
call("beforeAfter")
}
}
class InheritedTest : AbstractTest(), BeforeAfterInterface {
@Test override fun abstractTest() {
call("InheritedTest.abstractTest")
}
}
// FILE: box.kt
import common.*
fun box() = checkLog() {
suite("InheritedTest") {
test("abstractTest") {
call("beforeAfter")
call("InheritedTest.abstractTest")
call("beforeAfter")
}
test("someTest") {
call("beforeAfter")
call("AbstractTest.someTest")
call("beforeAfter")
}
test("someVarTest") {
call("beforeAfter")
call("TestyInterface.someVarTest")
call("beforeAfter")
}
}
}
@@ -0,0 +1,32 @@
// EXPECTED_REACHABLE_NODES: 1697
// !LANGUAGE: +MultiPlatformProjects
// TARGET_FRONTEND: ClassicFrontend
// FIR status: expect/actual in one module
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: lib.kt
import kotlin.test.Test
expect class PlatformTest {
@Test fun platformTest()
}
// FILE: main.kt
import common.*
import kotlin.test.Test
actual class PlatformTest {
@Test actual fun platformTest() {}
@Test fun someOtherTest() {}
}
fun box() = checkLog {
suite("PlatformTest") {
test("platformTest")
test("someOtherTest")
}
}
@@ -0,0 +1,97 @@
// EXPECTED_REACHABLE_NODES: 1735
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import common.call
import kotlin.test.Test
class Outer {
val prop = "prop"
@Test
fun test1() {
}
inner class Inner {
@Test fun innerTest() {
call(prop + "Inner")
}
inner class Inneer {
@Test fun innermostTest() {
call(prop + "Inneer")
}
}
}
class Nested {
@Test
fun a() {
}
@Test
fun b() {
}
class EvenDeeper {
@Test
fun c() {
}
}
}
@Test
fun test2() {
}
companion object {
@Test
fun companionTest() {
}
object InnerCompanion {
@Test
fun innerCompanionTest() {
}
}
}
}
// FILE: box.kt
import common.*
fun box() = checkLog {
suite("Outer") {
test("test1")
suite("Inner") {
test("innerTest") {
call("propInner")
}
suite("Inneer") {
test("innermostTest") {
call("propInneer")
}
}
}
suite("Nested") {
test("a")
test("b")
suite("EvenDeeper") {
test("c")
}
}
test("test2")
suite("Companion") {
test("companionTest")
suite("InnerCompanion") {
test("innerCompanionTest")
}
}
}
}
@@ -0,0 +1,83 @@
// EXPECTED_REACHABLE_NODES: 1737
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import common.call
import kotlin.test.Test
import kotlin.test.BeforeTest
import kotlin.test.AfterTest
open class A {
@Test fun foo(): String {
return "promise"
}
@Test fun bar() = "future"
}
interface WithBefore {
@BeforeTest fun before() {
call("before")
}
}
interface WithAfter {
@AfterTest fun after() {
call("after")
}
}
class B: A(), WithBefore
class C: A(), WithAfter
class D: A(), WithBefore, WithAfter
// FILE: box.kt
import common.*
fun box() = checkLog {
suite("A") {
test("foo") {
returned("promise")
}
test("bar") {
returned("future")
}
}
suite("B") {
test("foo") {
call("before")
returned("promise")
}
test("bar") {
call("before")
returned("future")
}
}
suite("C") {
test("foo") {
call("after")
returned("promise")
}
test("bar") {
call("after")
returned("future")
}
}
suite("D") {
test("foo") {
call("before")
call("after")
returned("promise")
}
test("bar") {
call("before")
call("after")
returned("future")
}
}
}
@@ -0,0 +1,26 @@
// EXPECTED_REACHABLE_NODES: 1698
// KJS_WITH_FULL_RUNTIME
// SKIP_DCE_DRIVEN
// RUN_UNIT_TESTS
// ES_MODULES
// FILE: test.kt
import common.call
import kotlin.test.Test
class Simple {
@Test fun foo() {
call("foo")
}
}
// FILE: box.kt
import common.*
fun box() = checkLog {
suite("Simple") {
test("foo") {
call("foo")
}
}
}
@@ -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"
}
@@ -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
@@ -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")
}
}
@@ -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")
}
}
@@ -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