From 79359b7bc23496f3081431c65ee9006ad5c9a2d1 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Wed, 20 Dec 2017 21:37:06 +0300 Subject: [PATCH] JS: test kotlin-test as box tests, support nested, fix mpp Support tests inside nested classes and companion objects (KT-21850 fixed). Don't launch multiplatform tests twice (KT-21567 fixed). --- .../jetbrains/kotlin/js/test/BasicBoxTest.kt | 21 +++- .../js/test/semantics/BoxJsTestGenerated.java | 45 ++++++++ .../js/translate/test/JSTestGenerator.kt | 27 +++-- .../testData/box/char/charInStringTemplate.kt | 1 + .../externalInlineNewDecomposed.kt | 2 +- .../testData/box/kotlin.test/_common.kt | 108 ++++++++++++++++++ .../testData/box/kotlin.test/beforeAfter.kt | 55 +++++++++ .../testData/box/kotlin.test/ignore.kt | 53 +++++++++ .../testData/box/kotlin.test/inherited.kt | 55 +++++++++ .../testData/box/kotlin.test/mpp.kt | 27 +++++ .../testData/box/kotlin.test/nested.kt | 87 ++++++++++++++ .../testData/box/kotlin.test/simple.kt | 16 +++ 12 files changed, 483 insertions(+), 14 deletions(-) create mode 100644 js/js.translator/testData/box/kotlin.test/_common.kt create mode 100644 js/js.translator/testData/box/kotlin.test/beforeAfter.kt create mode 100644 js/js.translator/testData/box/kotlin.test/ignore.kt create mode 100644 js/js.translator/testData/box/kotlin.test/inherited.kt create mode 100644 js/js.translator/testData/box/kotlin.test/mpp.kt create mode 100644 js/js.translator/testData/box/kotlin.test/nested.kt create mode 100644 js/js.translator/testData/box/kotlin.test/simple.kt diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 346f7b1b929..01a4f2582e2 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -114,6 +114,7 @@ abstract class BasicBoxTest( val outputFileName = module.outputFileName(outputDir) + ".js" generateJavaScriptFile(file.parent, module, outputFileName, dependencies, friends, modules.size > 1, + ENABLE_MULTIPLATFORM.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile, mainCallParameters) if (!module.name.endsWith(OLD_MODULE_SUFFIX)) Pair(outputFileName, module) else null @@ -280,6 +281,7 @@ abstract class BasicBoxTest( dependencies: List, friends: List, multiModule: Boolean, + multiplatform: Boolean, outputPrefixFile: File?, outputPostfixFile: File?, mainCallParameters: MainCallParameters @@ -298,7 +300,7 @@ abstract class BasicBoxTest( val psiFiles = createPsiFiles(allSourceFiles.sortedBy { it.canonicalPath }.map { it.canonicalPath }) val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct() - val config = createConfig(sourceDirs, module, dependencies, friends, multiModule, incrementalData = null) + val config = createConfig(sourceDirs, module, dependencies, friends, multiModule, multiplatform, incrementalData = null) val outputFile = File(outputFileName) val incrementalData = IncrementalData() @@ -306,7 +308,7 @@ abstract class BasicBoxTest( mainCallParameters, incrementalData) if (module.hasFilesToRecompile) { - checkIncrementalCompilation(sourceDirs, module, kotlinFiles, dependencies, friends, multiModule, outputFile, + checkIncrementalCompilation(sourceDirs, module, kotlinFiles, dependencies, friends, multiModule, multiplatform, outputFile, outputPrefixFile, outputPostfixFile, mainCallParameters, incrementalData) } } @@ -318,6 +320,7 @@ abstract class BasicBoxTest( dependencies: List, friends: List, multiModule: Boolean, + multiplatform: Boolean, outputFile: File, outputPrefixFile: File?, outputPostfixFile: File?, @@ -339,7 +342,7 @@ abstract class BasicBoxTest( .sortedBy { it.canonicalPath } .map { sourceToTranslationUnit[it]!! } - val recompiledConfig = createConfig(sourceDirs, module, dependencies, friends, multiModule, incrementalData) + val recompiledConfig = createConfig(sourceDirs, module, dependencies, friends, multiModule, multiplatform, incrementalData) val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") translateFiles(translationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, @@ -516,7 +519,7 @@ abstract class BasicBoxTest( private fun createConfig( sourceDirs: List,module: TestModule, dependencies: List, friends: List, - multiModule: Boolean, incrementalData: IncrementalData? + multiModule: Boolean, multiplatform: Boolean, incrementalData: IncrementalData? ): JsConfig { val configuration = environment.configuration.copy() @@ -550,6 +553,15 @@ abstract class BasicBoxTest( configuration.put(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, typedArraysEnabled) + if (multiplatform) { + val defaultLanguageVersionSettings = configuration.languageVersionSettings + configuration.languageVersionSettings = object : LanguageVersionSettings by defaultLanguageVersionSettings { + override fun getFeatureSupport(feature: LanguageFeature): LanguageFeature.State = + if (feature == LanguageFeature.MultiPlatformProjects) LanguageFeature.State.ENABLED + else defaultLanguageVersionSettings.getFeatureSupport(feature) + } + } + return JsConfig(project, configuration, METADATA_CACHE, (JsConfig.JS_STDLIB + JsConfig.JS_KOTLIN_TEST).toSet()) } @@ -699,6 +711,7 @@ abstract class BasicBoxTest( private val EXPECTED_REACHABLE_NODES = Pattern.compile("^// *$EXPECTED_REACHABLE_NODES_DIRECTIVE: *([0-9]+) *$", Pattern.MULTILINE) private val RECOMPILE_PATTERN = Pattern.compile("^// *RECOMPILE *$", Pattern.MULTILINE) private val SOURCE_MAP_SOURCE_EMBEDDING = Regex("^// *SOURCE_MAP_EMBED_SOURCES: ([A-Z]+)*\$", RegexOption.MULTILINE) + private val ENABLE_MULTIPLATFORM = Pattern.compile("^// *MULTIPLATFORM *$", Pattern.MULTILINE) val TEST_MODULE = "JS_TESTS" private val DEFAULT_MODULE = "main" diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 58432c13954..3afbfb60ba5 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -5886,6 +5886,51 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { } } + @TestMetadata("js/js.translator/testData/box/kotlin.test") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Kotlin_test extends AbstractBoxJsTest { + public void testAllFilesPresentInKotlin_test() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/kotlin.test"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("beforeAfter.kt") + public void testBeforeAfter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/kotlin.test/beforeAfter.kt"); + doTest(fileName); + } + + @TestMetadata("ignore.kt") + public void testIgnore() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/kotlin.test/ignore.kt"); + doTest(fileName); + } + + @TestMetadata("inherited.kt") + public void testInherited() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/kotlin.test/inherited.kt"); + doTest(fileName); + } + + @TestMetadata("mpp.kt") + public void testMpp() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/kotlin.test/mpp.kt"); + doTest(fileName); + } + + @TestMetadata("nested.kt") + public void testNested() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/kotlin.test/nested.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/kotlin.test/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("js/js.translator/testData/box/labels") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt index 249065ba9dd..1bc51b58a01 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/test/JSTestGenerator.kt @@ -58,20 +58,21 @@ class JSTestGenerator(val context: TranslationContext) { } private fun generateTestFunctions(classDescriptor: ClassDescriptor, parentFun: JsFunction) { - if (classDescriptor.modality === Modality.ABSTRACT) return + if (classDescriptor.modality === Modality.ABSTRACT || classDescriptor.isExpect) return val suiteFunction = JsFunction(context.scope(), JsBlock(), "suite function") val descriptors = classDescriptor.unsubstitutedMemberScope .getContributedDescriptors(DescriptorKindFilter.FUNCTIONS, MemberScope.ALL_NAME_FILTER) - .filterIsInstance() - val beforeFunctions = descriptors.filter { it.isBefore } - val afterFunctions = descriptors.filter { it.isAfter } + val beforeFunctions = descriptors.filterIsInstance().filter { it.isBefore } + val afterFunctions = descriptors.filterIsInstance().filter { it.isAfter } descriptors.forEach { - if (it.isTest) { - generateCodeForTestMethod(it, beforeFunctions, afterFunctions, classDescriptor, suiteFunction) + when { + it is ClassDescriptor -> generateTestFunctions(it, suiteFunction) + it is FunctionDescriptor && it.isTest -> + generateCodeForTestMethod(it, beforeFunctions, afterFunctions, classDescriptor, suiteFunction) } } @@ -101,9 +102,7 @@ class JSTestGenerator(val context: TranslationContext) { val functionToTest = JsFunction(scope, JsBlock(), "test function") val innerContext = context.contextWithScope(functionToTest) - val expression = ReferenceTranslator.translateAsTypeReference(classDescriptor, innerContext) - val testClass = JsNew(expression) - val classVal = innerContext.defineTemporary(testClass) + val classVal = innerContext.defineTemporary(classDescriptor.instance(innerContext)) fun FunctionDescriptor.buildCall() = CallTranslator.buildCall(context, this, emptyList(), classVal).makeStmt() @@ -119,6 +118,16 @@ class JSTestGenerator(val context: TranslationContext) { return functionToTest } + private fun ClassDescriptor.instance(context: TranslationContext): JsExpression { + return if (kind == ClassKind.OBJECT) { + ReferenceTranslator.translateAsValueReference(this, context) + } + else { + val args = if (isInner) listOf((containingDeclaration as ClassDescriptor).instance(context)) else emptyList() + JsNew(ReferenceTranslator.translateAsTypeReference(this, context), args) + } + } + private val suiteRef: JsExpression by lazy { findFunction("suite") } private val testRef: JsExpression by lazy { findFunction("test") } diff --git a/js/js.translator/testData/box/char/charInStringTemplate.kt b/js/js.translator/testData/box/char/charInStringTemplate.kt index fcb982055a7..692e44ee9d4 100644 --- a/js/js.translator/testData/box/char/charInStringTemplate.kt +++ b/js/js.translator/testData/box/char/charInStringTemplate.kt @@ -1,5 +1,6 @@ // EXPECTED_REACHABLE_NODES: 1114 package foo +import kotlin.test.assertEquals var log = "" diff --git a/js/js.translator/testData/box/inlineMultiModule/externalInlineNewDecomposed.kt b/js/js.translator/testData/box/inlineMultiModule/externalInlineNewDecomposed.kt index b58daa7ab6c..f4ee5825913 100644 --- a/js/js.translator/testData/box/inlineMultiModule/externalInlineNewDecomposed.kt +++ b/js/js.translator/testData/box/inlineMultiModule/externalInlineNewDecomposed.kt @@ -1,4 +1,4 @@ -// EXPECTED_REACHABLE_NODES: 4643 +// EXPECTED_REACHABLE_NODES: 5107 // MODULE: lib // FILE: lib.kt diff --git a/js/js.translator/testData/box/kotlin.test/_common.kt b/js/js.translator/testData/box/kotlin.test/_common.kt new file mode 100644 index 00000000000..7afdeb98412 --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/_common.kt @@ -0,0 +1,108 @@ +import kotlin.test.FrameworkAdapter + +private val context = TestContext() + +fun call(name: String) = context.call(name) + +fun raise(name: String): Nothing { + context.raised(name) + throw Exception(name) +} + +@Suppress("INVISIBLE_MEMBER") +val underscore = kotlin.test.setAdapter(object : FrameworkAdapter { + override fun suite(name: String, ignored: Boolean, suiteFn: () -> Unit) { + context.suite(name, ignored) { suiteFn() } + } + + override fun test(name: String, ignored: Boolean, testFn: () -> Unit) { + context.test(name, ignored) { testFn() } + } +}) + +class TestContext { + val log: String + get() = logHead + (lastRecord ?: "") + + var indentation = "" + + fun suite(name: String, ignored: Boolean = false, body: TestContext.() -> Unit) = indent { + record("suite(\"$name\"${optionalIgnore(ignored)}) {") + body.runSafely() + record("}") + } + + + fun test(name: String, ignored: Boolean = false, body: TestContext.() -> Unit = {}) = indent { + val num = record("test(\"$name\"${optionalIgnore(ignored)}) {") + + body.runSafely() + + if (!writtenSince(num)) { + record("test(\"$name\"${optionalIgnore(ignored)})", replaceLast = true) + } + else { + record("}") + } + } + + fun call(name: String) = indent { + record("call(\"$name\")") + } + + fun raised(msg: String) = indent { + record("raised(\"$msg\")") + } + + fun caught(msg: String) = indent { + record("caught(\"$msg\")") + } + + private fun (TestContext.() -> Unit).runSafely() { + try { + this() + } + 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(body: TestContext.() -> Unit): String { + val expectedContext = TestContext() + expectedContext.suite("") { + body() + } + if (context.log != expectedContext.log) { + return "Failed test structure check. Expected: ${expectedContext.log}; actual: ${context.log}." + } + else { + return "OK" + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/kotlin.test/beforeAfter.kt b/js/js.translator/testData/box/kotlin.test/beforeAfter.kt new file mode 100644 index 00000000000..d3e43fc138d --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/beforeAfter.kt @@ -0,0 +1,55 @@ +// EXPECTED_REACHABLE_NODES: 1183 +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") + } +} + +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") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/kotlin.test/ignore.kt b/js/js.translator/testData/box/kotlin.test/ignore.kt new file mode 100644 index 00000000000..0536e543a6f --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/ignore.kt @@ -0,0 +1,53 @@ +// EXPECTED_REACHABLE_NODES: 1192 +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() { + } +} + +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) + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/kotlin.test/inherited.kt b/js/js.translator/testData/box/kotlin.test/inherited.kt new file mode 100644 index 00000000000..67554c742dd --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/inherited.kt @@ -0,0 +1,55 @@ +// EXPECTED_REACHABLE_NODES: 1197 +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") + } +} + +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") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/kotlin.test/mpp.kt b/js/js.translator/testData/box/kotlin.test/mpp.kt new file mode 100644 index 00000000000..2e8082ec587 --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/mpp.kt @@ -0,0 +1,27 @@ +// EXPECTED_REACHABLE_NODES: 1173 +// MULTIPLATFORM + +// MODULE: lib +// FILE: lib.kt +import kotlin.test.Test + +expect class PlatformTest { + @Test fun platformTest() +} + +// MODULE: main(lib) +// FILE: main.kt +import kotlin.test.Test + +actual class PlatformTest { + @Test actual fun platformTest() {} + + @Test fun someOtherTest() {} +} + +fun box() = checkLog { + suite("PlatformTest") { + test("platformTest") + test("someOtherTest") + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/kotlin.test/nested.kt b/js/js.translator/testData/box/kotlin.test/nested.kt new file mode 100644 index 00000000000..29f24fe84eb --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/nested.kt @@ -0,0 +1,87 @@ +// EXPECTED_REACHABLE_NODES: 1200 +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 inneerTest() { + 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() { + } + } + } +} + +fun box() = checkLog { + suite("Outer") { + test("test1") + suite("Inner") { + test("innerTest") { + call("propInner") + } + suite("Inneer") { + test("inneerTest") { + call("propInneer") + } + } + } + suite("Nested") { + test("a") + test("b") + suite("EvenDeeper") { + test("c") + } + } + test("test2") + suite("Companion") { + test("companionTest") + suite("InnerCompanion") { + test("innerCompanionTest") + } + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/kotlin.test/simple.kt b/js/js.translator/testData/box/kotlin.test/simple.kt new file mode 100644 index 00000000000..fe4f68eaa8c --- /dev/null +++ b/js/js.translator/testData/box/kotlin.test/simple.kt @@ -0,0 +1,16 @@ +// EXPECTED_REACHABLE_NODES: 1173 +import kotlin.test.Test + +class Simple { + @Test fun foo() { + call("foo") + } +} + +fun box() = checkLog { + suite("Simple") { + test("foo") { + call("foo") + } + } +} \ No newline at end of file