[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:
+60
-20
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -5401,6 +5401,11 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/ignore.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("illegalParameters.kt")
|
||||
public void testIllegalParameters() throws Exception {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/illegalParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("incremental.kt")
|
||||
public void testIncremental() throws Exception {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/incremental.kt");
|
||||
|
||||
+5
@@ -5401,6 +5401,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/ignore.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("illegalParameters.kt")
|
||||
public void testIllegalParameters() throws Exception {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/illegalParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("incremental.kt")
|
||||
public void testIncremental() throws Exception {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/incremental.kt");
|
||||
|
||||
+5
@@ -5416,6 +5416,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/ignore.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("illegalParameters.kt")
|
||||
public void testIllegalParameters() throws Exception {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/illegalParameters.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("incremental.kt")
|
||||
public void testIncremental() throws Exception {
|
||||
runTest("js/js.translator/testData/box/kotlin.test/incremental.kt");
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// IGNORE_BACKEND: JS
|
||||
// KJS_WITH_FULL_RUNTIME
|
||||
// SKIP_DCE_DRIVEN
|
||||
|
||||
import common.*
|
||||
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() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -20,7 +20,7 @@ class Outer {
|
||||
}
|
||||
|
||||
inner class Inneer {
|
||||
@Test fun inneerTest() {
|
||||
@Test fun innermostTest() {
|
||||
call(prop + "Inneer")
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ fun box() = checkLog {
|
||||
call("propInner")
|
||||
}
|
||||
suite("Inneer") {
|
||||
test("inneerTest") {
|
||||
test("innermostTest") {
|
||||
call("propInneer")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user