unit-tests: Support Ignore annotation
This commit is contained in:
+11
-7
@@ -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>()
|
||||
|
||||
+38
-16
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user