[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 = """
|
val runner = """
|
||||||
export default WebAssembly.instantiateStreaming(fetch('${outputWasmFile.name}'), { runtime, js_code }).then((it) => {
|
export default WebAssembly.instantiateStreaming(fetch('${outputWasmFile.name}'), { runtime, js_code }).then((it) => {
|
||||||
wasmInstance = it.instance;
|
wasmInstance = it.instance;
|
||||||
|
|
||||||
|
wasmInstance.exports.startUnitTests?.();
|
||||||
wasmInstance.exports.main?.();
|
wasmInstance.exports.main?.();
|
||||||
|
|
||||||
return it.instance.exports;
|
return it.instance.exports;
|
||||||
});
|
});
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|||||||
+5
@@ -41,6 +41,11 @@ interface JsCommonBackendContext : CommonBackendContext {
|
|||||||
|
|
||||||
val es6mode: Boolean
|
val es6mode: Boolean
|
||||||
get() = false
|
get() = false
|
||||||
|
|
||||||
|
val suiteFun: IrSimpleFunctionSymbol?
|
||||||
|
val testFun: IrSimpleFunctionSymbol?
|
||||||
|
|
||||||
|
fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: investigate if it could be removed
|
// TODO: investigate if it could be removed
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class JsIrBackendContext(
|
|||||||
|
|
||||||
private val testContainerFuns = mutableMapOf<IrModuleFragment, IrSimpleFunction>()
|
private val testContainerFuns = mutableMapOf<IrModuleFragment, IrSimpleFunction>()
|
||||||
|
|
||||||
fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction {
|
override fun createTestContainerFun(module: IrModuleFragment): IrSimpleFunction {
|
||||||
return testContainerFuns.getOrPut(module) {
|
return testContainerFuns.getOrPut(module) {
|
||||||
val file = syntheticFile("tests", module)
|
val file = syntheticFile("tests", module)
|
||||||
irFactory.addFunction(file) {
|
irFactory.addFunction(file) {
|
||||||
@@ -325,8 +325,8 @@ class JsIrBackendContext(
|
|||||||
val throwISEsymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_ISE"))).single())
|
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 throwIAEsymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_IAE"))).single())
|
||||||
|
|
||||||
val suiteFun = getFunctions(FqName("kotlin.test.suite")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
override 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 testFun = getFunctions(FqName("kotlin.test.test")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||||
|
|
||||||
val primitiveClassProperties by lazy2 {
|
val primitiveClassProperties by lazy2 {
|
||||||
primitiveClassesObject.owner.declarations.filterIsInstance<IrProperty>()
|
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.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
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.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
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.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
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) }
|
val generator = TestGenerator(context) { context.createTestContainerFun(moduleFragment) }
|
||||||
|
|
||||||
moduleFragment.files.toList().forEach {
|
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) {
|
override fun lower(irFile: IrFile) {
|
||||||
irFile.declarations.forEach {
|
irFile.declarations.forEach {
|
||||||
@@ -61,7 +62,7 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
|
|||||||
|
|
||||||
val function = context.irFactory.buildFun {
|
val function = context.irFactory.buildFun {
|
||||||
this.name = Name.identifier("$name test fun")
|
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
|
this.origin = JsIrBuilder.SYNTHESIZED_DECLARATION
|
||||||
}
|
}
|
||||||
function.parent = parentFunction
|
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.JsCommonBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsCommonCoroutineSymbols
|
import org.jetbrains.kotlin.ir.backend.js.JsCommonCoroutineSymbols
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsMapping
|
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.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.builders.declarations.buildFun
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
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.impl.IrExternalPackageFragmentImpl
|
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
|
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.symbols.impl.DescriptorlessExternalPackageFragmentSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
|
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
|
||||||
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
|
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
|
||||||
@@ -119,4 +119,54 @@ class WasmBackendContext(
|
|||||||
/*TODO*/
|
/*TODO*/
|
||||||
print(message)
|
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"
|
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(
|
private val expectDeclarationsRemovingPhase = makeWasmModulePhase(
|
||||||
::ExpectDeclarationsRemoveLowering,
|
::ExpectDeclarationsRemoveLowering,
|
||||||
name = "ExpectDeclarationsRemoving",
|
name = "ExpectDeclarationsRemoving",
|
||||||
@@ -469,6 +475,7 @@ val wasmPhases = NamedCompilerPhase(
|
|||||||
name = "IrModuleLowering",
|
name = "IrModuleLowering",
|
||||||
description = "IR module lowering",
|
description = "IR module lowering",
|
||||||
lower = validateIrBeforeLowering then
|
lower = validateIrBeforeLowering then
|
||||||
|
generateTests then
|
||||||
excludeDeclarationsFromCodegenPhase then
|
excludeDeclarationsFromCodegenPhase then
|
||||||
expectDeclarationsRemovingPhase then
|
expectDeclarationsRemovingPhase then
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.name.FqName
|
|||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
class WasmSymbols(
|
class WasmSymbols(
|
||||||
context: WasmBackendContext,
|
context: WasmBackendContext,
|
||||||
@@ -39,7 +40,8 @@ class WasmSymbols(
|
|||||||
context.module.getPackage(StandardNames.COLLECTIONS_PACKAGE_FQ_NAME)
|
context.module.getPackage(StandardNames.COLLECTIONS_PACKAGE_FQ_NAME)
|
||||||
private val builtInsPackage: PackageViewDescriptor =
|
private val builtInsPackage: PackageViewDescriptor =
|
||||||
context.module.getPackage(StandardNames.BUILT_INS_PACKAGE_FQ_NAME)
|
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 throwNullPointerException = getInternalFunction("THROW_NPE")
|
||||||
override val throwISE = getInternalFunction("THROW_ISE")
|
override val throwISE = getInternalFunction("THROW_ISE")
|
||||||
@@ -131,6 +133,10 @@ class WasmSymbols(
|
|||||||
|
|
||||||
val stringGetLiteral = getFunction("stringLiteral", builtInsPackage)
|
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 wasmClassId = getInternalFunction("wasmClassId")
|
||||||
val wasmInterfaceId = getInternalFunction("wasmInterfaceId")
|
val wasmInterfaceId = getInternalFunction("wasmInterfaceId")
|
||||||
|
|
||||||
@@ -211,10 +217,17 @@ class WasmSymbols(
|
|||||||
findProperty(context.module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single()
|
findProperty(context.module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single()
|
||||||
|
|
||||||
private fun getFunction(name: String, ownerPackage: PackageViewDescriptor): IrSimpleFunctionSymbol {
|
private fun getFunction(name: String, ownerPackage: PackageViewDescriptor): IrSimpleFunctionSymbol {
|
||||||
val tmp = findFunctions(ownerPackage.memberScope, Name.identifier(name)).single()
|
return maybeGetFunction(name, ownerPackage) ?: throw IllegalArgumentException("Function $name not found")
|
||||||
return symbolTable.referenceSimpleFunction(tmp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 getInternalFunction(name: String) = getFunction(name, wasmInternalPackage)
|
||||||
|
|
||||||
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName))
|
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;
|
const ${sanitizeName(config.moduleId)} = wasmInstance.exports;
|
||||||
|
|
||||||
|
wasmInstance.exports.startUnitTests?.();
|
||||||
|
|
||||||
const actualResult = importStringFromWasm(wasmInstance.exports.$testFunction());
|
const actualResult = importStringFromWasm(wasmInstance.exports.$testFunction());
|
||||||
if (actualResult !== "OK")
|
if (actualResult !== "OK")
|
||||||
throw `Wrong box result '${'$'}{actualResult}'; Expected "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