unit-tests: Top level test support

This commit is contained in:
Ilya Matveev
2017-09-13 19:41:39 +03:00
committed by ilmat192
parent 45992a2b75
commit f1c59707dd
6 changed files with 332 additions and 228 deletions
@@ -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) {
@@ -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()
+4 -13
View File
@@ -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)
}
}
+27 -19
View File
@@ -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() }
}
}