[Wasm] Add compiler support for the kotlin.test library
This commit is contained in:
committed by
TeamCityServer
parent
7943298240
commit
bede039c08
@@ -323,7 +323,10 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
||||
val runner = """
|
||||
export default WebAssembly.instantiateStreaming(fetch('${outputWasmFile.name}'), { runtime, js_code }).then((it) => {
|
||||
wasmInstance = it.instance;
|
||||
|
||||
wasmInstance.exports.startUnitTests?.();
|
||||
wasmInstance.exports.main?.();
|
||||
|
||||
return it.instance.exports;
|
||||
});
|
||||
""".trimIndent()
|
||||
|
||||
+5
@@ -41,6 +41,11 @@ interface JsCommonBackendContext : CommonBackendContext {
|
||||
|
||||
val es6mode: Boolean
|
||||
get() = false
|
||||
|
||||
val suiteFun: IrSimpleFunctionSymbol?
|
||||
val testFun: IrSimpleFunctionSymbol?
|
||||
|
||||
fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction
|
||||
}
|
||||
|
||||
// TODO: investigate if it could be removed
|
||||
|
||||
@@ -123,7 +123,7 @@ class JsIrBackendContext(
|
||||
|
||||
private val testContainerFuns = mutableMapOf<IrModuleFragment, IrSimpleFunction>()
|
||||
|
||||
fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction {
|
||||
override fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction {
|
||||
return testContainerFuns.getOrPut(module) {
|
||||
val file = syntheticFile("tests", module)
|
||||
irFactory.addFunction(file) {
|
||||
@@ -325,8 +325,8 @@ class JsIrBackendContext(
|
||||
val throwISEsymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_ISE"))).single())
|
||||
val throwIAEsymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_IAE"))).single())
|
||||
|
||||
val suiteFun = getFunctions(FqName("kotlin.test.suite")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
val testFun = getFunctions(FqName("kotlin.test.test")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
override val suiteFun = getFunctions(FqName("kotlin.test.suite")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
override val testFun = getFunctions(FqName("kotlin.test.test")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
|
||||
val primitiveClassProperties by lazy2 {
|
||||
primitiveClassesObject.owner.declarations.filterIsInstance<IrProperty>()
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
@@ -26,7 +27,7 @@ import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
fun generateTests(context: JsIrBackendContext, moduleFragment: IrModuleFragment) {
|
||||
fun generateTests(context: JsCommonBackendContext, moduleFragment: IrModuleFragment) {
|
||||
val generator = TestGenerator(context) { context.createTestContainerFun(moduleFragment) }
|
||||
|
||||
moduleFragment.files.toList().forEach {
|
||||
@@ -34,7 +35,7 @@ fun generateTests(context: JsIrBackendContext, moduleFragment: IrModuleFragment)
|
||||
}
|
||||
}
|
||||
|
||||
class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: () -> IrSimpleFunction) : FileLoweringPass {
|
||||
class TestGenerator(val context: JsCommonBackendContext, val testContainerFactory: () -> IrSimpleFunction) : FileLoweringPass {
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.declarations.forEach {
|
||||
@@ -61,7 +62,7 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
|
||||
|
||||
val function = context.irFactory.buildFun {
|
||||
this.name = Name.identifier("$name test fun")
|
||||
this.returnType = context.irBuiltIns.anyNType
|
||||
this.returnType = if (this@createInvocation == context.suiteFun!!) context.irBuiltIns.unitType else context.irBuiltIns.anyNType
|
||||
this.origin = JsIrBuilder.SYNTHESIZED_DECLARATION
|
||||
}
|
||||
function.parent = parentFunction
|
||||
|
||||
+54
-4
@@ -17,14 +17,14 @@ import org.jetbrains.kotlin.ir.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonCoroutineSymbols
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsMapping
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.JsInnerClassesSupport
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
|
||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
|
||||
@@ -119,4 +119,54 @@ class WasmBackendContext(
|
||||
/*TODO*/
|
||||
print(message)
|
||||
}
|
||||
|
||||
//
|
||||
// Unit test support, mostly borrowed from the JS implementation
|
||||
//
|
||||
|
||||
override val suiteFun: IrSimpleFunctionSymbol?
|
||||
get() = wasmSymbols.suiteFun
|
||||
override val testFun: IrSimpleFunctionSymbol?
|
||||
get() = wasmSymbols.testFun
|
||||
|
||||
private fun syntheticFile(name: String, module: IrModuleFragment): IrFile {
|
||||
return IrFileImpl(object : IrFileEntry {
|
||||
override val name = "<$name>"
|
||||
override val maxOffset = UNDEFINED_OFFSET
|
||||
|
||||
override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int) =
|
||||
SourceRangeInfo(
|
||||
"",
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET
|
||||
)
|
||||
|
||||
override fun getLineNumber(offset: Int) = UNDEFINED_OFFSET
|
||||
override fun getColumnNumber(offset: Int) = UNDEFINED_OFFSET
|
||||
}, internalPackageFragmentDescriptor, module).also {
|
||||
module.files += it
|
||||
}
|
||||
}
|
||||
|
||||
private val testContainerFuns = mutableMapOf<IrModuleFragment, IrSimpleFunction>()
|
||||
|
||||
val testEntryPoints: Collection<IrSimpleFunction>
|
||||
get() = testContainerFuns.values
|
||||
|
||||
override fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction {
|
||||
return testContainerFuns.getOrPut(module) {
|
||||
val file = syntheticFile("tests", module)
|
||||
irFactory.addFunction(file) {
|
||||
name = Name.identifier("testContainer")
|
||||
returnType = irBuiltIns.unitType
|
||||
origin = JsIrBuilder.SYNTHESIZED_DECLARATION
|
||||
}.apply {
|
||||
body = irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,12 @@ private val validateIrAfterLowering = makeCustomWasmModulePhase(
|
||||
description = "Validate IR after lowering"
|
||||
)
|
||||
|
||||
private val generateTests = makeCustomWasmModulePhase(
|
||||
{ context, module -> generateWasmTests(context, module) },
|
||||
name = "GenerateTests",
|
||||
description = "Generates code to execute kotlin.test cases"
|
||||
)
|
||||
|
||||
private val expectDeclarationsRemovingPhase = makeWasmModulePhase(
|
||||
::ExpectDeclarationsRemoveLowering,
|
||||
name = "ExpectDeclarationsRemoving",
|
||||
@@ -469,6 +475,7 @@ val wasmPhases = NamedCompilerPhase(
|
||||
name = "IrModuleLowering",
|
||||
description = "IR module lowering",
|
||||
lower = validateIrBeforeLowering then
|
||||
generateTests then
|
||||
excludeDeclarationsFromCodegenPhase then
|
||||
expectDeclarationsRemovingPhase then
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
class WasmSymbols(
|
||||
context: WasmBackendContext,
|
||||
@@ -39,7 +40,8 @@ class WasmSymbols(
|
||||
context.module.getPackage(StandardNames.COLLECTIONS_PACKAGE_FQ_NAME)
|
||||
private val builtInsPackage: PackageViewDescriptor =
|
||||
context.module.getPackage(StandardNames.BUILT_INS_PACKAGE_FQ_NAME)
|
||||
|
||||
private val kotlinTestPackage: PackageViewDescriptor =
|
||||
context.module.getPackage(FqName("kotlin.test"))
|
||||
|
||||
override val throwNullPointerException = getInternalFunction("THROW_NPE")
|
||||
override val throwISE = getInternalFunction("THROW_ISE")
|
||||
@@ -131,6 +133,10 @@ class WasmSymbols(
|
||||
|
||||
val stringGetLiteral = getFunction("stringLiteral", builtInsPackage)
|
||||
|
||||
val testFun = maybeGetFunction("test", kotlinTestPackage)
|
||||
val suiteFun = maybeGetFunction("suite", kotlinTestPackage)
|
||||
val startUnitTests = maybeGetFunction("startUnitTests", kotlinTestPackage)
|
||||
|
||||
val wasmClassId = getInternalFunction("wasmClassId")
|
||||
val wasmInterfaceId = getInternalFunction("wasmInterfaceId")
|
||||
|
||||
@@ -211,10 +217,17 @@ class WasmSymbols(
|
||||
findProperty(context.module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single()
|
||||
|
||||
private fun getFunction(name: String, ownerPackage: PackageViewDescriptor): IrSimpleFunctionSymbol {
|
||||
val tmp = findFunctions(ownerPackage.memberScope, Name.identifier(name)).single()
|
||||
return symbolTable.referenceSimpleFunction(tmp)
|
||||
return maybeGetFunction(name, ownerPackage) ?: throw IllegalArgumentException("Function $name not found")
|
||||
}
|
||||
|
||||
private fun maybeGetFunction(name: String, ownerPackage: PackageViewDescriptor): IrSimpleFunctionSymbol? {
|
||||
val tmp = findFunctions(ownerPackage.memberScope, Name.identifier(name))
|
||||
if (tmp.isEmpty())
|
||||
return null
|
||||
return symbolTable.referenceSimpleFunction(tmp.single())
|
||||
}
|
||||
|
||||
|
||||
private fun getInternalFunction(name: String) = getFunction(name, wasmInternalPackage)
|
||||
|
||||
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName))
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.backend.wasm.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.TestGenerator
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
|
||||
|
||||
fun generateWasmTests(context: WasmBackendContext, moduleFragment: IrModuleFragment) {
|
||||
val generator = TestGenerator(context) { context.createTestContainerFun(moduleFragment) }
|
||||
|
||||
moduleFragment.files.toList().forEach {
|
||||
generator.lower(it)
|
||||
}
|
||||
|
||||
if (context.testEntryPoints.isEmpty())
|
||||
return
|
||||
require(context.wasmSymbols.startUnitTests != null) { "kotlin.test package must be present" }
|
||||
|
||||
val builder = context.createIrBuilder(context.wasmSymbols.startUnitTests)
|
||||
val startFunctionBody = context.wasmSymbols.startUnitTests.owner.body as IrBlockBody
|
||||
|
||||
context.testEntryPoints.forEach { testEntry ->
|
||||
startFunctionBody.statements.add(
|
||||
builder.irCall(testEntry)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -192,6 +192,8 @@ abstract class BasicWasmBoxTest(
|
||||
|
||||
const ${sanitizeName(config.moduleId)} = wasmInstance.exports;
|
||||
|
||||
wasmInstance.exports.startUnitTests?.();
|
||||
|
||||
const actualResult = importStringFromWasm(wasmInstance.exports.$testFunction());
|
||||
if (actualResult !== "OK")
|
||||
throw `Wrong box result '${'$'}{actualResult}'; Expected "OK"`;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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 kotlin.js
|
||||
|
||||
@ExperimentalStdlibApi
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
@SinceKotlin("1.6")
|
||||
public actual annotation class JsEagerInitialization
|
||||
Reference in New Issue
Block a user