diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 488f28a0ec7..6adb7d89dea 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -353,6 +353,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { return config.configuration.getBoolean(KonanConfigKeys.DEBUG) } + fun shouldGenerateTestRunner(): Boolean = config.configuration.getBoolean(KonanConfigKeys.GENERATE_TEST_RUNNER) + override fun log(message: () -> String) { if (phase?.verbose ?: false) { println(message()) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 4181f2cef49..1fd67ab3267 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -40,9 +40,12 @@ internal class KonanLower(val context: Context) { fun lowerModule(irModule: IrModuleFragment) { val phaser = PhaseManager(context) - //phaser.phase(KonanPhase.TEST_PROCESSOR) { - // TestProcessor(context).process(irModule) - //} + // TODO: Find a place for the phase + if (context.shouldGenerateTestRunner()) { + phaser.phase(KonanPhase.TEST_PROCESSOR) { + irModule.files.forEach(TestProcessor(context)::lower) + } + } phaser.phase(KonanPhase.LOWER_SPECIAL_CALLS) { irModule.files.forEach(SpecialCallsLowering(context)::lower) 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 fdc3b510340..b68e2ce8bb0 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 @@ -21,13 +21,18 @@ import org.jetbrains.kotlin.backend.common.ir.Ir 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.lower.TestProcessor import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation +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 kotlin.properties.Delegates // This is what Context collects about IR. @@ -164,4 +169,39 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym val kLocalDelegatedPropertyImpl = symbolTable.referenceClass(context.reflectionTypes.kLocalDelegatedPropertyImpl) val kLocalDelegatedMutablePropertyImpl = symbolTable.referenceClass(context.reflectionTypes.kLocalDelegatedMutablePropertyImpl) + private fun getKonanTestClass(className: String) = symbolTable.referenceClass( + builtInsPackage("konan", "test").getContributedClassifier( + Name.identifier(className), NoLookupLocation.FROM_BACKEND + ) as ClassDescriptor) + + val abstractTestSuite = getKonanTestClass("AbstractTestSuite") + val baseClassSuite = getKonanTestClass("BaseClassSuite") + val baseTopLevelSuite = getKonanTestClass("BaseTopLevelSuite") + 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 registerFunction = symbolTable.referenceFunction(abstractTestSuite.descriptor + .unsubstitutedMemberScope + .getContributedFunctions(Name.identifier("registerFunction"), NoLookupLocation.FROM_BACKEND) + .single { + it.valueParameters.size == 2 && + it.valueParameters[0].type == testFunctionKind.descriptor.defaultType + /* TODO: Check if it.valueParameters[1] has a function type */ + }) + + private val testFunctionKindCache = mutableMapOf() + fun getTestFunctionKind(kind: TestProcessor.FunctionKind): IrEnumEntrySymbol = testFunctionKindCache.getOrPut(kind) { + symbolTable.referenceEnumEntry(testFunctionKind.descriptor.getMemberScope().getContributedClassifier( + kind.runtimeKindName, NoLookupLocation.FROM_BACKEND + ) as ClassDescriptor) + } } \ No newline at end of file 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 e35d247b26d..a3bd63145c8 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 @@ -1,123 +1,341 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.common.IrElementVisitorVoidWithContext +import org.jetbrains.kotlin.backend.common.descriptors.replace +import org.jetbrains.kotlin.backend.common.ir.createFakeOverrideDescriptor +import org.jetbrains.kotlin.backend.common.lower.SimpleMemberScope +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.contributedMethods -import org.jetbrains.kotlin.backend.konan.llvm.symbolName -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract +import org.jetbrains.kotlin.backend.konan.reportCompilationWarning +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrConstructorSymbolImpl +import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.util.addFakeOverrides +import org.jetbrains.kotlin.ir.util.addTopLevelInitializer +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.createParameterDeclarations import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid 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 internal class TestProcessor (val context: KonanBackendContext): FileLoweringPass { - // TODO: Replace with symbols - companion object { - val fqNameTestAnnotation = FqName("kotlin.test.Test") - val fqNameBeforeAnnotation = FqName("kotlin.test.Before") - val fqNameAfterAnnotation = FqName("kotlin.test.After") - val fqNameBeforeClassAnnotation = FqName("kotlin.test.BeforeClass") - val fqNameAfterClassAnnotation = FqName("kotlin.test.AfterClass") - val fqNameIgnoreAnnotation = FqName("kotlin.test.Ignore") + object TEST_SUITE_CLASS: IrDeclarationOriginImpl("TEST_SUITE_CLASS") + object TEST_SUITE_GENERATED_MEMBER: IrDeclarationOriginImpl("TEST_SUITE_GENERATED_MEMBER") + + val symbols = context.ir.symbols + + val baseClassSuiteDescriptor = symbols.baseClassSuite.descriptor + val topLevelTestSuiteDescriptor = symbols.baseTopLevelSuite.descriptor + + // TODO: Support ignore + enum class FunctionKind(annotationNameString: String, runtimeKindString: String) { + TEST("kotlin.test.Test", "") { + override val runtimeKindName: Name get() = throw NotImplementedError() + }, + + BEFORE("kotlin.test.Before", "BEFORE"), + AFTER("kotlin.test.After", "AFTER"), + BEFORE_CLASS("kotlin.test.BeforeClass", "BEFORE_CLASS"), + AFTER_CLASS("kotlin.test.AfterClass", "AFTER_CLASS"); + + val annotationFqName = FqName(annotationNameString) + open val runtimeKindName = Name.identifier(runtimeKindString) } - private inner class TestClass(val clazz: IrClassSymbol) { - val testFunctions = mutableListOf() - val beforeFunctions = mutableListOf() - val afterFunctions = mutableListOf() - } + val FunctionKind.runtimeKind: IrEnumEntrySymbol + get() = symbols.getTestFunctionKind(this) - private inner class TopLevelTestSuite() { - val testFunctions = mutableListOf() - val beforeFunctions = mutableListOf() - val afterFunctions = mutableListOf() - val beforeClassFunctions = mutableListOf() - val afterClassFunctions = mutableListOf() + private data class TestFunction(val function: IrFunctionSymbol, val kind: FunctionKind) + + private class TestSuite(val owner: IrClassSymbol) { + val functions = mutableListOf() + + fun registerFunction(function: IrFunctionSymbol, kinds: Collection) = kinds.forEach { + functions.add(TestFunction(function, it)) + } } private inner class AnnotationCollector : IrElementVisitorVoid { - val testClasses = mutableMapOf() - val topLevelTestSuite = TopLevelTestSuite() + val testClasses = mutableMapOf() + val topLevelFunctions = mutableListOf() + + private fun MutableMap.getTestSuite(key: IrClassSymbol) = + getOrPut(key) { TestSuite(key) } + + private fun MutableMap.getTestSuite(key: ClassDescriptor) = + getTestSuite(symbols.symbolTable.referenceClass(key)) + + private fun MutableList.registerFunction( + function: IrFunctionSymbol, + kinds: Collection) = kinds.forEach { add(TestFunction(function, it)) } + + private fun ClassDescriptor.canContainTests() = + !isInner && constructors.any { it.valueParameters.size == 0 } && !isAbstract() override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) } + fun IrFunctionSymbol.hasAnnotatoin(fqName: FqName) = descriptor.annotations.any { it.fqName == fqName } - -// override fun visitClass(declaration: IrClass) { -// val doNotprocess = declaration.descriptor.staticScope.contributedMethods.none { it.testFunction() } -// if (!doNotprocess) { -// testClasses.add(declaration.symbol) -// -// -// -// declaration.acceptChildrenVoid(object:IrElementVisitorVoid{ -// override fun visitElement(element: IrElement) { -// element.acceptChildrenVoid(this) -// } -// -// override fun visitFunction(declaration: IrFunction) { -// processFunction(declaration.descriptor) -// } -// }) -// } -// } - + // TODO: Use symbols instead of containingDeclaration when such information is available. override fun visitFunction(declaration: IrFunction) { - val descriptor = declaration.descriptor - val symbol = declaration.symbol + val functionSymbol = declaration.symbol + val kinds = FunctionKind.values().filter { functionSymbol.hasAnnotatoin(it.annotationFqName) } + if (kinds.isEmpty()) { + return + } + val owner = declaration.descriptor.containingDeclaration - // TODO: Make it smarter - //if (descriptor.test()) testFunctions.add(symbol) - //if (descriptor.before()) beforeFunctions.add(symbol) - //if (descriptor.after()) afterFunctions.add(symbol) - //if (descriptor.beforeClass()) beforeClassFunctions.add(symbol) - //if (descriptor.afterClass()) afterClassFunctions.add(symbol) + when { + owner is PackageFragmentDescriptor -> + topLevelFunctions.registerFunction(functionSymbol, kinds) + owner is ClassDescriptor && owner.canContainTests() -> + testClasses.getTestSuite(owner).registerFunction(declaration.symbol, kinds) + owner is ClassDescriptor && !owner.canContainTests() -> + context.reportCompilationWarning("Class cannot contain unit-tests: ${owner.fqNameSafe}") + else -> + UnsupportedOperationException("Cannot create test function for owner: $owner") + } + } + } + private fun Name.testClassName() = Name.identifier("$this\$test") + + /** Creates a [SymbolWithIrBuilder] building the BaseClassSuite.createInstance() function. */ + private fun instanceGetterBuilder(testClass: IrClassSymbol, testSuite: IrClassSymbol) = + object : SymbolWithIrBuilder() { + + 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)) + } + } + + // TODO: add comments for parameters. + override fun doInitialize() { + val descriptor = symbol.descriptor as SimpleFunctionDescriptorImpl + descriptor.initialize( + /* receiverParameterType = */ null, + /* dispatchReceiverParameter = */ testSuite.descriptor.thisAsReceiverParameter, + /* typeParameters = */ emptyList(), + /* unsubstitutedValueParameters = */ emptyList(), + /* returnType = */ testClass.descriptor.defaultType, + /* modality = */ Modality.FINAL, + /* visibility = */ Visibilities.PROTECTED + ).apply { + overriddenDescriptors += superFunction + } + } + + override fun buildSymbol() = IrSimpleFunctionSymbolImpl( + SimpleFunctionDescriptorImpl.create( + /* containingDeclaration = */ testSuite.descriptor, + /* annotations = */ Annotations.EMPTY, + /* name = */ getterName, + /* kind = */ CallableMemberDescriptor.Kind.SYNTHESIZED, + /* source = */ SourceElement.NO_SOURCE + ) + ) + } + + /** 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 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( + ClassConstructorDescriptorImpl.createSynthesized( + testSuite.descriptor, + Annotations.EMPTY, + true, + SourceElement.NO_SOURCE + ) + ) } - } + /** Creates a [SymbolWithIrBuilder] building a class test suite (BaseClassSuite subclass) */ + private fun classSuiteBuilder(testClass: IrClassSymbol, irFile: IrFile, functions: Collection) = + object: SymbolWithIrBuilder() { + val constructorBuilder = constructorBuilder(testClass, symbol, functions) + val instanceGetterBuilder = instanceGetterBuilder(testClass, symbol) - override fun lower(irFile: IrFile) { - irFile.acceptChildrenVoid(AnnotationCollector()) - /** - * TODO: for all test classes generate registration... - */ - } + override fun buildIr() = IrClassImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + TEST_SUITE_CLASS, + symbol).apply { + createParameterDeclarations() + addMember(constructorBuilder.ir) + addMember(instanceGetterBuilder.ir) + addFakeOverrides() + } - private fun FunctionDescriptor.isTestFunction() = - annotations.any { annotation -> - hasTestAnnotation(annotation) + override fun doInitialize() { + val descriptor = symbol.descriptor as ClassDescriptorImpl + val constructorDescriptor = constructorBuilder.symbol.descriptor + + val contributedDescriptors = baseClassSuiteDescriptor.unsubstitutedMemberScope + .getContributedDescriptors() + .map { + if (it == instanceGetterBuilder.superFunction) { + instanceGetterBuilder.symbol.descriptor + } else { + it.createFakeOverrideDescriptor(descriptor) + } + } + .filterNotNull() + + 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 + ) + ) } - - private fun hasTestAnnotation(annotation: AnnotationDescriptor): Boolean { - return annotation.fqName == fqNameTestAnnotation || - annotation.fqName == fqNameBeforeAnnotation || - annotation.fqName == fqNameAfterAnnotation || - annotation.fqName == fqNameBeforeClassAnnotation || - annotation.fqName == fqNameAfterClassAnnotation + 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()) + }) } - fun FunctionDescriptor.isAfter() = testAnnotationFqName(fqNameAfterAnnotation) - fun FunctionDescriptor.isBefore() = testAnnotationFqName(fqNameBeforeAnnotation) - fun FunctionDescriptor.isAfterClass() = testAnnotationFqName(fqNameAfterClassAnnotation) - fun FunctionDescriptor.isBeforeClass() = testAnnotationFqName(fqNameBeforeClassAnnotation) - fun FunctionDescriptor.isTest() = testAnnotationFqName(fqNameTestAnnotation) - - fun FunctionDescriptor.testAnnotationFqName(fqName:FqName) = annotations.any { it.fqName == fqNameTestAnnotation } + private fun createTestSuites(irFile: IrFile, annotationCollector: AnnotationCollector) { + annotationCollector.testClasses.forEach { _, testClass -> + generateTestSuite(testClass.owner, irFile, testClass.functions) + } + // TODO: Use another name for a file? + // generateTestSuite(irFile.name, irFile, annotationCollector.topLevelFunctions) + } + override fun lower(irFile: IrFile) { + val annotationCollector = AnnotationCollector() + irFile.acceptChildrenVoid(annotationCollector) + createTestSuites(irFile, annotationCollector) + } } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnitProcessor.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnitProcessor.kt deleted file mode 100644 index 7ee40f4f5cf..00000000000 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnitProcessor.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.jetbrains.kotlin.backend.konan.lower - -import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.konan.KonanBackendContext -import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor -import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid -import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid -import org.jetbrains.kotlin.name.FqName - -internal class UnitProcessor (val context: KonanBackendContext):FileLoweringPass { - val fqNameTestAnnotation = FqName("org.junit.Test") - val fqNameBeforeAnnotation = FqName("org.junit.Before") - val fqNameAfterAnnotation = FqName("org.junit.After") - val fqNameBeforeClassAnnotation = FqName("org.junit.BeforeClass") - val fqNameAfterClassAnnotation = FqName("org.junit.AfterClass") - - override fun lower(irFile: IrFile) { - val testFunction = mutableListOf() - val beforeFunction = mutableListOf() - val afterFunction = mutableListOf() - val beforeClassFunction = mutableListOf() - val afterClassFunction = mutableListOf() - val testClasses = mutableListOf() - irFile.acceptChildrenVoid(object: IrElementVisitorVoid { - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitClass(declaration: IrClass) { - val classDescriptor = declaration.descriptor - val doNotprocess = declaration.descriptor.staticScope.contributedMethods.none{it.junitFunction()} - if (!doNotprocess) { - testClasses.add(declaration.descriptor) - declaration.acceptChildrenVoid(object:IrElementVisitorVoid{ - override fun visitElement(element: IrElement) { - element.acceptChildrenVoid(this) - } - - override fun visitFunction(declaration: IrFunction) { - processFunction(declaration.descriptor) - } - }) - } - } - - override fun visitFunction(declaration: IrFunction) { - processFunction(declaration.descriptor) - } - - private fun processFunction(descriptor: FunctionDescriptor) { - when { - descriptor.test() -> testFunction.add(descriptor) - descriptor.before() -> beforeFunction.add(descriptor) - descriptor.after() -> afterFunction.add(descriptor) - descriptor.beforeClass() -> beforeClassFunction.add(descriptor) - descriptor.afterClass() -> afterClassFunction.add(descriptor) - } - } - }) - /** - * TODO: for all test classes generate registration... - */ - - } - - private fun FunctionDescriptor.junitFunction() = - annotations.any { annotation -> - hasJunitAnnotation(annotation) - } - - - private fun hasJunitAnnotation(annotation: AnnotationDescriptor): Boolean { - return annotation.fqName == fqNameTestAnnotation && - annotation.fqName == fqNameBeforeAnnotation && - annotation.fqName == fqNameAfterAnnotation && - annotation.fqName == fqNameBeforeClassAnnotation && - annotation.fqName == fqNameAfterClassAnnotation - } - - fun FunctionDescriptor.after() = testAnnotationFqName(fqNameAfterAnnotation) - fun FunctionDescriptor.before() = testAnnotationFqName(fqNameBeforeAnnotation) - fun FunctionDescriptor.afterClass() = testAnnotationFqName(fqNameAfterClassAnnotation) - fun FunctionDescriptor.beforeClass() = testAnnotationFqName(fqNameBeforeClassAnnotation) - fun FunctionDescriptor.test() = testAnnotationFqName(fqNameTestAnnotation) - - fun FunctionDescriptor.testAnnotationFqName(fqName:FqName) = annotations.any { it.fqName == fqNameTestAnnotation } - -} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/CapturedTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/CapturedTypes.kt index 356840b5f53..1841117948a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/CapturedTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/CapturedTypes.kt @@ -10,7 +10,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.* private val konanInternalPackageName = FqName("konan.internal") private val fakeCapturedTypeClassName = Name.identifier("FAKE_CAPTURED_TYPE_CLASS") -internal fun createFakeClass(packageName: FqName, className: Name) +internal fun createFakeClass(packageName: FqName, className: Name) = MutableClassDescriptor( EmptyPackageFragmentDescriptor( ErrorUtils.getErrorModule(), diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 2d43c27d7fe..885329518cc 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1952,7 +1952,7 @@ task deserialized_members(type: LinkKonanTest) { task testing_smoke(type: RunKonanTest) { source = "testing/smoke.kt" - flags = [] // TODO: Add flag to generate test-runner + flags = ['-tr'] // TODO: Add flag to generate test-runner } // Just check that the driver is able to produce runnable binaries. diff --git a/backend.native/tests/testing/smoke.kt b/backend.native/tests/testing/smoke.kt index b2817a9d4bc..b73faf0dffa 100644 --- a/backend.native/tests/testing/smoke.kt +++ b/backend.native/tests/testing/smoke.kt @@ -1,4 +1,5 @@ import kotlin.test.Test +import konan.test.* class A { @Test @@ -7,8 +8,17 @@ class A { } } -@Test -fun test() { - println("test") -} +//abstract class AA { +// abstract fun a(): T +//} +// +// +//class BB: AA() { +// override fun a() = 5 +//} + +//@Test +//fun test() { +// println("test") +//} diff --git a/runtime/src/main/kotlin/konan/test/Launcher.kt b/runtime/src/main/kotlin/konan/test/Launcher.kt index 5461f2c66d7..dd246facc9f 100644 --- a/runtime/src/main/kotlin/konan/test/Launcher.kt +++ b/runtime/src/main/kotlin/konan/test/Launcher.kt @@ -1,21 +1,5 @@ package konan.test -class A { - fun test() { println("test") } -} - -fun getTestSuites(): Collection { - return listOf( - object: TestClass("A") { - override fun createInstance() = A() - - init { - registerTestCase(BasicTestCase("test", A::test)) - } - } - ) -} - fun main(args:Array) { - TestRunner(getTestSuites()).run() + TestRunner.run() } diff --git a/runtime/src/main/kotlin/konan/test/TestListener.kt b/runtime/src/main/kotlin/konan/test/TestListener.kt index 0739a38ec1c..1d4020bc078 100644 --- a/runtime/src/main/kotlin/konan/test/TestListener.kt +++ b/runtime/src/main/kotlin/konan/test/TestListener.kt @@ -1,5 +1,7 @@ package konan.test +import kotlin.AssertionError + interface TestListener { fun pass(testCase: TestCase) fun fail(testCase: TestCase, e: AssertionError) diff --git a/runtime/src/main/kotlin/konan/test/TestRunner.kt b/runtime/src/main/kotlin/konan/test/TestRunner.kt index c2d7aaa23b0..269d3186052 100644 --- a/runtime/src/main/kotlin/konan/test/TestRunner.kt +++ b/runtime/src/main/kotlin/konan/test/TestRunner.kt @@ -1,6 +1,8 @@ package konan.test -class TestRunner(suites: Collection = emptyList()) { +import kotlin.AssertionError + +object TestRunner { object SimpleTestListener: TestListener { override fun pass(testCase: TestCase) = println("Pass: $testCase") @@ -10,7 +12,7 @@ class TestRunner(suites: Collection = emptyList()) { } - private val _suites = mutableListOf().apply { addAll(suites) } + private val _suites = mutableListOf() val suites: Collection get() = _suites fun register(suite: TestSuite) = _suites.add(suite) diff --git a/runtime/src/main/kotlin/konan/test/TestSuite.kt b/runtime/src/main/kotlin/konan/test/TestSuite.kt index d54e3848c03..cda9c553bea 100644 --- a/runtime/src/main/kotlin/konan/test/TestSuite.kt +++ b/runtime/src/main/kotlin/konan/test/TestSuite.kt @@ -1,5 +1,7 @@ package konan.test +import kotlin.AssertionError + interface TestCase { val name: String } @@ -10,7 +12,7 @@ interface TestSuite { fun run(listener: TestListener) } -enum class FunctionKind { +enum class TestFunctionKind { BEFORE, AFTER, BEFORE_CLASS, @@ -21,7 +23,7 @@ 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. - open class BasicTestCase>(override val name: String, val testFunction: F): TestCase { + class BasicTestCase>(override val name: String, val testFunction: F): TestCase { override fun toString(): String = name } @@ -29,22 +31,24 @@ abstract class AbstractTestSuite>(override val name: String): override val testCases: Map> get() = _testCases - private val specialFunctions = mutableMapOf>() - private fun Map>.getFunctions(type: FunctionKind) = + private val specialFunctions = mutableMapOf>() + private fun Map>.getFunctions(type: TestFunctionKind) = specialFunctions.getOrPut(type) { mutableSetOf() } - val before: Collection get() = specialFunctions.getFunctions(FunctionKind.BEFORE) - val after: Collection get() = specialFunctions.getFunctions(FunctionKind.AFTER) + val before: Collection get() = specialFunctions.getFunctions(TestFunctionKind.BEFORE) + val after: Collection get() = specialFunctions.getFunctions(TestFunctionKind.AFTER) // TODO: Must be in companions. Support it. - val beforeClass: Collection get() = specialFunctions.getFunctions(FunctionKind.BEFORE_CLASS) - val afterClass: Collection get() = specialFunctions.getFunctions(FunctionKind.AFTER_CLASS) + 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)) 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: FunctionKind, function: F) = + protected fun registerFunction(type: TestFunctionKind, function: F) = specialFunctions.getFunctions(type).add(function) protected abstract fun doBeforeClass() @@ -52,26 +56,29 @@ abstract class AbstractTestSuite>(override val name: String): protected abstract fun doTest(testCase: BasicTestCase) + init { + TestRunner.register(this) + } + override fun run(listener: TestListener) { doBeforeClass() testCases.values.forEach { try { doTest(it) + listener.pass(it) // TODO: merge catches? } catch (e: AssertionError) { listener.fail(it, e) } catch (e: Throwable) { listener.error(it, e) } - listener.pass(it) + } doAfterClass() } } -abstract class TestClass(name: String): AbstractTestSuite Unit>(name) { - - class TestClassCase(name: String, testFunction: T.() -> Unit): BasicTestCase Unit>(name, testFunction) +abstract class BaseClassSuite(name: String): AbstractTestSuite Unit>(name) { abstract fun createInstance(): T @@ -88,9 +95,7 @@ abstract class TestClass(name: String): AbstractTestSuite Unit>(name) } } -class TopLevelTestSuite(name: String): AbstractTestSuite<() -> Unit>(name) { - - class TopLevelTestCase(name: String, testFunction: () -> Unit): BasicTestCase<() -> Unit>(name, testFunction) +class BaseTopLevelSuite(name: String): AbstractTestSuite<() -> Unit>(name) { override fun doBeforeClass() = beforeClass.forEach { it() } override fun doAfterClass() = afterClass.forEach { it() }