diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index b68e2ce8bb0..16dcaac70e7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -22,17 +22,25 @@ import org.jetbrains.kotlin.backend.common.ir.Symbols import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.ValueType import org.jetbrains.kotlin.backend.konan.descriptors.getMemberScope +import org.jetbrains.kotlin.backend.konan.descriptors.isKFunctionType import org.jetbrains.kotlin.backend.konan.lower.TestProcessor import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType +import org.jetbrains.kotlin.builtins.isFunctionType +import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrEnumEntry import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.calls.components.isVararg +import org.jetbrains.kotlin.serialization.KonanIr +import org.jetbrains.kotlin.types.KotlinType import kotlin.properties.Delegates // This is what Context collects about IR. @@ -174,29 +182,36 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym Name.identifier(className), NoLookupLocation.FROM_BACKEND ) as ClassDescriptor) - val abstractTestSuite = getKonanTestClass("AbstractTestSuite") - val baseClassSuite = getKonanTestClass("BaseClassSuite") - val baseTopLevelSuite = getKonanTestClass("BaseTopLevelSuite") + private fun getFunction(name: Name, receiverType: KotlinType, predicate: (FunctionDescriptor) -> Boolean) = + symbolTable.referenceFunction(receiverType.memberScope + .getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).single(predicate) + ) + + val baseClassSuite = getKonanTestClass("BaseClassSuite") + val topLevelSuite = getKonanTestClass("TopLevelSuite") val testFunctionKind = getKonanTestClass("TestFunctionKind") - // TODO: Optimize / rename - val registerTestCase = symbolTable.referenceFunction(abstractTestSuite.descriptor - .unsubstitutedMemberScope - .getContributedFunctions(Name.identifier("registerTestCase"), NoLookupLocation.FROM_BACKEND) - .single { - it.valueParameters.size == 2 && - KotlinBuiltIns.isString(it.valueParameters[0].type) - /* TODO: Check if it.valueParameters[1] has a function type */ - }) + val baseClassSuiteConstructor = baseClassSuite.descriptor.constructors.single { + it.valueParameters.size == 1 && KotlinBuiltIns.isString(it.valueParameters[0].type) + } - val registerFunction = symbolTable.referenceFunction(abstractTestSuite.descriptor - .unsubstitutedMemberScope - .getContributedFunctions(Name.identifier("registerFunction"), NoLookupLocation.FROM_BACKEND) - .single { + val topLevelSuiteConstructor = symbolTable.referenceConstructor(topLevelSuite.descriptor.constructors.single { + it.valueParameters.size == 1 && KotlinBuiltIns.isString(it.valueParameters[0].type) + }) + + val topLevelSuiteRegisterFunction = + getFunction(Name.identifier("registerFunction"), topLevelSuite.descriptor.defaultType) { it.valueParameters.size == 2 && - it.valueParameters[0].type == testFunctionKind.descriptor.defaultType - /* TODO: Check if it.valueParameters[1] has a function type */ - }) + it.valueParameters[0].type == testFunctionKind.descriptor.defaultType && + it.valueParameters[1].type.isFunctionType + } + + val topLevelSuiteRegisterTestCase = + getFunction(Name.identifier("registerTestCase"), topLevelSuite.descriptor.defaultType) { + it.valueParameters.size == 2 && + KotlinBuiltIns.isString(it.valueParameters[0].type) && + it.valueParameters[1].type.isFunctionType + } private val testFunctionKindCache = mutableMapOf() fun getTestFunctionKind(kind: TestProcessor.FunctionKind): IrEnumEntrySymbol = testFunctionKindCache.getOrPut(kind) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt index a3bd63145c8..014eb68cb2c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt @@ -8,7 +8,10 @@ import org.jetbrains.kotlin.backend.common.lower.SymbolWithIrBuilder import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.konan.KonanBackendContext import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract +import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.konan.reportCompilationWarning +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl @@ -36,6 +39,8 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.KotlinType +import java.io.File internal class TestProcessor (val context: KonanBackendContext): FileLoweringPass { @@ -45,10 +50,64 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas val symbols = context.ir.symbols val baseClassSuiteDescriptor = symbols.baseClassSuite.descriptor - val topLevelTestSuiteDescriptor = symbols.baseTopLevelSuite.descriptor + // region Useful extensions. + var testSuiteCnt = 0 + fun Name.synthesizeSuiteClassName() = identifier.synthesizeSuiteClassName() + fun String.synthesizeSuiteClassName() = "${splitToSequence('.') + .mapIndexed { i, it -> if (i != 0) it.capitalize() } + .joinToString("")}\$test\$${testSuiteCnt++}".synthesizedName + + private val IrFile.fileName get() = name.substringAfterLast(File.separatorChar) + private val IrFile.topLevelSuiteName get() = "Tests in file: $fileName" + + private fun IrStatementsBuilder.generateFunctionRegistration( + receiver: IrValueSymbol, + registerTestCase: IrFunctionSymbol, + registerFunction: IrFunctionSymbol, + functions: Collection) { + functions.forEach { + if (it.kind == FunctionKind.TEST) { + // Call registerTestCase(name: String, testFunction: () -> Unit) method. + +irCall(registerTestCase).apply { + dispatchReceiver = irGet(receiver) + putValueArgument(0, IrConstImpl.string( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + context.builtIns.stringType, + it.function.descriptor.name.identifier) + ) + putValueArgument(1, IrFunctionReferenceImpl(UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + descriptor.valueParameters[1].type, + it.function, + it.function.descriptor, emptyMap())) + } + } else { + // Call registerFunction(kind: TestFunctionKind, () -> Unit) method. + +irCall(registerFunction).apply { + dispatchReceiver = irGet(receiver) + val testKindEntry = it.kind.runtimeKind + putValueArgument(0, IrGetEnumValueImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + testKindEntry.descriptor.defaultType, + testKindEntry) + ) + putValueArgument(1, IrFunctionReferenceImpl(UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + descriptor.valueParameters[1].type, + it.function, + it.function.descriptor, emptyMap())) + } + } + } + } + // endregion + + // region Classes for annotation collection. // TODO: Support ignore - enum class FunctionKind(annotationNameString: String, runtimeKindString: String) { + internal enum class FunctionKind(annotationNameString: String, runtimeKindString: String) { TEST("kotlin.test.Test", "") { override val runtimeKindName: Name get() = throw NotImplementedError() }, @@ -62,12 +121,12 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas open val runtimeKindName = Name.identifier(runtimeKindString) } - val FunctionKind.runtimeKind: IrEnumEntrySymbol + private val FunctionKind.runtimeKind: IrEnumEntrySymbol get() = symbols.getTestFunctionKind(this) private data class TestFunction(val function: IrFunctionSymbol, val kind: FunctionKind) - private class TestSuite(val owner: IrClassSymbol) { + private class ClassSuite(val ownerClass: IrClassSymbol) { val functions = mutableListOf() fun registerFunction(function: IrFunctionSymbol, kinds: Collection) = kinds.forEach { @@ -76,13 +135,13 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas } private inner class AnnotationCollector : IrElementVisitorVoid { - val testClasses = mutableMapOf() + val testClasses = mutableMapOf() val topLevelFunctions = mutableListOf() - private fun MutableMap.getTestSuite(key: IrClassSymbol) = - getOrPut(key) { TestSuite(key) } + private fun MutableMap.getTestSuite(key: IrClassSymbol) = + getOrPut(key) { ClassSuite(key) } - private fun MutableMap.getTestSuite(key: ClassDescriptor) = + private fun MutableMap.getTestSuite(key: ClassDescriptor) = getTestSuite(symbols.symbolTable.referenceClass(key)) private fun MutableList.registerFunction( @@ -115,40 +174,40 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas owner is ClassDescriptor && !owner.canContainTests() -> context.reportCompilationWarning("Class cannot contain unit-tests: ${owner.fqNameSafe}") else -> - UnsupportedOperationException("Cannot create test function for owner: $owner") + UnsupportedOperationException("Cannot create test function $declaration (defined in $owner") } } } + // endregion - private fun Name.testClassName() = Name.identifier("$this\$test") + //region Symbol and IR builders - /** Creates a [SymbolWithIrBuilder] building the BaseClassSuite.createInstance() function. */ - private fun instanceGetterBuilder(testClass: IrClassSymbol, testSuite: IrClassSymbol) = - object : SymbolWithIrBuilder() { + /** Builds the BaseClassSuite.createInstance() function. */ + private inner class InstanceGetterBuilder(val testClass: IrClassSymbol, val testSuite: IrClassSymbol) + : SymbolWithIrBuilder() { - val getterName = Name.identifier("createInstance") - val superFunction = baseClassSuiteDescriptor - .unsubstitutedMemberScope - .getContributedFunctions(getterName, NoLookupLocation.FROM_BACKEND) - .single { it.valueParameters.isEmpty() } + val getterName = Name.identifier("createInstance") + val superFunction = baseClassSuiteDescriptor + .unsubstitutedMemberScope + .getContributedFunctions(getterName, NoLookupLocation.FROM_BACKEND) + .single { it.valueParameters.isEmpty() } - override fun buildIr() = IrFunctionImpl( - startOffset = UNDEFINED_OFFSET, - endOffset = UNDEFINED_OFFSET, - origin = TEST_SUITE_GENERATED_MEMBER, - symbol = symbol).apply { - val builder = context.createIrBuilder(symbol) - createParameterDeclarations() - body = builder.irBlockBody { - val constructor = testClass.constructors.single { it.descriptor.valueParameters.isEmpty() } - +irReturn(irCall(constructor)) - } + override fun buildIr() = IrFunctionImpl( + startOffset = UNDEFINED_OFFSET, + endOffset = UNDEFINED_OFFSET, + origin = TEST_SUITE_GENERATED_MEMBER, + symbol = symbol).apply { + val builder = context.createIrBuilder(symbol) + createParameterDeclarations() + body = builder.irBlockBody { + val constructor = testClass.constructors.single { it.descriptor.valueParameters.isEmpty() } + +irReturn(irCall(constructor)) } + } - // TODO: add comments for parameters. - override fun doInitialize() { - val descriptor = symbol.descriptor as SimpleFunctionDescriptorImpl - descriptor.initialize( + override fun doInitialize() { + val descriptor = symbol.descriptor as SimpleFunctionDescriptorImpl + descriptor.initialize( /* receiverParameterType = */ null, /* dispatchReceiverParameter = */ testSuite.descriptor.thisAsReceiverParameter, /* typeParameters = */ emptyList(), @@ -156,182 +215,198 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas /* returnType = */ testClass.descriptor.defaultType, /* modality = */ Modality.FINAL, /* visibility = */ Visibilities.PROTECTED - ).apply { - overriddenDescriptors += superFunction - } + ).apply { + overriddenDescriptors += superFunction } + } - override fun buildSymbol() = IrSimpleFunctionSymbolImpl( - SimpleFunctionDescriptorImpl.create( + override fun buildSymbol() = IrSimpleFunctionSymbolImpl( + SimpleFunctionDescriptorImpl.create( /* containingDeclaration = */ testSuite.descriptor, /* annotations = */ Annotations.EMPTY, /* name = */ getterName, /* kind = */ CallableMemberDescriptor.Kind.SYNTHESIZED, /* source = */ SourceElement.NO_SOURCE + ) + ) + } + + /** + * Builds a constructor for a test suite class representing a test class (any class in the original IrFile with + * method(s) annotated with @Test). The test suite class is a subclass of ClassTestSuite + * where T is the test class. + */ + private inner class ClassSuiteConstructorBuilder(val testClass: IrClassSymbol, + val testSuite: IrClassSymbol, + val functions: Collection) + : SymbolWithIrBuilder() { + + val suiteName = testClass.descriptor.fqNameSafe.toString() + + private fun IrClassSymbol.getFunction(name: String, predicate: (FunctionDescriptor) -> Boolean) = + symbols.symbolTable.referenceFunction(descriptor.unsubstitutedMemberScope + .getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND) + .single(predicate)) + + override fun buildIr() = IrConstructorImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + TEST_SUITE_GENERATED_MEMBER, + symbol).apply { + createParameterDeclarations() + + val registerTestCase = testSuite.getFunction("registerTestCase") { + it.valueParameters.size == 2 && + KotlinBuiltIns.isString(it.valueParameters[0].type) && + it.valueParameters[1].type.isFunctionType + } + val registerFunction = testSuite.getFunction("registerFunction") { + it.valueParameters.size == 2 && + it.valueParameters[0].type == symbols.testFunctionKind.descriptor.defaultType && + it.valueParameters[1].type.isFunctionType + } + + body = context.createIrBuilder(symbol).irBlockBody { + val superConstructor = symbols.baseClassSuiteConstructor + +IrDelegatingConstructorCallImpl( + startOffset = UNDEFINED_OFFSET, + endOffset = UNDEFINED_OFFSET, + symbol = symbols.symbolTable.referenceConstructor(superConstructor), + descriptor = superConstructor, + typeArguments = mapOf( + superConstructor.typeParameters[0] to testClass.descriptor.defaultType + ) + ).apply { + putValueArgument(0, IrConstImpl.string( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + context.builtIns.stringType, + suiteName) ) - ) + } + // TODO: What about null here? + generateFunctionRegistration(testSuite.owner.thisReceiver!!.symbol, + registerTestCase, registerFunction, functions) + } } - /** Creates a [SymbolWithIrBuilder] building a constructor for a class test suite (BaseClassSuite subclass) */ - private fun constructorBuilder(testClass: IrClassSymbol, - testSuite: IrClassSymbol, - functions: Collection) = - object : SymbolWithIrBuilder() { - - val superConstructor = baseClassSuiteDescriptor.constructors.single { - it.valueParameters.size == 1 /* && arg[0].type == String */ + override fun doInitialize() { + val descriptor = symbol.descriptor as ClassConstructorDescriptorImpl + descriptor.initialize(emptyList(), Visibilities.PUBLIC).apply { + returnType = testSuite.descriptor.defaultType } + } - override fun buildIr() = IrConstructorImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - TEST_SUITE_GENERATED_MEMBER, - symbol).apply { - //createParameterDeclarations() TODO: WHat??? - val builder = context.createIrBuilder(symbol) - - body = builder.irBlockBody { - // TODO: What about type args? - +IrDelegatingConstructorCallImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - symbols.symbolTable.referenceConstructor(superConstructor), - superConstructor, - mapOf(superConstructor.typeParameters[0] to testClass.descriptor.defaultType)).apply { - putValueArgument(0, IrConstImpl.string( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - context.builtIns.stringType, - testClass.descriptor.fqNameSafe.toString()) - ) - } - // TODO: Use fake overrides for calls? - val thisReceiver = testSuite.owner.thisReceiver!!.symbol // TODO: What about null here? - functions.forEach { - if (it.kind == FunctionKind.TEST) { - +irCall(symbols.registerTestCase).apply { - dispatchReceiver = irGet(thisReceiver) - putValueArgument(0, IrConstImpl.string( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - context.builtIns.stringType, - it.function.descriptor.name.identifier) - ) - putValueArgument(1, IrFunctionReferenceImpl(UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - descriptor.valueParameters[1].type, - it.function, - it.function.descriptor, emptyMap())) - } - } else { - +irCall(symbols.registerFunction).apply { - dispatchReceiver = irGet(thisReceiver) - val testKindEntry = it.kind.runtimeKind - putValueArgument(0, IrGetEnumValueImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - testKindEntry.descriptor.defaultType, - testKindEntry) - ) - putValueArgument(1, IrFunctionReferenceImpl(UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - descriptor.valueParameters[1].type, - it.function, - it.function.descriptor, emptyMap())) - } - } - } - } - } - - override fun doInitialize() { - val descriptor = symbol.descriptor as ClassConstructorDescriptorImpl - descriptor.initialize(emptyList(), Visibilities.PUBLIC).apply { - returnType = testSuite.descriptor.defaultType - } - } - - override fun buildSymbol() = IrConstructorSymbolImpl( + override fun buildSymbol() = IrConstructorSymbolImpl( ClassConstructorDescriptorImpl.createSynthesized( testSuite.descriptor, Annotations.EMPTY, true, SourceElement.NO_SOURCE ) - ) + ) + } + /** + * Builds a test suite class representing a test class (any class in the original IrFile with method(s) + * annotated with @Test). The test suite class is a subclass of ClassTestSuite where T is the test class. + */ + private inner class ClassSuiteBuilder(val testClass: IrClassSymbol, + val containingDeclaration: DeclarationDescriptor, + val functions: Collection) + : SymbolWithIrBuilder() { + + val suiteClassName = testClass.descriptor.name.synthesizeSuiteClassName() + val superType = baseClassSuiteDescriptor.defaultType.replace(listOf(testClass.descriptor.defaultType)) + + val constructorBuilder = ClassSuiteConstructorBuilder(testClass, symbol, functions) + val instanceGetterBuilder = InstanceGetterBuilder(testClass, symbol) + + + override fun buildIr() = IrClassImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + TEST_SUITE_CLASS, + symbol).apply { + createParameterDeclarations() + addMember(constructorBuilder.ir) + addMember(instanceGetterBuilder.ir) + addFakeOverrides() } - /** Creates a [SymbolWithIrBuilder] building a class test suite (BaseClassSuite subclass) */ - private fun classSuiteBuilder(testClass: IrClassSymbol, irFile: IrFile, functions: Collection) = - object: SymbolWithIrBuilder() { + override fun doInitialize() { + val descriptor = symbol.descriptor as ClassDescriptorImpl + val constructorDescriptor = constructorBuilder.symbol.descriptor - val constructorBuilder = constructorBuilder(testClass, symbol, functions) - val instanceGetterBuilder = instanceGetterBuilder(testClass, symbol) + val contributedDescriptors = baseClassSuiteDescriptor + .unsubstitutedMemberScope + .getContributedDescriptors() + .map { + if (it == instanceGetterBuilder.superFunction) { + instanceGetterBuilder.symbol.descriptor + } else { + it.createFakeOverrideDescriptor(symbol.descriptor as ClassDescriptorImpl) + } + }.filterNotNull() - override fun buildIr() = IrClassImpl( - UNDEFINED_OFFSET, - UNDEFINED_OFFSET, - TEST_SUITE_CLASS, - symbol).apply { - createParameterDeclarations() - addMember(constructorBuilder.ir) - addMember(instanceGetterBuilder.ir) - addFakeOverrides() - } + descriptor.initialize( + SimpleMemberScope(contributedDescriptors),setOf(constructorDescriptor), constructorDescriptor + ) - override fun doInitialize() { - val descriptor = symbol.descriptor as ClassDescriptorImpl - val constructorDescriptor = constructorBuilder.symbol.descriptor + constructorBuilder.initialize() + instanceGetterBuilder.initialize() + } - val contributedDescriptors = baseClassSuiteDescriptor.unsubstitutedMemberScope - .getContributedDescriptors() - .map { - if (it == instanceGetterBuilder.superFunction) { - instanceGetterBuilder.symbol.descriptor - } else { - it.createFakeOverrideDescriptor(descriptor) - } - } - .filterNotNull() + override fun buildSymbol(): org.jetbrains.kotlin.ir.symbols.IrClassSymbol = IrClassSymbolImpl( + ClassDescriptorImpl( + /* containingDeclaration = */ containingDeclaration, + /* name = */ suiteClassName, + /* modality = */ Modality.FINAL, + /* kind = */ ClassKind.CLASS, + /* superTypes = */ listOf(superType), + /* source = */ SourceElement.NO_SOURCE, + /* isExternal = */ false + ) + ) + } + //endregion - descriptor.initialize(SimpleMemberScope(contributedDescriptors), setOf(constructorDescriptor), constructorDescriptor) - - constructorBuilder.initialize() - instanceGetterBuilder.initialize() - } - - override fun buildSymbol(): org.jetbrains.kotlin.ir.symbols.IrClassSymbol = IrClassSymbolImpl( - ClassDescriptorImpl( - /* containingDeclaration = */ irFile.packageFragmentDescriptor, - /* name = */ testClass.descriptor.name.testClassName(), - /* modality = */ Modality.FINAL, - /* kind = */ ClassKind.CLASS, - /* superTypes = */ listOf(baseClassSuiteDescriptor.defaultType.replace(listOf(testClass.descriptor.defaultType))), - /* source = */ SourceElement.NO_SOURCE, - /* isExternal = */ false - ) + // region IR generation methods + private fun generateClassSuite(irFile: IrFile, testClass: IrClassSymbol, functions: Collection) = + with(ClassSuiteBuilder(testClass, irFile.packageFragmentDescriptor, functions)) { + initialize() + irFile.declarations.add(ir) + irFile.addTopLevelInitializer( + IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, ir.symbol.constructors.single()) ) } - private fun generateTestSuite(testClass: IrClassSymbol, irFile: IrFile, functions: Collection) { - // Generate class - val builder = classSuiteBuilder(testClass, irFile, functions) - builder.initialize() - val irTestSuite = builder.ir - irFile.addTopLevelInitializer(context.createIrBuilder(irFile.symbol).irBlock { - +irTestSuite - +IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, irTestSuite.symbol.constructors.single()) + private fun generateTopLevelSuite(irFile: IrFile, functions: Collection) { + val builder = context.createIrBuilder(irFile.symbol) + irFile.addTopLevelInitializer(builder.irBlock { + val constructorCall = irCall(symbols.topLevelSuiteConstructor).apply { + putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, + context.builtIns.stringType, irFile.topLevelSuiteName)) + } + val testSuiteVal = irTemporary(constructorCall, "topLevelTestSuite") + generateFunctionRegistration(testSuiteVal.symbol, + symbols.topLevelSuiteRegisterTestCase, + symbols.topLevelSuiteRegisterFunction, + functions) }) } + // TODO: Support tests in objects + // TODO: Support beforeTest/afterTest for companions. private fun createTestSuites(irFile: IrFile, annotationCollector: AnnotationCollector) { annotationCollector.testClasses.forEach { _, testClass -> - generateTestSuite(testClass.owner, irFile, testClass.functions) + generateClassSuite(irFile, testClass.ownerClass, testClass.functions) + } + if (annotationCollector.topLevelFunctions.isNotEmpty()) { + generateTopLevelSuite(irFile, annotationCollector.topLevelFunctions) } - // TODO: Use another name for a file? - // generateTestSuite(irFile.name, irFile, annotationCollector.topLevelFunctions) } + // endregion override fun lower(irFile: IrFile) { val annotationCollector = AnnotationCollector() diff --git a/backend.native/tests/testing/smoke.kt b/backend.native/tests/testing/smoke.kt index b73faf0dffa..37af8b9ed8a 100644 --- a/backend.native/tests/testing/smoke.kt +++ b/backend.native/tests/testing/smoke.kt @@ -8,17 +8,8 @@ class A { } } -//abstract class AA { -// abstract fun a(): T -//} -// -// -//class BB: AA() { -// override fun a() = 5 -//} - -//@Test -//fun test() { -// println("test") -//} +@Test +fun test() { + println("test") +} diff --git a/runtime/src/main/kotlin/konan/test/TestListener.kt b/runtime/src/main/kotlin/konan/test/TestListener.kt index 1d4020bc078..0bc9160c38a 100644 --- a/runtime/src/main/kotlin/konan/test/TestListener.kt +++ b/runtime/src/main/kotlin/konan/test/TestListener.kt @@ -3,6 +3,12 @@ package konan.test import kotlin.AssertionError interface TestListener { + fun startTesting(runner: TestRunner) + fun endTesting(runner: TestRunner) + + fun startSuite(suite: TestSuite) + fun endSuite(suite: TestSuite) + fun pass(testCase: TestCase) fun fail(testCase: TestCase, e: AssertionError) fun error(testCase: TestCase, e: Throwable) diff --git a/runtime/src/main/kotlin/konan/test/TestRunner.kt b/runtime/src/main/kotlin/konan/test/TestRunner.kt index 269d3186052..a5a375618fb 100644 --- a/runtime/src/main/kotlin/konan/test/TestRunner.kt +++ b/runtime/src/main/kotlin/konan/test/TestRunner.kt @@ -5,11 +5,16 @@ import kotlin.AssertionError object TestRunner { object SimpleTestListener: TestListener { - override fun pass(testCase: TestCase) = println("Pass: $testCase") - override fun fail(testCase: TestCase, e: AssertionError) = println("Fail: $testCase (${e.message})") - override fun error(testCase: TestCase, e: Throwable) = println("Error: $testCase (${e.message})") - override fun ignore(testCase: TestCase) = println("Ignore: $testCase") + override fun startTesting(runner: TestRunner) = println("Starting testing\n") + override fun endTesting(runner: TestRunner) = println("Testing finished\n") + override fun startSuite(suite: TestSuite) = println("Starting test suite: $suite\n") + override fun endSuite(suite: TestSuite) = println("Test suite finished: $suite\n") + + override fun pass(testCase: TestCase) = println("Pass: $testCase\n") + override fun fail(testCase: TestCase, e: AssertionError) = println("Fail: $testCase (Exception: ${e.message})\n") + override fun error(testCase: TestCase, e: Throwable) = println("Error: $testCase (Exception: ${e.message})\n") + override fun ignore(testCase: TestCase) = println("Ignore: $testCase\n") } private val _suites = mutableListOf() @@ -20,9 +25,13 @@ object TestRunner { fun register(vararg suites: TestSuite) = _suites.addAll(suites) fun run() { + SimpleTestListener.startTesting(this) suites.forEach { + SimpleTestListener.startSuite(it) it.run(SimpleTestListener) + SimpleTestListener.endSuite(it) } + SimpleTestListener.endTesting(this) } } diff --git a/runtime/src/main/kotlin/konan/test/TestSuite.kt b/runtime/src/main/kotlin/konan/test/TestSuite.kt index cda9c553bea..09c4f4d34a4 100644 --- a/runtime/src/main/kotlin/konan/test/TestSuite.kt +++ b/runtime/src/main/kotlin/konan/test/TestSuite.kt @@ -4,6 +4,7 @@ import kotlin.AssertionError interface TestCase { val name: String + val suite: TestSuite } interface TestSuite { @@ -23,8 +24,12 @@ abstract class AbstractTestSuite>(override val name: String): override fun toString(): String = name // TODO: Make inner and remove the type param when the bug is fixed. - class BasicTestCase>(override val name: String, val testFunction: F): TestCase { - override fun toString(): String = name + class BasicTestCase>( + override val name: String, + override val suite: AbstractTestSuite, + val testFunction: F + ) : TestCase { + override fun toString(): String = "$suite.$name" } private val _testCases = mutableMapOf>() @@ -42,13 +47,11 @@ abstract class AbstractTestSuite>(override val name: String): val beforeClass: Collection get() = specialFunctions.getFunctions(TestFunctionKind.BEFORE_CLASS) val afterClass: Collection get() = specialFunctions.getFunctions(TestFunctionKind.AFTER_CLASS) - fun registerTestCase(name: String, testFunction: F) = registerTestCase(BasicTestCase(name, testFunction)) + private fun registerTestCase(testCase: BasicTestCase) = _testCases.put(testCase.name, testCase) + fun registerTestCase(name: String, testFunction: F) = registerTestCase(createTestCase(name, testFunction)) + fun createTestCase(name: String, testFunction: F) = BasicTestCase(name, this, testFunction) - protected fun registerTestCase(testCase: BasicTestCase) = _testCases.put(testCase.name, testCase) - protected fun registerTestCases(testCases: Collection>) = testCases.forEach { registerTestCase(it) } - protected fun registerTestCases(vararg testCases: BasicTestCase) = registerTestCases(testCases.toList()) - - protected fun registerFunction(type: TestFunctionKind, function: F) = + fun registerFunction(type: TestFunctionKind, function: F) = specialFunctions.getFunctions(type).add(function) protected abstract fun doBeforeClass() @@ -83,26 +86,31 @@ abstract class BaseClassSuite(name: String): AbstractTestSuite Unit>( abstract fun createInstance(): T // TODO: What about companions? - override fun doBeforeClass() {} // = beforeClass.forEach { createInstance().it() } - override fun doAfterClass() {} // = afterClass.forEach { createInstance().it() } + override fun doBeforeClass() {} // = beforeClass.forEach { createInstance().it() } + override fun doAfterClass() {} // = afterClass.forEach { createInstance().it() } override fun doTest(testCase: BasicTestCase Unit>) { val instance = createInstance() val testFunction = testCase.testFunction - before.forEach { instance.it() } - instance.testFunction() - after.forEach { instance.it() } + try { + before.forEach { instance.it() } + instance.testFunction() + } finally { + after.forEach { instance.it() } + } } } -class BaseTopLevelSuite(name: String): AbstractTestSuite<() -> Unit>(name) { +class TopLevelSuite(name: String): AbstractTestSuite<() -> Unit>(name) { override fun doBeforeClass() = beforeClass.forEach { it() } override fun doAfterClass() = afterClass.forEach { it() } - override fun doTest(testCase: BasicTestCase<() -> Unit>) { - before.forEach { it() } - testCase.testFunction() - after.forEach { it() } - } + override fun doTest(testCase: BasicTestCase<() -> Unit>) = + try { + before.forEach { it() } + testCase.testFunction() + } finally { + after.forEach { it() } + } } \ No newline at end of file