unit-tests: Simple support for test classes
This commit is contained in:
+2
@@ -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())
|
||||
|
||||
+6
-3
@@ -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)
|
||||
|
||||
+40
@@ -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<TestProcessor.FunctionKind, IrEnumEntrySymbol>()
|
||||
fun getTestFunctionKind(kind: TestProcessor.FunctionKind): IrEnumEntrySymbol = testFunctionKindCache.getOrPut(kind) {
|
||||
symbolTable.referenceEnumEntry(testFunctionKind.descriptor.getMemberScope().getContributedClassifier(
|
||||
kind.runtimeKindName, NoLookupLocation.FROM_BACKEND
|
||||
) as ClassDescriptor)
|
||||
}
|
||||
}
|
||||
+303
-85
@@ -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<IrFunctionSymbol>()
|
||||
val beforeFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val afterFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
}
|
||||
val FunctionKind.runtimeKind: IrEnumEntrySymbol
|
||||
get() = symbols.getTestFunctionKind(this)
|
||||
|
||||
private inner class TopLevelTestSuite() {
|
||||
val testFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val beforeFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val afterFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val beforeClassFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val afterClassFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
private data class TestFunction(val function: IrFunctionSymbol, val kind: FunctionKind)
|
||||
|
||||
private class TestSuite(val owner: IrClassSymbol) {
|
||||
val functions = mutableListOf<TestFunction>()
|
||||
|
||||
fun registerFunction(function: IrFunctionSymbol, kinds: Collection<FunctionKind>) = kinds.forEach {
|
||||
functions.add(TestFunction(function, it))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class AnnotationCollector : IrElementVisitorVoid {
|
||||
val testClasses = mutableMapOf<IrClassSymbol, TestClass>()
|
||||
val topLevelTestSuite = TopLevelTestSuite()
|
||||
val testClasses = mutableMapOf<IrClassSymbol, TestSuite>()
|
||||
val topLevelFunctions = mutableListOf<TestFunction>()
|
||||
|
||||
private fun MutableMap<IrClassSymbol, TestSuite>.getTestSuite(key: IrClassSymbol) =
|
||||
getOrPut(key) { TestSuite(key) }
|
||||
|
||||
private fun MutableMap<IrClassSymbol, TestSuite>.getTestSuite(key: ClassDescriptor) =
|
||||
getTestSuite(symbols.symbolTable.referenceClass(key))
|
||||
|
||||
private fun MutableList<TestFunction>.registerFunction(
|
||||
function: IrFunctionSymbol,
|
||||
kinds: Collection<FunctionKind>) = 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<T>.createInstance() function. */
|
||||
private fun instanceGetterBuilder(testClass: IrClassSymbol, testSuite: IrClassSymbol) =
|
||||
object : SymbolWithIrBuilder<IrSimpleFunctionSymbol, IrFunction>() {
|
||||
|
||||
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<TestFunction>) =
|
||||
object : SymbolWithIrBuilder<IrConstructorSymbol, IrConstructor>() {
|
||||
|
||||
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<TestFunction>) =
|
||||
object: SymbolWithIrBuilder<IrClassSymbol, IrClass>() {
|
||||
|
||||
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<TestFunction>) {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
-95
@@ -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<FunctionDescriptor>()
|
||||
val beforeFunction = mutableListOf<FunctionDescriptor>()
|
||||
val afterFunction = mutableListOf<FunctionDescriptor>()
|
||||
val beforeClassFunction = mutableListOf<FunctionDescriptor>()
|
||||
val afterClassFunction = mutableListOf<FunctionDescriptor>()
|
||||
val testClasses = mutableListOf<ClassDescriptor>()
|
||||
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 }
|
||||
|
||||
}
|
||||
+1
-1
@@ -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(),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<T> {
|
||||
// abstract fun a(): T
|
||||
//}
|
||||
//
|
||||
//
|
||||
//class BB: AA<Int>() {
|
||||
// override fun a() = 5
|
||||
//}
|
||||
|
||||
//@Test
|
||||
//fun test() {
|
||||
// println("test")
|
||||
//}
|
||||
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
package konan.test
|
||||
|
||||
class A {
|
||||
fun test() { println("test") }
|
||||
}
|
||||
|
||||
fun getTestSuites(): Collection<TestSuite> {
|
||||
return listOf(
|
||||
object: TestClass<A>("A") {
|
||||
override fun createInstance() = A()
|
||||
|
||||
init {
|
||||
registerTestCase(BasicTestCase("test", A::test))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun main(args:Array<String>) {
|
||||
TestRunner(getTestSuites()).run()
|
||||
TestRunner.run()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package konan.test
|
||||
|
||||
import kotlin.AssertionError
|
||||
|
||||
interface TestListener {
|
||||
fun pass(testCase: TestCase)
|
||||
fun fail(testCase: TestCase, e: AssertionError)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package konan.test
|
||||
|
||||
class TestRunner(suites: Collection<TestSuite> = 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<TestSuite> = emptyList()) {
|
||||
|
||||
}
|
||||
|
||||
private val _suites = mutableListOf<TestSuite>().apply { addAll(suites) }
|
||||
private val _suites = mutableListOf<TestSuite>()
|
||||
val suites: Collection<TestSuite> get() = _suites
|
||||
|
||||
fun register(suite: TestSuite) = _suites.add(suite)
|
||||
|
||||
@@ -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<F: Function<Unit>>(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<F: Function<Unit>>(override val name: String, val testFunction: F): TestCase {
|
||||
class BasicTestCase<F: Function<Unit>>(override val name: String, val testFunction: F): TestCase {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
@@ -29,22 +31,24 @@ abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String):
|
||||
override val testCases: Map<String, BasicTestCase<F>>
|
||||
get() = _testCases
|
||||
|
||||
private val specialFunctions = mutableMapOf<FunctionKind, MutableSet<F>>()
|
||||
private fun Map<FunctionKind, Set<F>>.getFunctions(type: FunctionKind) =
|
||||
private val specialFunctions = mutableMapOf<TestFunctionKind, MutableSet<F>>()
|
||||
private fun Map<TestFunctionKind, Set<F>>.getFunctions(type: TestFunctionKind) =
|
||||
specialFunctions.getOrPut(type) { mutableSetOf() }
|
||||
|
||||
val before: Collection<F> get() = specialFunctions.getFunctions(FunctionKind.BEFORE)
|
||||
val after: Collection<F> get() = specialFunctions.getFunctions(FunctionKind.AFTER)
|
||||
val before: Collection<F> get() = specialFunctions.getFunctions(TestFunctionKind.BEFORE)
|
||||
val after: Collection<F> get() = specialFunctions.getFunctions(TestFunctionKind.AFTER)
|
||||
|
||||
// TODO: Must be in companions. Support it.
|
||||
val beforeClass: Collection<F> get() = specialFunctions.getFunctions(FunctionKind.BEFORE_CLASS)
|
||||
val afterClass: Collection<F> get() = specialFunctions.getFunctions(FunctionKind.AFTER_CLASS)
|
||||
val beforeClass: Collection<F> get() = specialFunctions.getFunctions(TestFunctionKind.BEFORE_CLASS)
|
||||
val afterClass: Collection<F> get() = specialFunctions.getFunctions(TestFunctionKind.AFTER_CLASS)
|
||||
|
||||
fun registerTestCase(name: String, testFunction: F) = registerTestCase(BasicTestCase(name, testFunction))
|
||||
|
||||
protected fun registerTestCase(testCase: BasicTestCase<F>) = _testCases.put(testCase.name, testCase)
|
||||
protected fun registerTestCases(testCases: Collection<BasicTestCase<F>>) = testCases.forEach { registerTestCase(it) }
|
||||
protected fun registerTestCases(vararg testCases: BasicTestCase<F>) = 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<F: Function<Unit>>(override val name: String):
|
||||
|
||||
protected abstract fun doTest(testCase: BasicTestCase<F>)
|
||||
|
||||
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<T>(name: String): AbstractTestSuite<T.() -> Unit>(name) {
|
||||
|
||||
class TestClassCase<T>(name: String, testFunction: T.() -> Unit): BasicTestCase<T.() -> Unit>(name, testFunction)
|
||||
abstract class BaseClassSuite<T>(name: String): AbstractTestSuite<T.() -> Unit>(name) {
|
||||
|
||||
abstract fun createInstance(): T
|
||||
|
||||
@@ -88,9 +95,7 @@ abstract class TestClass<T>(name: String): AbstractTestSuite<T.() -> 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() }
|
||||
|
||||
Reference in New Issue
Block a user