unit-tests: Simple support for test classes

This commit is contained in:
Ilya Matveev
2017-09-11 13:51:04 +03:00
committed by ilmat192
parent 1abb430f15
commit 45992a2b75
12 changed files with 395 additions and 224 deletions
@@ -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())
@@ -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)
@@ -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)
}
}
@@ -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)
}
}
@@ -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 }
}
@@ -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(),
+1 -1
View File
@@ -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.
+14 -4
View File
@@ -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 -17
View File
@@ -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)
+21 -16
View File
@@ -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() }