unit-tests: Top level test support
This commit is contained in:
+34
-19
@@ -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<TestProcessor.FunctionKind, IrEnumEntrySymbol>()
|
||||
fun getTestFunctionKind(kind: TestProcessor.FunctionKind): IrEnumEntrySymbol = testFunctionKindCache.getOrPut(kind) {
|
||||
|
||||
+248
-173
@@ -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 <T: IrElement> IrStatementsBuilder<T>.generateFunctionRegistration(
|
||||
receiver: IrValueSymbol,
|
||||
registerTestCase: IrFunctionSymbol,
|
||||
registerFunction: IrFunctionSymbol,
|
||||
functions: Collection<TestFunction>) {
|
||||
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<TestFunction>()
|
||||
|
||||
fun registerFunction(function: IrFunctionSymbol, kinds: Collection<FunctionKind>) = kinds.forEach {
|
||||
@@ -76,13 +135,13 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
|
||||
}
|
||||
|
||||
private inner class AnnotationCollector : IrElementVisitorVoid {
|
||||
val testClasses = mutableMapOf<IrClassSymbol, TestSuite>()
|
||||
val testClasses = mutableMapOf<IrClassSymbol, ClassSuite>()
|
||||
val topLevelFunctions = mutableListOf<TestFunction>()
|
||||
|
||||
private fun MutableMap<IrClassSymbol, TestSuite>.getTestSuite(key: IrClassSymbol) =
|
||||
getOrPut(key) { TestSuite(key) }
|
||||
private fun MutableMap<IrClassSymbol, ClassSuite>.getTestSuite(key: IrClassSymbol) =
|
||||
getOrPut(key) { ClassSuite(key) }
|
||||
|
||||
private fun MutableMap<IrClassSymbol, TestSuite>.getTestSuite(key: ClassDescriptor) =
|
||||
private fun MutableMap<IrClassSymbol, ClassSuite>.getTestSuite(key: ClassDescriptor) =
|
||||
getTestSuite(symbols.symbolTable.referenceClass(key))
|
||||
|
||||
private fun MutableList<TestFunction>.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<T>.createInstance() function. */
|
||||
private fun instanceGetterBuilder(testClass: IrClassSymbol, testSuite: IrClassSymbol) =
|
||||
object : SymbolWithIrBuilder<IrSimpleFunctionSymbol, IrFunction>() {
|
||||
/** Builds the BaseClassSuite<T>.createInstance() function. */
|
||||
private inner class InstanceGetterBuilder(val testClass: IrClassSymbol, val testSuite: IrClassSymbol)
|
||||
: SymbolWithIrBuilder<IrSimpleFunctionSymbol, IrFunction>() {
|
||||
|
||||
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<T>
|
||||
* where T is the test class.
|
||||
*/
|
||||
private inner class ClassSuiteConstructorBuilder(val testClass: IrClassSymbol,
|
||||
val testSuite: IrClassSymbol,
|
||||
val functions: Collection<TestFunction>)
|
||||
: SymbolWithIrBuilder<IrConstructorSymbol, IrConstructor>() {
|
||||
|
||||
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<TestFunction>) =
|
||||
object : SymbolWithIrBuilder<IrConstructorSymbol, IrConstructor>() {
|
||||
|
||||
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<T> where T is the test class.
|
||||
*/
|
||||
private inner class ClassSuiteBuilder(val testClass: IrClassSymbol,
|
||||
val containingDeclaration: DeclarationDescriptor,
|
||||
val functions: Collection<TestFunction>)
|
||||
: SymbolWithIrBuilder<IrClassSymbol, IrClass>() {
|
||||
|
||||
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<TestFunction>) =
|
||||
object: SymbolWithIrBuilder<IrClassSymbol, IrClass>() {
|
||||
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<TestFunction>) =
|
||||
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<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())
|
||||
private fun generateTopLevelSuite(irFile: IrFile, functions: Collection<TestFunction>) {
|
||||
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()
|
||||
|
||||
@@ -8,17 +8,8 @@ class A {
|
||||
}
|
||||
}
|
||||
|
||||
//abstract class AA<T> {
|
||||
// abstract fun a(): T
|
||||
//}
|
||||
//
|
||||
//
|
||||
//class BB: AA<Int>() {
|
||||
// override fun a() = 5
|
||||
//}
|
||||
|
||||
//@Test
|
||||
//fun test() {
|
||||
// println("test")
|
||||
//}
|
||||
@Test
|
||||
fun test() {
|
||||
println("test")
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<TestSuite>()
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import kotlin.AssertionError
|
||||
|
||||
interface TestCase {
|
||||
val name: String
|
||||
val suite: TestSuite
|
||||
}
|
||||
|
||||
interface TestSuite {
|
||||
@@ -23,8 +24,12 @@ 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.
|
||||
class BasicTestCase<F: Function<Unit>>(override val name: String, val testFunction: F): TestCase {
|
||||
override fun toString(): String = name
|
||||
class BasicTestCase<F: Function<Unit>>(
|
||||
override val name: String,
|
||||
override val suite: AbstractTestSuite<F>,
|
||||
val testFunction: F
|
||||
) : TestCase {
|
||||
override fun toString(): String = "$suite.$name"
|
||||
}
|
||||
|
||||
private val _testCases = mutableMapOf<String, BasicTestCase<F>>()
|
||||
@@ -42,13 +47,11 @@ abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String):
|
||||
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))
|
||||
private fun registerTestCase(testCase: BasicTestCase<F>) = _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<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: 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<T>(name: String): AbstractTestSuite<T.() -> 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<T.() -> 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() }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user