[JS IR] Throw exception if test class or test method has explicit parameter

resolves https://youtrack.jetbrains.com/issue/KT-41032
This commit is contained in:
Shagen Ogandzhanian
2020-11-23 15:28:05 +01:00
parent c4a8d1c3a1
commit 2d4e9af289
6 changed files with 273 additions and 22 deletions
@@ -6,21 +6,23 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
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.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -37,26 +39,24 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
override fun lower(irFile: IrFile) {
irFile.declarations.forEach {
if (it is IrClass) {
generateTestCalls(it) { suiteForPackage(irFile.fqName).function }
generateTestCalls(it) { suiteForPackage(irFile.fqName) }
}
// TODO top-level functions
}
}
private val packageSuites = mutableMapOf<FqName, FunctionWithBody>()
private val packageSuites = mutableMapOf<FqName, IrSimpleFunction>()
private fun suiteForPackage(fqName: FqName) = packageSuites.getOrPut(fqName) {
context.suiteFun!!.createInvocation(fqName.asString(), testContainerFactory())
}
private data class FunctionWithBody(val function: IrSimpleFunction, val body: IrBlockBody)
private fun IrSimpleFunctionSymbol.createInvocation(
name: String,
parentFunction: IrSimpleFunction,
ignored: Boolean = false
): FunctionWithBody {
): IrSimpleFunction {
val body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, emptyList())
val function = context.irFactory.buildFun {
@@ -67,8 +67,7 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
function.parent = parentFunction
function.body = body
val parentBody = parentFunction.body as IrBlockBody
parentBody.statements += JsIrBuilder.buildCall(this).apply {
(parentFunction.body as IrBlockBody).statements += JsIrBuilder.buildCall(this).apply {
putValueArgument(0, JsIrBuilder.buildString(context.irBuiltIns.stringType, name))
putValueArgument(1, JsIrBuilder.buildBoolean(context.irBuiltIns.booleanType, ignored))
@@ -76,13 +75,15 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
putValueArgument(2, JsIrBuilder.buildFunctionExpression(refType, function))
}
return FunctionWithBody(function, body)
return function
}
private fun generateTestCalls(irClass: IrClass, parentFunction: () -> IrSimpleFunction) {
if (irClass.modality == Modality.ABSTRACT || irClass.isEffectivelyExternal() || irClass.isExpect) return
val suiteFunBody by lazy { context.suiteFun!!.createInvocation(irClass.name.asString(), parentFunction(), irClass.isIgnored) }
val suiteFunBody by lazy {
context.suiteFun!!.createInvocation(irClass.name.asString(), parentFunction(), irClass.isIgnored)
}
val beforeFunctions = irClass.declarations.filterIsInstance<IrSimpleFunction>().filter { it.isBefore }
val afterFunctions = irClass.declarations.filterIsInstance<IrSimpleFunction>().filter { it.isAfter }
@@ -90,10 +91,30 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
irClass.declarations.forEach {
when {
it is IrClass ->
generateTestCalls(it) { suiteFunBody.function }
generateTestCalls(it) { suiteFunBody }
it is IrSimpleFunction && it.isTest ->
generateCodeForTestMethod(it, beforeFunctions, afterFunctions, irClass, suiteFunBody.function)
generateCodeForTestMethod(it, beforeFunctions, afterFunctions, irClass, suiteFunBody)
}
}
}
private fun IrDeclarationWithVisibility.isVisibleFromTests() =
(visibility == DescriptorVisibilities.PUBLIC) || (visibility == DescriptorVisibilities.INTERNAL)
private fun IrDeclarationWithVisibility.isEffectivelyVisibleFromTests(): Boolean {
return generateSequence(this) { it.parent as? IrDeclarationWithVisibility }.all {
it.isVisibleFromTests()
}
}
private fun IrClass.canBeInstantiated(): Boolean {
val isClassReachable = isEffectivelyVisibleFromTests()
return if (isObject) {
isClassReachable
} else {
isClassReachable && constructors.any {
it.isVisibleFromTests() && it.explicitParametersCount == if (isInner) 1 else 0
}
}
}
@@ -105,7 +126,25 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
irClass: IrClass,
parentFunction: IrSimpleFunction
) {
val (fn, body) = context.testFun!!.createInvocation(testFun.name.asString(), parentFunction, testFun.isIgnored)
val fn = context.testFun!!.createInvocation(testFun.name.asString(), parentFunction, testFun.isIgnored)
val body = fn.body as IrBlockBody
val exceptionMessage = when {
testFun.valueParameters.isNotEmpty() || !testFun.isEffectivelyVisibleFromTests() ->
"Test method ${irClass.fqNameWhenAvailable ?: irClass.name}::${testFun.name} should have public or internal visibility, can not have parameters"
!irClass.canBeInstantiated() ->
"Test class ${irClass.fqNameWhenAvailable ?: irClass.name} must declare a public or internal constructor with no explicit parameters"
else -> null
}
if (exceptionMessage != null) {
val irBuilder = context.createIrBuilder(fn.symbol)
body.statements += irBuilder.irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
putValueArgument(0, irBuilder.irString(exceptionMessage))
}
return
}
val classVal = JsIrBuilder.buildVar(irClass.defaultType, fn, initializer = irClass.instance())
@@ -145,13 +184,14 @@ class TestGenerator(val context: JsIrBackendContext, val testContainerFactory: (
return if (kind == ClassKind.OBJECT) {
JsIrBuilder.buildGetObjectValue(defaultType, symbol)
} else {
declarations.asSequence().filterIsInstance<IrConstructor>().single { it.isPrimary }.let { constructor ->
IrConstructorCallImpl.fromSymbolOwner(defaultType, constructor.symbol).also {
if (isInner) {
it.dispatchReceiver = (parent as IrClass).instance()
declarations.asSequence().filterIsInstance<IrConstructor>().first { it.explicitParametersCount == if (isInner) 1 else 0 }
.let { constructor ->
IrConstructorCallImpl.fromSymbolOwner(defaultType, constructor.symbol).also {
if (isInner) {
it.dispatchReceiver = (parent as IrClass).instance()
}
}
}
}
}
}