[Wasm] Add compiler support for the kotlin.test library

This commit is contained in:
Igor Laevsky
2021-10-08 19:36:18 +03:00
committed by TeamCityServer
parent 7943298240
commit bede039c08
10 changed files with 140 additions and 13 deletions
@@ -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()
@@ -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>()
@@ -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
@@ -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"`;
+12
View File
@@ -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