unit-tests: Support Ignore annotation

This commit is contained in:
Ilya Matveev
2017-09-18 14:07:33 +03:00
committed by ilmat192
parent 6440e6694d
commit 1e5a27505a
6 changed files with 104 additions and 41 deletions
@@ -192,25 +192,29 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym
val testFunctionKind = getKonanTestClass("TestFunctionKind")
val baseClassSuiteConstructor = baseClassSuite.descriptor.constructors.single {
it.valueParameters.size == 1 && KotlinBuiltIns.isString(it.valueParameters[0].type)
it.valueParameters.size == 2 &&
KotlinBuiltIns.isString(it.valueParameters[0].type) && // name: String
KotlinBuiltIns.isBoolean(it.valueParameters[1].type) // ignored: Boolean
}
val topLevelSuiteConstructor = symbolTable.referenceConstructor(topLevelSuite.descriptor.constructors.single {
it.valueParameters.size == 1 && KotlinBuiltIns.isString(it.valueParameters[0].type)
it.valueParameters.size == 1 &&
KotlinBuiltIns.isString(it.valueParameters[0].type) // name: String
})
val topLevelSuiteRegisterFunction =
getFunction(Name.identifier("registerFunction"), topLevelSuite.descriptor.defaultType) {
it.valueParameters.size == 2 &&
it.valueParameters[0].type == testFunctionKind.descriptor.defaultType &&
it.valueParameters[1].type.isFunctionType
it.valueParameters[0].type == testFunctionKind.descriptor.defaultType && // kind: TestFunctionKind
it.valueParameters[1].type.isFunctionType // function: () -> Unit
}
val topLevelSuiteRegisterTestCase =
getFunction(Name.identifier("registerTestCase"), topLevelSuite.descriptor.defaultType) {
it.valueParameters.size == 2 &&
KotlinBuiltIns.isString(it.valueParameters[0].type) &&
it.valueParameters[1].type.isFunctionType
it.valueParameters.size == 3 &&
KotlinBuiltIns.isString(it.valueParameters[0].type) && // name: String
it.valueParameters[1].type.isFunctionType && // function: () -> Unit
KotlinBuiltIns.isBoolean(it.valueParameters[2].type) // ignored: Boolean
}
private val testFunctionKindCache = mutableMapOf<TestProcessor.FunctionKind, IrEnumEntrySymbol>()
@@ -48,8 +48,10 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
object TEST_SUITE_GENERATED_MEMBER: IrDeclarationOriginImpl("TEST_SUITE_GENERATED_MEMBER")
companion object {
val companionGetterName = Name.identifier("getCompanion")
val instanceGetterName = Name.identifier("createInstance")
val COMPANION_GETTER_NAME = Name.identifier("getCompanion")
val INSTANCE_GETTER_NAME = Name.identifier("createInstance")
val IGNORE_FQ_NAME = FqName.fromSegments(listOf("kotlin", "test" , "Ignore"))
}
val symbols = context.ir.symbols
@@ -89,11 +91,18 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
context.builtIns.stringType,
it.function.descriptor.name.identifier)
)
putValueArgument(1, IrFunctionReferenceImpl(UNDEFINED_OFFSET,
putValueArgument(1, IrFunctionReferenceImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
descriptor.valueParameters[1].type,
it.function,
it.function.descriptor, emptyMap()))
putValueArgument(2, IrConstImpl.boolean(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
context.builtIns.booleanType,
it.ignored
))
}
} else {
// Call registerFunction(kind: TestFunctionKind, () -> Unit) method.
@@ -140,7 +149,10 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
private val FunctionKind.runtimeKind: IrEnumEntrySymbol
get() = symbols.getTestFunctionKind(this)
private data class TestFunction(val function: IrFunctionSymbol, val kind: FunctionKind)
private data class TestFunction(val function: IrFunctionSymbol, val kind: FunctionKind) {
val ignored: Boolean
get() = function.descriptor.annotations.hasAnnotation(IGNORE_FQ_NAME)
}
private inner class TestClass(val ownerClass: IrClassSymbol) {
var companion: IrClassSymbol? = null
@@ -322,7 +334,8 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
val testClassType: KotlinType,
val testCompanionType: KotlinType,
val testSuite: IrClassSymbol,
val functions: Collection<TestFunction>)
val functions: Collection<TestFunction>,
val ignored: Boolean)
: SymbolWithIrBuilder<IrConstructorSymbol, IrConstructor>() {
private fun IrClassSymbol.getFunction(name: String, predicate: (FunctionDescriptor) -> Boolean) =
@@ -338,14 +351,15 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
createParameterDeclarations()
val registerTestCase = testSuite.getFunction("registerTestCase") {
it.valueParameters.size == 2 &&
KotlinBuiltIns.isString(it.valueParameters[0].type) &&
it.valueParameters[1].type.isFunctionType
it.valueParameters.size == 3 &&
KotlinBuiltIns.isString(it.valueParameters[0].type) && // name: String
it.valueParameters[1].type.isFunctionType && // function: testClassType.() -> Unit
KotlinBuiltIns.isBoolean(it.valueParameters[2].type) // ignored: Boolean
}
val registerFunction = testSuite.getFunction("registerFunction") {
it.valueParameters.size == 2 &&
it.valueParameters[0].type == symbols.testFunctionKind.descriptor.defaultType &&
it.valueParameters[1].type.isFunctionType
it.valueParameters[0].type == symbols.testFunctionKind.descriptor.defaultType && // kind: TestFunctionKind
it.valueParameters[1].type.isFunctionType // function: () -> Unit
}
body = context.createIrBuilder(symbol).irBlockBody {
@@ -364,6 +378,12 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
context.builtIns.stringType,
suiteName)
)
putValueArgument(1, IrConstImpl.boolean(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
context.builtIns.booleanType,
ignored
))
}
generateFunctionRegistration(testSuite.owner.thisReceiver!!.symbol,
registerTestCase, registerFunction, functions)
@@ -397,7 +417,8 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
val functions: Collection<TestFunction>)
: SymbolWithIrBuilder<IrClassSymbol, IrClass>() {
private val IrClassSymbol.isObject: Boolean get() = descriptor.kind == ClassKind.OBJECT
private val IrClassSymbol.ignored: Boolean get() = descriptor.annotations.hasAnnotation(IGNORE_FQ_NAME)
private val IrClassSymbol.isObject: Boolean get() = descriptor.kind == ClassKind.OBJECT
val suiteName = testClass.descriptor.fqNameSafe.toString()
val suiteClassName = testClass.descriptor.name.synthesizeSuiteClassName()
@@ -412,19 +433,20 @@ internal class TestProcessor (val context: KonanBackendContext): FileLoweringPas
val superType = baseClassSuiteDescriptor.defaultType.replace(listOf(testClassType, testCompanionType))
val constructorBuilder = ClassSuiteConstructorBuilder(
suiteName, testClassType, testCompanionType, symbol, functions
suiteName, testClassType, testCompanionType, symbol, functions, testClass.ignored
)
val instanceGetterBuilder: GetterBuilder
val companionGetterBuilder: GetterBuilder?
init {
if (testClass.isObject) {
instanceGetterBuilder = ObjectGetterBuilder(testClass, symbol, instanceGetterName)
companionGetterBuilder = ObjectGetterBuilder(testClass, symbol, companionGetterName)
instanceGetterBuilder = ObjectGetterBuilder(testClass, symbol, INSTANCE_GETTER_NAME)
companionGetterBuilder = ObjectGetterBuilder(testClass, symbol, COMPANION_GETTER_NAME)
} else {
instanceGetterBuilder = InstanceGetterBuilder(testClass, symbol, instanceGetterName)
instanceGetterBuilder = InstanceGetterBuilder(testClass, symbol, INSTANCE_GETTER_NAME)
companionGetterBuilder = testCompanion?.let {
ObjectGetterBuilder(it, symbol, companionGetterName)
ObjectGetterBuilder(it, symbol, COMPANION_GETTER_NAME)
}
}
}
@@ -8,6 +8,7 @@ interface TestListener {
fun startSuite(suite: TestSuite)
fun endSuite(suite: TestSuite)
fun ignoreSuite(suite: TestSuite)
fun start(testCase: TestCase)
fun pass(testCase: TestCase)
@@ -8,6 +8,7 @@ object TestRunner {
override fun startSuite(suite: TestSuite) = println("Starting test suite: $suite")
override fun endSuite(suite: TestSuite) = println("Test suite finished: $suite")
override fun ignoreSuite(suite: TestSuite) = println("Test suite ignored: $suite")
override fun start(testCase: TestCase) = println("Starting test case: $testCase")
override fun pass(testCase: TestCase) = println("Passed: $testCase")
@@ -28,9 +29,13 @@ object TestRunner {
fun run() {
SimpleTestListener.startTesting(this)
suites.forEach {
SimpleTestListener.startSuite(it)
it.run(SimpleTestListener)
SimpleTestListener.endSuite(it)
if (it.ignored) {
SimpleTestListener.ignoreSuite(it)
} else {
SimpleTestListener.startSuite(it)
it.run(SimpleTestListener)
SimpleTestListener.endSuite(it)
}
}
SimpleTestListener.endTesting(this)
}
+27 -12
View File
@@ -1,12 +1,16 @@
package konan.test
import kotlin.IllegalArgumentException
interface TestCase {
val name: String
val ignored: Boolean
val suite: TestSuite
}
interface TestSuite {
val name: String
val ignored: Boolean
val testCases: Map<String, TestCase>
fun run(listener: TestListener)
}
@@ -18,14 +22,16 @@ enum class TestFunctionKind {
AFTER_CLASS
}
abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String): TestSuite {
abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String, override val ignored: Boolean)
: TestSuite {
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,
override val suite: AbstractTestSuite<F>,
val testFunction: F
val testFunction: F,
override val ignored: Boolean
) : TestCase {
override fun toString(): String = "$name ($suite)"
}
@@ -35,8 +41,12 @@ abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String):
get() = _testCases
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)
fun registerTestCase(name: String, testFunction: F, ignored: Boolean) =
registerTestCase(createTestCase(name, testFunction, ignored))
fun createTestCase(name: String, testFunction: F, ignored: Boolean) =
BasicTestCase(name, this, testFunction, ignored)
protected abstract fun doBeforeClass()
protected abstract fun doAfterClass()
@@ -50,12 +60,16 @@ abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String):
override fun run(listener: TestListener) {
doBeforeClass()
testCases.values.forEach {
try {
listener.start(it)
doTest(it)
listener.pass(it)
} catch (e: Throwable) {
listener.fail(it, e)
if (it.ignored) {
listener.ignore(it)
} else {
try {
listener.start(it)
doTest(it)
listener.pass(it)
} catch (e: Throwable) {
listener.fail(it, e)
}
}
}
@@ -63,7 +77,8 @@ abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String):
}
}
abstract class BaseClassSuite<INSTANCE, COMPANION>(name: String): AbstractTestSuite<INSTANCE.() -> Unit>(name) {
abstract class BaseClassSuite<INSTANCE, COMPANION>(name: String, ignored: Boolean)
: AbstractTestSuite<INSTANCE.() -> Unit>(name, ignored) {
// These two methods are overrided in test suite classes generated by the compiler.
abstract fun createInstance(): INSTANCE
@@ -117,7 +132,7 @@ abstract class BaseClassSuite<INSTANCE, COMPANION>(name: String): AbstractTestSu
private typealias TopLevelFun = () -> Unit
class TopLevelSuite(name: String): AbstractTestSuite<TopLevelFun>(name) {
class TopLevelSuite(name: String): AbstractTestSuite<TopLevelFun>(name, false) {
private val specialFunctions = mutableMapOf<TestFunctionKind, MutableSet<TopLevelFun>>()
private fun getFunctions(type: TestFunctionKind) = specialFunctions.getOrPut(type) { mutableSetOf() }
@@ -1,28 +1,44 @@
package kotlin.test
/**
* Marks a function as a test.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class Test
// Not supported in Kotlin/Common
/**
* Marks a function to be executed before a suite. Not supported in Kotlin/Common.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class BeforeClass
// Not supported in Kotlin/Common
/**
* Marks a function to be executed after a suite. Not supported in Kotlin/Common.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class AfterClass
/**
* Marks a function to be executed before a test.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class BeforeEach
/**
* Marks a function to be executed after a test.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class AfterEach
// TODO: Support.
/**
* Marks a test or a suite as ignored/pending.
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class Ignore