From ff50d40b32ba97d8426ad3f7e0dd114bea51e942 Mon Sep 17 00:00:00 2001 From: Artem Kobzar Date: Thu, 16 Nov 2023 19:02:04 +0000 Subject: [PATCH] [K/JS] Rework kotlin tests compilation to make it works with per-file granularity ^KT-61525 Fixed --- .../ir/backend/js/JsCommonBackendContext.kt | 2 +- .../ir/backend/js/JsIrBackendContext.kt | 19 +- .../ir/backend/js/ic/HashCalculatorForIC.kt | 2 +- .../kotlin/ir/backend/js/ic/JsPerFileCache.kt | 150 +++++-- .../ir/backend/js/lower/TestGenerator.kt | 9 +- .../irToJs/IrModuleToJsTransformer.kt | 76 ++-- .../irToJs/JsIrProgramFragment.kt | 10 +- .../irToJs/JsTestFunctionTransformer.kt | 72 ++++ .../backend/js/transformers/irToJs/Merger.kt | 24 +- .../irToJs/ModuleFragmentToExternalName.kt | 15 +- .../transformers/irToJs/PerFileGenerator.kt | 44 +- .../js/transformers/irToJs/ReservedJsNames.kt | 3 + .../serialization/JsIrAstDeserializer.kt | 10 +- .../utils/serialization/JsIrAstSerializer.kt | 13 +- .../kotlin/backend/wasm/WasmBackendContext.kt | 4 +- js/js.tests/build.gradle.kts | 3 + .../incremental/AbstractInvalidationTest.kt | 10 +- ...JsFirInvalidationPerFileTestGenerated.java | 6 + ...FirInvalidationPerModuleTestGenerated.java | 6 + ...IrES6InvalidationPerFileTestGenerated.java | 6 + ...ES6InvalidationPerModuleTestGenerated.java | 6 + .../JsIrInvalidationPerFileTestGenerated.java | 6 + ...sIrInvalidationPerModuleTestGenerated.java | 6 + .../js/test/fir/FirJsBoxTestGenerated.java | 64 +++ .../js/test/fir/FirJsES6BoxTestGenerated.java | 64 +++ .../js/test/ir/IrBoxJsES6TestGenerated.java | 64 +++ .../js/test/ir/IrBoxJsTestGenerated.java | 64 +++ .../box/esModules/kotlin.test/_common.kt | 188 +++++++++ .../box/esModules/kotlin.test/beforeAfter.kt | 65 +++ .../box/esModules/kotlin.test/ignore.kt | 62 +++ .../kotlin.test/illegalParameters.kt | 201 +++++++++ .../box/esModules/kotlin.test/incremental.kt | 384 ++++++++++++++++++ .../box/esModules/kotlin.test/inherited.kt | 65 +++ .../testData/box/esModules/kotlin.test/mpp.kt | 32 ++ .../box/esModules/kotlin.test/nested.kt | 97 +++++ .../esModules/kotlin.test/returnTestResult.kt | 83 ++++ .../box/esModules/kotlin.test/simple.kt | 26 ++ .../invalidation/kotlinTest/main/common.kt | 186 +++++++++ .../invalidation/kotlinTest/main/m.kt | 51 +++ .../invalidation/kotlinTest/main/module.info | 16 + .../invalidation/kotlinTest/main/test1.1.kt | 20 + .../invalidation/kotlinTest/main/test1.2.kt | 27 ++ .../invalidation/kotlinTest/main/test2.3.kt | 15 + .../invalidation/kotlinTest/project.info | 18 + libraries/kotlin.test/js-ir/build.gradle.kts | 1 + .../src/main/kotlin/setupIrJsBoxTests.kt | 2 + .../FirWasmJsTranslatorTestGenerated.java | 64 +++ .../test/K1WasmJsTranslatorTestGenerated.java | 64 +++ 48 files changed, 2290 insertions(+), 135 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsTestFunctionTransformer.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/_common.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/ignore.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/incremental.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/inherited.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/mpp.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/nested.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt create mode 100644 js/js.translator/testData/box/esModules/kotlin.test/simple.kt create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/main/common.kt create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/main/m.kt create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/main/module.info create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.1.kt create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.2.kt create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/main/test2.3.kt create mode 100644 js/js.translator/testData/incremental/invalidation/kotlinTest/project.info diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt index f9cff3b4de8..7388eb06013 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsCommonBackendContext.kt @@ -47,7 +47,7 @@ interface JsCommonBackendContext : CommonBackendContext { val enumEntries: IrClassSymbol val createEnumEntries: IrSimpleFunctionSymbol - fun createTestContainerFun(irFile: IrFile): IrSimpleFunction + fun createTestContainerFun(container: IrDeclaration): IrSimpleFunction } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index c99e762d682..e2e01dbefc1 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -120,14 +120,17 @@ class JsIrBackendContext( val testFunsPerFile = hashMapOf() - override fun createTestContainerFun(irFile: IrFile): IrSimpleFunction { - return testFunsPerFile.getOrPut(irFile) { - irFactory.addFunction(irFile) { - name = Name.identifier("test fun") - returnType = irBuiltIns.unitType - origin = JsIrBuilder.SYNTHESIZED_DECLARATION - }.apply { - body = irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, emptyList()) + override fun createTestContainerFun(container: IrDeclaration): IrSimpleFunction { + val irFile = container.file + return irFactory.stageController.restrictTo(container) { + testFunsPerFile.getOrPut(irFile) { + irFactory.addFunction(irFile) { + name = Name.identifier("test fun") + returnType = irBuiltIns.unitType + origin = JsIrBuilder.SYNTHESIZED_DECLARATION + }.apply { + body = irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, emptyList()) + } } } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt index 10181bd90ff..02261d64262 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/HashCalculatorForIC.kt @@ -244,4 +244,4 @@ internal fun CrossModuleReferences.crossModuleReferencesHashForIC() = HashCalcul update(import.moduleExporter.internalName.toString()) } } -}.finalizeAndGetHash() +}.finalizeAndGetHash() \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt index 0f609dcfa59..4f6149bcc4f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsPerFileCache.kt @@ -30,7 +30,7 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult private fun SrcFileArtifact.loadJsIrModuleHeaders(moduleArtifact: ModuleArtifact) = with(loadJsIrFragments()!!) { LoadedJsIrModuleHeaders( - mainFragment.mainFunction, + mainFragment.mainFunctionTag, mainFragment.run { asIrModuleHeader( getMainFragmentExternalName(moduleArtifact), @@ -83,7 +83,10 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult protected open val filePrefix by lazy(LazyThreadSafetyMode.NONE) { fileArtifact.srcFilePath.run { "${substringAfterLast('/')}.${cityHash64()}" } } override fun loadJsIrModule(): JsIrModule { - val fragments = fileArtifact.loadJsIrFragments()!! + val fragments = fileArtifact.loadJsIrFragments()!!.also { + it.mainFragment.testEnvironment = null + } + val isExportFileCachedInfo = this is ExportFileCachedInfo return JsIrModule( jsIrHeader.moduleName, @@ -97,6 +100,7 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult open class MainFileCachedInfo(moduleArtifact: ModuleArtifact, fileArtifact: SrcFileArtifact, moduleHeader: JsIrModuleHeader? = null) : SerializableCachedFileInfo(moduleArtifact, fileArtifact, moduleHeader) { var mainFunctionTag: String? = null + var testEnvironment: JsIrProgramTestEnvironment? = null var exportFileCachedInfo: ExportFileCachedInfo? = null val jsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_JS) } @@ -105,25 +109,27 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult class Merged(private val cachedFileInfos: List) : MainFileCachedInfo(cachedFileInfos.first().moduleArtifact, cachedFileInfos.first().fileArtifact) { + override fun loadJsIrModule(): JsIrModule = cachedFileInfos.map { it.loadJsIrModule() }.merge() + override val filePrefix by lazy(LazyThreadSafetyMode.NONE) { val hash = cachedFileInfos.map { it.fileArtifact.srcFilePath }.sorted().joinToString().cityHash64() fileArtifact.srcFilePath.run { "${substringAfterLast('/')}.$hash.merged" } } - override fun loadJsIrModule(): JsIrModule = cachedFileInfos.map { it.loadJsIrModule() }.merge() - init { assert(cachedFileInfos.size > 1) { "Merge is unnecessary" } - val isModified = cachedFileInfos.any { it.fileArtifact.isModified() } + var isModified = false + + for (info in cachedFileInfos) { + if (!info.fileArtifact.isModified()) { + isModified = true + } + info.testEnvironment?.let { testEnvironment = it } + } + val mainAndExportHeaders = when { isModified -> cachedFileInfos.asSequence().map { it.fileArtifact.loadJsIrModuleHeaders(moduleArtifact) } - else -> cachedFileInfos.asSequence().map { - LoadedJsIrModuleHeaders( - it.mainFunctionTag, - it.jsIrHeader, - it.exportFileCachedInfo?.jsIrHeader - ) - } + else -> cachedFileInfos.asSequence().map { LoadedJsIrModuleHeaders(it.mainFunctionTag, it.jsIrHeader, it.exportFileCachedInfo?.jsIrHeader) } } val mainHeaders = mutableListOf() @@ -141,15 +147,13 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult } jsIrHeader = mainHeaders.merge() - exportFileCachedInfo = exportHeaders - .takeIf { it.isNotEmpty() } - ?.let { - ExportFileCachedInfo.Merged( - filePrefix, - it.merge(), - cachedFileInfos.mapNotNull(MainFileCachedInfo::exportFileCachedInfo) - ) - } + exportFileCachedInfo = exportHeaders.takeIf { it.isNotEmpty() }?.let { + ExportFileCachedInfo.Merged( + filePrefix, + it.merge(), + cachedFileInfos.mapNotNull(MainFileCachedInfo::exportFileCachedInfo) + ) + } } } } @@ -175,6 +179,8 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult class ModuleProxyFileCachedInfo(moduleArtifact: ModuleArtifact, moduleHeader: JsIrModuleHeader? = null) : CachedFileInfo(moduleArtifact, moduleHeader) { var mainFunctionTag: String? = null + var suiteFunctionTag: String? = null + var packagesToItsTestFunctions: CachedTestFunctionsWithTheirPackage = emptyMap() val jsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_JS) } val dtsFileArtifact by lazy(LazyThreadSafetyMode.NONE) { getArtifactWithName(CACHED_FILE_D_TS) } @@ -187,6 +193,8 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult jsIrHeader.externalModuleName, jsIrHeader.externalModuleName, mainFunctionTag, + suiteFunctionTag, + packagesToItsTestFunctions, jsIrHeader.importedWithEffectInModuleWithName ) } @@ -201,9 +209,20 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult it.crossFileReferencesHash = ICHash.fromProtoStream(this) - if (it is CachedFileInfo.ExportFileCachedInfo) { - it.tsDeclarationsHash = runIf(readBool()) { readInt64() } - reexportedIn = cachedFileInfo.moduleArtifact.moduleExternalName + when (it) { + is CachedFileInfo.MainFileCachedInfo -> { + it.mainFunctionTag = ifTrue { readString() } + it.testEnvironment = ifTrue { JsIrProgramTestEnvironment(readString(), readString()) } + } + is CachedFileInfo.ExportFileCachedInfo -> { + it.tsDeclarationsHash = ifTrue { readInt64() } + reexportedIn = cachedFileInfo.moduleArtifact.moduleExternalName + } + is CachedFileInfo.ModuleProxyFileCachedInfo -> { + it.mainFunctionTag = ifTrue { readString() } + it.suiteFunctionTag = ifTrue { readString() } + it.packagesToItsTestFunctions = loadTestFunctions() + } } @@ -222,6 +241,14 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult ) } + private fun CodedInputStream.loadTestFunctions() = buildMap { + repeat(readInt32()) { + put(readString(), buildList { + repeat(readInt32()) { add(readString()) } + }) + } + } + private fun CachedFileInfo.MainFileCachedInfo.readModuleHeaderCache(f: CodedInputStream.() -> T): T? = moduleHeaderArtifact?.useCodedInputIfExists(f) @@ -231,7 +258,6 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult return mainFileCachedFileInfo.readModuleHeaderCache { mainFileCachedFileInfo.apply { exportFileCachedInfo = fetchFileInfoForExportedPart(this) - mainFunctionTag = ifTrue { readString() } loadSingleCachedFileInfo(this) } } @@ -239,10 +265,7 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult private fun ModuleArtifact.fetchModuleProxyFileInfo(): CachedFileInfo.ModuleProxyFileCachedInfo? { val mainFileCachedFileInfo = CachedFileInfo.ModuleProxyFileCachedInfo(this) - return mainFileCachedFileInfo.moduleHeaderArtifact?.useCodedInputIfExists { - mainFileCachedFileInfo.mainFunctionTag = ifTrue { readString() } - loadSingleCachedFileInfo(mainFileCachedFileInfo) - } + return mainFileCachedFileInfo.moduleHeaderArtifact?.useCodedInputIfExists { loadSingleCachedFileInfo(mainFileCachedFileInfo) } } private fun CodedInputStream.fetchFileInfoForExportedPart(mainCachedFileInfo: CachedFileInfo.MainFileCachedInfo): CachedFileInfo.ExportFileCachedInfo? { @@ -256,24 +279,43 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult private fun CodedOutputStream.commitSingleFileInfo(cachedFileInfo: CachedFileInfo) { writeStringNoTag(cachedFileInfo.jsIrHeader.externalModuleName) cachedFileInfo.crossFileReferencesHash.toProtoStream(this) - if (cachedFileInfo is CachedFileInfo.ExportFileCachedInfo) { - ifNotNull(cachedFileInfo.tsDeclarationsHash, ::writeInt64NoTag) + when (cachedFileInfo) { + is CachedFileInfo.MainFileCachedInfo -> { + ifNotNull(cachedFileInfo.mainFunctionTag, ::writeStringNoTag) + ifNotNull(cachedFileInfo.testEnvironment) { + writeStringNoTag(it.testFunctionTag) + writeStringNoTag(it.suiteFunctionTag) + } + } + is CachedFileInfo.ExportFileCachedInfo -> ifNotNull(cachedFileInfo.tsDeclarationsHash, ::writeInt64NoTag) + is CachedFileInfo.ModuleProxyFileCachedInfo -> { + ifNotNull(cachedFileInfo.mainFunctionTag, ::writeStringNoTag) + ifNotNull(cachedFileInfo.suiteFunctionTag, ::writeStringNoTag) + writeTestFunctions(cachedFileInfo.packagesToItsTestFunctions) + } } ifNotNull(cachedFileInfo.jsIrHeader.importedWithEffectInModuleWithName) { writeStringNoTag(it) } commitJsIrModuleHeaderNames(cachedFileInfo.jsIrHeader) } + private fun CodedOutputStream.writeTestFunctions(cachedTestFunctionsWithTheirPackage: CachedTestFunctionsWithTheirPackage) { + writeInt32NoTag(cachedTestFunctionsWithTheirPackage.size) + cachedTestFunctionsWithTheirPackage.forEach { (key, value) -> + writeStringNoTag(key) + writeInt32NoTag(value.size) + value.forEach(::writeStringNoTag) + } + } + private fun CachedFileInfo.commitFileInfo() = when (this) { is CachedFileInfo.MainFileCachedInfo -> { moduleHeaderArtifact?.useCodedOutput { ifNotNull(exportFileCachedInfo) { commitSingleFileInfo(it) } - ifNotNull(mainFunctionTag) { writeStringNoTag(it) } commitSingleFileInfo(this@commitFileInfo) } } is CachedFileInfo.ModuleProxyFileCachedInfo -> { moduleHeaderArtifact?.useCodedOutput { - ifNotNull(mainFunctionTag) { writeStringNoTag(it) } commitSingleFileInfo(this@commitFileInfo) } } @@ -282,26 +324,39 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult private fun ModuleArtifact.generateModuleProxyFileCachedInfo( mainFunctionTag: String?, + suiteFunctionTag: String?, + cachedTestFunctionsWithTheirPackage: CachedTestFunctionsWithTheirPackage, importedWithEffectInModuleWithName: String? = null ): CachedFileInfo { val moduleHeader = generateProxyIrModuleWith( moduleExternalName, moduleExternalName, mainFunctionTag, + suiteFunctionTag, + cachedTestFunctionsWithTheirPackage, importedWithEffectInModuleWithName ).makeModuleHeader() return CachedFileInfo.ModuleProxyFileCachedInfo(this, moduleHeader) - .also { it.mainFunctionTag = mainFunctionTag } + .also { + it.mainFunctionTag = mainFunctionTag + it.suiteFunctionTag = suiteFunctionTag + it.packagesToItsTestFunctions = cachedTestFunctionsWithTheirPackage + } } private fun ModuleArtifact.loadFileInfoFor(fileArtifact: SrcFileArtifact): CachedFileInfo.MainFileCachedInfo { val headers = fileArtifact.loadJsIrModuleHeaders(this) + val mainFragment = + headers.mainHeader.associatedModule?.fragments?.single() ?: error("Unexpected multiple fragments inside mainHeader") - val mainCachedFileInfo = CachedFileInfo.MainFileCachedInfo(this, fileArtifact, headers.mainHeader) - .apply { mainFunctionTag = headers.mainFunctionTag } + val mainCachedFileInfo = CachedFileInfo.MainFileCachedInfo(this, fileArtifact, headers.mainHeader).apply { + mainFunctionTag = headers.mainFunctionTag + testEnvironment = mainFragment.testEnvironment + mainFragment.testEnvironment = null + } if (headers.exportHeader != null) { - val tsDeclarationsHash = fileArtifact.loadJsIrFragments()?.exportFragment?.dts?.raw?.cityHash64() + val tsDeclarationsHash = headers.exportHeader.associatedModule?.fragments?.single()?.dts?.raw?.cityHash64() val cachedExportFileInfo = mainCachedFileInfo.readModuleHeaderCache { fetchFileInfoForExportedPart(mainCachedFileInfo) } mainCachedFileInfo.exportFileCachedInfo = if (cachedExportFileInfo?.tsDeclarationsHash != tsDeclarationsHash) { CachedFileInfo.ExportFileCachedInfo( @@ -351,7 +406,7 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult override val CachedFileInfo.artifactName get() = jsIrHeader.externalModuleName override val CachedFileInfo.hasEffect get() = jsIrHeader.importedWithEffectInModuleWithName != null override val CachedFileInfo.hasExport get() = this is CachedFileInfo.MainFileCachedInfo && exportFileCachedInfo != null - override val CachedFileInfo.packageFqn get() = moduleFragmentToExternalName.excludeFileNameFromExternalName(jsIrHeader.moduleName) + override val CachedFileInfo.packageFqn get() = moduleFragmentToExternalName.getPackageFqn(jsIrHeader.moduleName) override val CachedFileInfo.mainFunction get() = when (this) { is CachedFileInfo.MainFileCachedInfo -> mainFunctionTag @@ -359,6 +414,9 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult else -> error("Unexpected CachedFileInfo type ${this::class.simpleName}") } + override fun CachedFileInfo.takeTestEnvironmentOwnership() = + (this as CachedFileInfo.MainFileCachedInfo).testEnvironment + override fun SrcFileArtifact.generateArtifact(module: ModuleArtifact) = when { isModified() -> module.loadFileInfoFor(this) else -> module.fetchFileInfoFor(this) ?: module.loadFileInfoFor(this) @@ -370,10 +428,18 @@ class JsPerFileCache(private val moduleArtifacts: List) : JsMult else -> CachedFileInfo.MainFileCachedInfo.Merged(map { it as CachedFileInfo.MainFileCachedInfo }) } - override fun ModuleArtifact.generateArtifact(mainFunctionTag: String?, moduleNameForEffects: String?) = - fetchModuleProxyFileInfo()?.takeIf { - it.mainFunction == mainFunctionTag && it.jsIrHeader.importedWithEffectInModuleWithName == moduleNameForEffects - } ?: generateModuleProxyFileCachedInfo(mainFunctionTag, moduleNameForEffects) + override fun ModuleArtifact.generateArtifact( + mainFunctionTag: String?, + suiteFunctionTag: String?, + testFunctions: CachedTestFunctionsWithTheirPackage, + moduleNameForEffects: String? + ) = fetchModuleProxyFileInfo()?.takeIf { + it.mainFunctionTag == mainFunctionTag + && it.jsIrHeader.importedWithEffectInModuleWithName == moduleNameForEffects + && suiteFunctionTag == it.suiteFunctionTag && + it.packagesToItsTestFunctions == testFunctions && + it.jsIrHeader.importedWithEffectInModuleWithName == moduleNameForEffects + } ?: generateModuleProxyFileCachedInfo(mainFunctionTag, suiteFunctionTag, testFunctions, moduleNameForEffects) } return perFileGenerator.generatePerFileArtifacts(moduleArtifacts) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt index 2cdb9768af6..470bfac39a4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TestGenerator.kt @@ -46,7 +46,7 @@ class TestGenerator(val context: JsCommonBackendContext, val groupByPackage: Boo if (irFile.declarations.isEmpty()) return ArrayList(irFile.declarations).forEach { if (it is IrClass) { - generateTestCalls(it) { if (groupByPackage) suiteForPackage(irFile) else context.createTestContainerFun(irFile) } + generateTestCalls(it) { if (groupByPackage) suiteForPackage(it) else context.createTestContainerFun(it) } } // TODO top-level functions @@ -55,8 +55,11 @@ class TestGenerator(val context: JsCommonBackendContext, val groupByPackage: Boo private val packageSuites = hashMapOf() - private fun suiteForPackage(irFile: IrFile) = packageSuites.getOrPut(irFile.packageFqName) { - context.suiteFun!!.createInvocation(irFile.packageFqName.asString(), context.createTestContainerFun(irFile)) + private fun suiteForPackage(container: IrDeclaration): IrSimpleFunction { + val irFile = container.file + return packageSuites.getOrPut(irFile.packageFqName) { + context.suiteFun!!.createInvocation(irFile.packageFqName.asString(), context.createTestContainerFun(container)) + } } private fun IrSimpleFunctionSymbol.createInvocation( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt index 5ec62ccd3f3..d9334d38024 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.backend.common.serialization.checkIsFunctionInterfac import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.export.* +import org.jetbrains.kotlin.ir.backend.js.ic.JsPerFileCache import org.jetbrains.kotlin.ir.backend.js.lower.JsCodeOutliningLowering import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering import org.jetbrains.kotlin.ir.backend.js.lower.isBuiltInClass @@ -29,7 +30,6 @@ import org.jetbrains.kotlin.js.sourceMap.SourceMapBuilderConsumer import org.jetbrains.kotlin.js.util.TextOutputImpl import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.utils.memoryOptimizedMap -import org.jetbrains.kotlin.utils.putToMultiMap import org.jetbrains.kotlin.utils.addToStdlib.runIf import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.io.File @@ -51,15 +51,28 @@ val IrModuleFragment.safeName: String fun generateProxyIrModuleWith( safeName: String, externalName: String, - mainFunction: String?, + mainFunctionTag: String?, + suiteFunctionTag: String? = null, + cachedTestFunctionsWithTheirPackage: CachedTestFunctionsWithTheirPackage = emptyMap(), importedWithEffectInModuleWithName: String? = null ): JsIrModule { val programFragment = JsIrProgramFragment(safeName, "").apply { - mainFunction?.let { - this.mainFunction = it - this.nameBindings[it] = JsName("main", true) + mainFunctionTag?.let { + this.mainFunctionTag = it + nameBindings[it] = ReservedJsNames.makeMainFunctionName() + } + cachedTestFunctionsWithTheirPackage.takeIf { it.isNotEmpty() }?.let { + nameBindings += it.values.asSequence() + .flatten() + .map { tag -> tag to ReservedJsNames.makeTestFunctionName() } + .plus(suiteFunctionTag!! to ReservedJsNames.makeSuiteFunctionName()) + + JsTestFunctionTransformer.generateTestFunctionCall( + it.asTestFunctionContainers(suiteFunctionTag, nameBindings) + )?.run { declarations.statements += makeStmt() } } } + return JsIrModule( safeName, externalName, @@ -276,25 +289,35 @@ class IrModuleToJsTransformer( } private fun generateJsIrProgramPerFile(exportData: List, mode: TranslationMode): JsIrProgram { - val mainModule = exportData.last() + val mainModuleWithExportedData = exportData.last() val perFileGenerator = object : PerFileGenerator { - override val mainModuleName get() = mainModule.fragment.safeName + override val mainModuleName = mainModuleWithExportedData.fragment.safeName + private val JsIrModules.mainFragment get() = mainModule.fragments.first() - override val IrAndExportedDeclarations.isMain get() = this === mainModule + override val IrAndExportedDeclarations.isMain get() = this === mainModuleWithExportedData override val IrAndExportedDeclarations.fileList get() = files - override val JsIrModules.artifactName get() = this.mainModule.externalModuleName - override val JsIrModules.hasEffect get() = this.mainModule.importedWithEffectInModuleWithName != null - override val JsIrModules.hasExport get() = this.exportModule != null - override val JsIrModules.packageFqn get() = this.mainModule.fragments.first().packageFqn - override val JsIrModules.mainFunction get() = this.mainModule.fragments.first().mainFunction + override val JsIrModules.artifactName get() = mainModule.externalModuleName + override val JsIrModules.hasEffect get() = mainModule.importedWithEffectInModuleWithName != null + override val JsIrModules.hasExport get() = exportModule != null + override val JsIrModules.packageFqn get() = mainFragment.packageFqn + override val JsIrModules.mainFunction get() = mainFragment.mainFunctionTag + + override fun JsIrModules.takeTestEnvironmentOwnership(): JsIrProgramTestEnvironment? { + val fragment = mainFragment + return fragment.testEnvironment.also { fragment.testEnvironment = null } + } override fun List.merge() = JsIrModules(map { it.mainModule }.merge(), mapNotNull { it.exportModule }.ifNotEmpty { merge() }) - override fun IrAndExportedDeclarations.generateArtifact(mainFunctionTag: String?, moduleNameForEffects: String?) = - JsIrModules(toJsIrProxyModule(mainFunctionTag, moduleNameForEffects)) + override fun IrAndExportedDeclarations.generateArtifact( + mainFunctionTag: String?, + suiteFunctionTag: String?, + testFunctions: CachedTestFunctionsWithTheirPackage, + moduleNameForEffects: String? + ) = JsIrModules(toJsIrProxyModule(mainFunctionTag, suiteFunctionTag, testFunctions, moduleNameForEffects)) override fun IrFileExports.generateArtifact(module: IrAndExportedDeclarations) = takeIf { !file.couldBeSkipped() } ?.let { generateProgramFragment(it, mode) } @@ -332,13 +355,17 @@ class IrModuleToJsTransformer( } private fun IrAndExportedDeclarations.toJsIrProxyModule( - mainFunction: String?, + mainFunctionTag: String?, + suiteFunctionTag: String?, + cachedTestFunctionsWithTheirPackage: CachedTestFunctionsWithTheirPackage, importedWithEffectInModuleWithName: String? = null ): JsIrModule { return generateProxyIrModuleWith( fragment.safeName, moduleFragmentToNameMapper.getExternalNameFor(fragment), - mainFunction, + mainFunctionTag, + suiteFunctionTag, + cachedTestFunctionsWithTheirPackage, importedWithEffectInModuleWithName ) } @@ -422,12 +449,6 @@ class IrModuleToJsTransformer( result.initializers.statements += staticContext.initializerBlock.statements result.eagerInitializers.statements += staticContext.eagerInitializerBlock.statements - - backendContext.testFunsPerFile[fileExports.file]?.let { - result.testFunInvocation = JsInvocation(staticContext.getNameForStaticFunction(it).makeRef()).makeStmt() - result.suiteFn = staticContext.getNameForStaticFunction(backendContext.suiteFun!!.owner) - } - result.importedModules += nameGenerator.importedModules val definitionSet = fileExports.file.declarations.toSet() @@ -435,9 +456,16 @@ class IrModuleToJsTransformer( if (shouldReferMainFunction) { JsMainFunctionDetector(backendContext).getMainFunctionOrNull(fileExports.file) ?.let { backendContext.mapping.mainFunctionToItsWrapper[it] } - ?.let { result.mainFunction = definitionSet.computeTag(it) } + ?.let { result.mainFunctionTag = definitionSet.computeTag(it) } } + backendContext.testFunsPerFile[fileExports.file] + ?.let { definitionSet.computeTag(it) } + ?.let { + val suiteFunctionTag = definitionSet.computeTag(backendContext.suiteFun!!.owner) ?: error("Expect suite function tag exists") + result.testEnvironment = JsIrProgramTestEnvironment(it, suiteFunctionTag) + } + result.computeAndSaveNameBindings(definitionSet, nameGenerator) result.computeAndSaveImports(definitionSet, nameGenerator) result.computeAndSaveDefinitions(definitionSet, fileExports) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt index e59eb02f1c5..113d293253b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsIrProgramFragment.kt @@ -14,6 +14,11 @@ import org.jetbrains.kotlin.serialization.js.ModuleKind class JsIrProgramFragments(val mainFragment: JsIrProgramFragment, val exportFragment: JsIrProgramFragment? = null) +data class JsIrProgramTestEnvironment( + val testFunctionTag: String, + val suiteFunctionTag: String +) + class JsIrProgramFragment(val name: String, val packageFqn: String) { val nameBindings = mutableMapOf() val optionalCrossModuleImports = hashSetOf() @@ -25,11 +30,10 @@ class JsIrProgramFragment(val name: String, val packageFqn: String) { val classes = mutableMapOf() val initializers = JsCompositeBlock() val eagerInitializers = JsCompositeBlock() - var mainFunction: String? = null - var testFunInvocation: JsStatement? = null - var suiteFn: JsName? = null + var mainFunctionTag: String? = null val definitions = mutableSetOf() val polyfills = JsCompositeBlock() + var testEnvironment: JsIrProgramTestEnvironment? = null } class JsIrModule( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsTestFunctionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsTestFunctionTransformer.kt new file mode 100644 index 00000000000..8c198b1783a --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/JsTestFunctionTransformer.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.ir.backend.js.transformers.irToJs + +import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope +import org.jetbrains.kotlin.js.backend.ast.* + +object JsTestFunctionTransformer { + fun generateTestFunctionCall(testFunctionContainers: List): JsInvocation? { + if (testFunctionContainers.isEmpty()) return null + + val testFunBody = JsBlock() + val testFun = JsFunction(emptyScope, testFunBody, "root test fun") + val suiteFunRef = testFunctionContainers.firstNotNullOf { it.suiteFunctionName }.makeRef() + + val tests = testFunctionContainers.groupBy({ it.packageFqn }) { + JsInvocation(it.testFunctionName.makeRef()).makeStmt() + } // String -> [IrSimpleFunction] + + for ((pkg, testCalls) in tests) { + val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg") + pkgTestFun.body.statements += testCalls + testFun.body.statements += JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt() + } + + return JsInvocation(testFun) + } + + class TestFunctionContainer( + val packageFqn: String, + val testFunctionName: JsName, + val suiteFunctionName: JsName + ) +} + +private fun Map.getTestFunctionBySignature(signature: String?): JsName { + return get(signature) ?: error("Null test functions should be filtered on a previous step") +} + +private fun Map.getSuiteFunctionBySignature(signature: String?): JsName { + return get(signature) ?: error("A Suite function signature should be present if a test function signature does") +} + +fun List.asTestFunctionContainers(): List { + return mapNotNull { fragment -> + fragment.testEnvironment?.let { + JsTestFunctionTransformer.TestFunctionContainer( + fragment.packageFqn, + fragment.nameBindings.getTestFunctionBySignature(it.testFunctionTag), + fragment.nameBindings.getSuiteFunctionBySignature(it.suiteFunctionTag) + ) + } + } +} + +fun CachedTestFunctionsWithTheirPackage.asTestFunctionContainers( + suiteFunction: String?, + nameBindings: Map +): List { + return entries.flatMap { (packageFqn, testFunctions) -> + testFunctions.map { + JsTestFunctionTransformer.TestFunctionContainer( + packageFqn, + nameBindings.getTestFunctionBySignature(it), + nameBindings.getSuiteFunctionBySignature(suiteFunction), + ) + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt index 14ba1258192..58889e1e2c4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/Merger.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs -import org.jetbrains.kotlin.idea.MainFunctionDetector import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope import org.jetbrains.kotlin.js.backend.ast.* @@ -61,8 +60,6 @@ class Merger( rename(f.initializers) rename(f.eagerInitializers) - f.testFunInvocation?.let { rename(it) } - f.suiteFn?.let { f.suiteFn = rename(it) } } } @@ -229,27 +226,14 @@ class Merger( moduleBody.addWithComment("block: init", initializerBlock.statements) // Merge test function invocations - if (fragments.any { it.testFunInvocation != null }) { - val testFunBody = JsBlock() - val testFun = JsFunction(emptyScope, testFunBody, "root test fun") - val suiteFunRef = fragments.firstNotNullOf { it.suiteFn }.makeRef() - - val tests = fragments.filter { it.testFunInvocation != null } - .groupBy({ it.packageFqn }) { it.testFunInvocation } // String -> [IrSimpleFunction] - - for ((pkg, testCalls) in tests) { - val pkgTestFun = JsFunction(emptyScope, JsBlock(), "test fun for $pkg") - pkgTestFun.body.statements += testCalls - testFun.body.statements += JsInvocation(suiteFunRef, JsStringLiteral(pkg), JsBooleanLiteral(false), pkgTestFun).makeStmt() - } - + JsTestFunctionTransformer.generateTestFunctionCall(fragments.asTestFunctionContainers())?.let { moduleBody.startRegion("block: tests") - moduleBody += JsInvocation(testFun).makeStmt() + moduleBody += it.makeStmt() moduleBody.endRegion() } val fragmentWithMainFunction = JsMainFunctionDetector.pickMainFunctionFromCandidates(fragments) { - JsMainFunctionDetector.MainFunctionCandidate(it.packageFqn, it.mainFunction) + JsMainFunctionDetector.MainFunctionCandidate(it.packageFqn, it.mainFunctionTag) } val exportStatements = declareAndCallJsExporter() + additionalExports + transitiveJsExport() @@ -271,7 +255,7 @@ class Merger( statements += moduleBody statements.addWithComment("block: exports", exportStatements) if (generateCallToMain && fragmentWithMainFunction != null) { - val mainFunctionTag = fragmentWithMainFunction.mainFunction ?: error("Expect to have main function signature at this point") + val mainFunctionTag = fragmentWithMainFunction.mainFunctionTag ?: error("Expect to have main function signature at this point") val mainFunctionName = fragmentWithMainFunction.nameBindings[mainFunctionTag] ?: error("Expect to have name binding for tag $mainFunctionTag") statements += JsInvocation(mainFunctionName.makeRef()).makeStmt() } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt index 23494782af9..dca0ec4c110 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ModuleFragmentToExternalName.kt @@ -42,10 +42,6 @@ class ModuleFragmentToExternalName(private val jsOutputNamesMapping: Map> + interface PerFileGenerator { val mainModuleName: String @@ -21,9 +23,16 @@ interface PerFileGenerator { val Artifact.packageFqn: String val Artifact.mainFunction: String? + fun Artifact.takeTestEnvironmentOwnership(): JsIrProgramTestEnvironment? + fun List.merge(): Artifact fun File.generateArtifact(module: Module): Artifact? - fun Module.generateArtifact(mainFunctionTag: String?, moduleNameForEffects: String?): Artifact + fun Module.generateArtifact( + mainFunctionTag: String?, + suiteFunctionTag: String?, + testFunctions: CachedTestFunctionsWithTheirPackage, + moduleNameForEffects: String? + ): Artifact fun generatePerFileArtifacts(modules: List): List { var someModuleHasEffect = false @@ -32,6 +41,8 @@ interface PerFileGenerator { for (module in modules) { var hasModuleLevelEffect = false var hasFileWithExportedDeclaration = false + var suiteFunctionTag: String? = null + val testFunctions = mutableMapOf>() val artifacts = module.fileList.mapNotNull { val generatedArtifact = it.generateArtifact(module) ?: return@mapNotNull null @@ -44,6 +55,11 @@ interface PerFileGenerator { hasModuleLevelEffect = true } + generatedArtifact.takeTestEnvironmentOwnership()?.let { (testFunction, suiteFunction) -> + testFunctions.putToMultiMap(generatedArtifact.packageFqn, testFunction) + suiteFunctionTag = suiteFunction + } + putToMultiMap(generatedArtifact.artifactName, generatedArtifact) generatedArtifact @@ -54,19 +70,21 @@ interface PerFileGenerator { } val mainFunctionTag = runIf(module.isMain) { - JsMainFunctionDetector - .pickMainFunctionFromCandidates(artifacts) { - JsMainFunctionDetector.MainFunctionCandidate( - it.packageFqn, - it.mainFunction - ) - } - ?.mainFunction + JsMainFunctionDetector.pickMainFunctionFromCandidates(artifacts) { + JsMainFunctionDetector.MainFunctionCandidate( + it.packageFqn, + it.mainFunction + ) + }?.mainFunction } - if (mainFunctionTag != null || hasFileWithExportedDeclaration || hasModuleLevelEffect || (module.isMain && someModuleHasEffect)) { - val proxyArtifact = - module.generateArtifact(mainFunctionTag, mainModuleName.takeIf { !module.isMain && hasModuleLevelEffect }) ?: continue + if (mainFunctionTag != null || hasFileWithExportedDeclaration || hasModuleLevelEffect || suiteFunctionTag != null || (module.isMain && someModuleHasEffect)) { + val proxyArtifact = module.generateArtifact( + mainFunctionTag, + suiteFunctionTag, + testFunctions, + mainModuleName.takeIf { !module.isMain && hasModuleLevelEffect } + ) ?: continue putToMultiMap(proxyArtifact.artifactName, proxyArtifact) } } @@ -74,4 +92,4 @@ interface PerFileGenerator { return nameToModulePerFile.values.map { it.merge() } } -} +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ReservedJsNames.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ReservedJsNames.kt index d77c9f2aab7..390772bbe31 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ReservedJsNames.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/ReservedJsNames.kt @@ -13,5 +13,8 @@ class ReservedJsNames { fun makeInternalModuleName() = JsName("_", false) fun makeJsExporterName() = JsName("\$jsExportAll\$", false) fun makeCrossModuleNameRef(moduleName: JsName) = JsNameRef("\$_\$", moduleName.makeRef()) + fun makeMainFunctionName() = JsName("main", true) + fun makeTestFunctionName() = JsName("test", true) + fun makeSuiteFunctionName() = JsName("suite", true) } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt index e2356a56c1a..9108028ae00 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstDeserializer.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlin.ir.backend.js.export.TypeScriptFragment import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragments +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramTestEnvironment import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.* @@ -103,10 +104,9 @@ private class JsIrAstDeserializer(private val source: ByteArray) { readRepeated { optionalCrossModuleImports.add(stringTable[readInt()]) } readRepeated { classes[nameTable[readInt()]] = readIrIcClassModel() } - ifTrue { testFunInvocation = readStatement() } - ifTrue { mainFunction = readString() } + ifTrue { mainFunctionTag = readString() } + ifTrue { testEnvironment = readTestEnvironment() } ifTrue { dts = TypeScriptFragment(readString()) } - ifTrue { suiteFn = nameTable[readInt()] } readRepeated { definitions += stringTable[readInt()] } } @@ -119,6 +119,10 @@ private class JsIrAstDeserializer(private val source: ByteArray) { } } + private fun readTestEnvironment(): JsIrProgramTestEnvironment { + return JsIrProgramTestEnvironment(stringTable[readInt()], stringTable[readInt()]) + } + private fun readStatement(): JsStatement { return withComments { withLocation { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt index e88b14139ae..40b4bdd4a44 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/utils/serialization/JsIrAstSerializer.kt @@ -151,22 +151,19 @@ private class JsIrAstSerializer { writeIrIcModel(model) } - ifNotNull(fragment.testFunInvocation) { - writeStatement(it) + ifNotNull(fragment.mainFunctionTag) { + writeString(it) } - ifNotNull(fragment.mainFunction) { - writeString(it) + ifNotNull(fragment.testEnvironment) { + writeInt(internalizeString(it.testFunctionTag)) + writeInt(internalizeString(it.suiteFunctionTag)) } ifNotNull(fragment.dts) { writeString(it.raw) } - ifNotNull(fragment.suiteFn) { - writeInt(internalizeName(it)) - } - writeCollection(fragment.definitions) { writeInt(internalizeString(it)) } diff --git a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt index 6428c8ced2d..dfce0ffd6ea 100644 --- a/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt +++ b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmBackendContext.kt @@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.types.IrTypeSystemContext import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.addChild +import org.jetbrains.kotlin.ir.util.file import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -182,7 +183,8 @@ class WasmBackendContext( val testEntryPoints: Collection get() = testContainerFuns.values - override fun createTestContainerFun(irFile: IrFile): IrSimpleFunction { + override fun createTestContainerFun(container: IrDeclaration): IrSimpleFunction { + val irFile = container.file val module = irFile.module return testContainerFuns.getOrPut(module) { val file = syntheticFile("tests", module) diff --git a/js/js.tests/build.gradle.kts b/js/js.tests/build.gradle.kts index bcac8d9fa99..5cde77af724 100644 --- a/js/js.tests/build.gradle.kts +++ b/js/js.tests/build.gradle.kts @@ -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/*") diff --git a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt index bae9eb25007..c2a12bbcfe3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt @@ -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() 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>, testInfo: List) { - 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() @@ -260,7 +262,7 @@ abstract class AbstractInvalidationTest( } private fun verifyJsExecutableProducerBuildModules(stepId: Int, gotRebuilt: List, expectedRebuilt: List) { - 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, diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerFileTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerFileTestGenerated.java index 42f0e2dc7e9..d1196117c87 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerFileTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerFileTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerModuleTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerModuleTestGenerated.java index a3606490afa..a87673c206a 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerModuleTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsFirInvalidationPerModuleTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerFileTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerFileTestGenerated.java index 0e1fdaf7529..c906b85a7fd 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerFileTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerFileTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerModuleTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerModuleTestGenerated.java index 6bcc353a558..5b21b6e6e2e 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerModuleTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationPerModuleTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerFileTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerFileTestGenerated.java index bcc7e9c3f37..818858634bf 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerFileTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerFileTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerModuleTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerModuleTestGenerated.java index cf6488fd6f5..3e01ed6cce9 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerModuleTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationPerModuleTestGenerated.java @@ -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 { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index 20bc9e69554..820c4b48bcc 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -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") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java index d77c3a86652..393706893c0 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsES6BoxTestGenerated.java @@ -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") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 2721135823f..bbdb1ee36b9 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -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") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index fafd8d2dd29..1442e363060 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -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") diff --git a/js/js.translator/testData/box/esModules/kotlin.test/_common.kt b/js/js.translator/testData/box/esModules/kotlin.test/_common.kt new file mode 100644 index 00000000000..50a9324835e --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/_common.kt @@ -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() + + 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 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" + } +} diff --git a/js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt b/js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt new file mode 100644 index 00000000000..517e0c4a1d8 --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/beforeAfter.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/ignore.kt b/js/js.translator/testData/box/esModules/kotlin.test/ignore.kt new file mode 100644 index 00000000000..f93740a392b --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/ignore.kt @@ -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) + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt b/js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt new file mode 100644 index 00000000000..97363b4bfd7 --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/illegalParameters.kt @@ -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") + } + } + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/incremental.kt b/js/js.translator/testData/box/esModules/kotlin.test/incremental.kt new file mode 100644 index 00000000000..bb960406329 --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/incremental.kt @@ -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") + } + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/inherited.kt b/js/js.translator/testData/box/esModules/kotlin.test/inherited.kt new file mode 100644 index 00000000000..955612431bd --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/inherited.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/mpp.kt b/js/js.translator/testData/box/esModules/kotlin.test/mpp.kt new file mode 100644 index 00000000000..64fe8e5496b --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/mpp.kt @@ -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") + } +} diff --git a/js/js.translator/testData/box/esModules/kotlin.test/nested.kt b/js/js.translator/testData/box/esModules/kotlin.test/nested.kt new file mode 100644 index 00000000000..b23fbc98166 --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/nested.kt @@ -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") + } + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt b/js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt new file mode 100644 index 00000000000..4a550f8a6c9 --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/returnTestResult.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/box/esModules/kotlin.test/simple.kt b/js/js.translator/testData/box/esModules/kotlin.test/simple.kt new file mode 100644 index 00000000000..bf4816e0224 --- /dev/null +++ b/js/js.translator/testData/box/esModules/kotlin.test/simple.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/main/common.kt b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/common.kt new file mode 100644 index 00000000000..ad37d9a7110 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/common.kt @@ -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() + + 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 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" + } +} diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/main/m.kt b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/m.kt new file mode 100644 index 00000000000..321210d7221 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/m.kt @@ -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" + } diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/main/module.info b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/module.info new file mode 100644 index 00000000000..ec8fbf43696 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/module.info @@ -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 diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.1.kt b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.1.kt new file mode 100644 index 00000000000..4908bd22522 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.1.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") + } +} diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.2.kt b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.2.kt new file mode 100644 index 00000000000..76460d82ee1 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test1.2.kt @@ -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") + } +} diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test2.3.kt b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test2.3.kt new file mode 100644 index 00000000000..5b8938a43c8 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/main/test2.3.kt @@ -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") + } +} diff --git a/js/js.translator/testData/incremental/invalidation/kotlinTest/project.info b/js/js.translator/testData/incremental/invalidation/kotlinTest/project.info new file mode 100644 index 00000000000..c1267cd540f --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/kotlinTest/project.info @@ -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 diff --git a/libraries/kotlin.test/js-ir/build.gradle.kts b/libraries/kotlin.test/js-ir/build.gradle.kts index 67f218c8dd9..64442af5020 100644 --- a/libraries/kotlin.test/js-ir/build.gradle.kts +++ b/libraries/kotlin.test/js-ir/build.gradle.kts @@ -1,3 +1,4 @@ +import org.gradle.api.tasks.bundling.Jar import org.jetbrains.kotlin.gradle.dsl.KotlinCompile plugins { diff --git a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupIrJsBoxTests.kt b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupIrJsBoxTests.kt index 1bdb782b002..cdf3378ff8d 100644 --- a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupIrJsBoxTests.kt +++ b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupIrJsBoxTests.kt @@ -15,6 +15,7 @@ fun Test.useJsIrBoxTests( setupV8() dependsOn(":kotlin-stdlib:jsJar") dependsOn(":kotlin-stdlib:jsJarForTests") // TODO: think how to remove dependency on the artifact in this place + dependsOn(":kotlin-test:kotlin-test-js-ir:jsJar") dependsOn(":kotlin-test:kotlin-test-js-ir:compileKotlinJs") dependsOn(":kotlin-stdlib-js-ir-minimal-for-test:compileKotlinJs") dependsOn(":kotlin-dom-api-compat:compileKotlinJs") @@ -24,5 +25,6 @@ fun Test.useJsIrBoxTests( systemProperty("kotlin.js.reduced.stdlib.path", reducedStdlibPath) systemProperty("kotlin.js.kotlin.test.path", kotlinJsTestPath) systemProperty("kotlin.js.stdlib.klib.path", "libraries/stdlib/build/libs/kotlin-stdlib-js-$version.klib") + systemProperty("kotlin.js.kotlin.test.klib.path", "libraries/kotlin.test/js-ir/build/libs/kotlin-test-js-$version.klib") systemProperty("kotlin.js.dom.api.compat", domApiCompatPath) } diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmJsTranslatorTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmJsTranslatorTestGenerated.java index 10a4437ae2e..21634d20180 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmJsTranslatorTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/FirWasmJsTranslatorTestGenerated.java @@ -357,6 +357,70 @@ public class FirWasmJsTranslatorTestGenerated extends AbstractFirWasmJsTranslato } } + @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.WASM, 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") diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmJsTranslatorTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmJsTranslatorTestGenerated.java index eba9a2966a3..dbf3cc32acb 100644 --- a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmJsTranslatorTestGenerated.java +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmJsTranslatorTestGenerated.java @@ -357,6 +357,70 @@ public class K1WasmJsTranslatorTestGenerated extends AbstractK1WasmJsTranslatorT } } + @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.WASM, 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")