From 1e5a27505ab7a39972e370edb01af4b5705214f0 Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Mon, 18 Sep 2017 14:07:33 +0300 Subject: [PATCH] unit-tests: Support Ignore annotation --- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 18 ++++--- .../backend/konan/lower/TestProcessor.kt | 54 +++++++++++++------ .../main/kotlin/konan/test/TestListener.kt | 1 + .../src/main/kotlin/konan/test/TestRunner.kt | 11 ++-- .../src/main/kotlin/konan/test/TestSuite.kt | 39 +++++++++----- .../src/main/kotlin/kotlin/test/Annotation.kt | 22 ++++++-- 6 files changed, 104 insertions(+), 41 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 16dcaac70e7..886734b1adb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -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() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt index 9c2956a0f81..9f82f561166 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/TestProcessor.kt @@ -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) + val functions: Collection, + val ignored: Boolean) : SymbolWithIrBuilder() { 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) : SymbolWithIrBuilder() { - 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) } } } diff --git a/runtime/src/main/kotlin/konan/test/TestListener.kt b/runtime/src/main/kotlin/konan/test/TestListener.kt index ae922be8035..277c1e09bba 100644 --- a/runtime/src/main/kotlin/konan/test/TestListener.kt +++ b/runtime/src/main/kotlin/konan/test/TestListener.kt @@ -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) diff --git a/runtime/src/main/kotlin/konan/test/TestRunner.kt b/runtime/src/main/kotlin/konan/test/TestRunner.kt index 4aec7f09aa9..6f938d48298 100644 --- a/runtime/src/main/kotlin/konan/test/TestRunner.kt +++ b/runtime/src/main/kotlin/konan/test/TestRunner.kt @@ -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) } diff --git a/runtime/src/main/kotlin/konan/test/TestSuite.kt b/runtime/src/main/kotlin/konan/test/TestSuite.kt index ef2c8c002ad..6622122a79f 100644 --- a/runtime/src/main/kotlin/konan/test/TestSuite.kt +++ b/runtime/src/main/kotlin/konan/test/TestSuite.kt @@ -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 fun run(listener: TestListener) } @@ -18,14 +22,16 @@ enum class TestFunctionKind { AFTER_CLASS } -abstract class AbstractTestSuite>(override val name: String): TestSuite { +abstract class AbstractTestSuite>(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>( override val name: String, override val suite: AbstractTestSuite, - val testFunction: F + val testFunction: F, + override val ignored: Boolean ) : TestCase { override fun toString(): String = "$name ($suite)" } @@ -35,8 +41,12 @@ abstract class AbstractTestSuite>(override val name: String): get() = _testCases private fun registerTestCase(testCase: BasicTestCase) = _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>(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>(override val name: String): } } -abstract class BaseClassSuite(name: String): AbstractTestSuite Unit>(name) { +abstract class BaseClassSuite(name: String, ignored: Boolean) + : AbstractTestSuite 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(name: String): AbstractTestSu private typealias TopLevelFun = () -> Unit -class TopLevelSuite(name: String): AbstractTestSuite(name) { +class TopLevelSuite(name: String): AbstractTestSuite(name, false) { private val specialFunctions = mutableMapOf>() private fun getFunctions(type: TestFunctionKind) = specialFunctions.getOrPut(type) { mutableSetOf() } diff --git a/runtime/src/main/kotlin/kotlin/test/Annotation.kt b/runtime/src/main/kotlin/kotlin/test/Annotation.kt index a7a0603b77c..7e471e047c6 100644 --- a/runtime/src/main/kotlin/kotlin/test/Annotation.kt +++ b/runtime/src/main/kotlin/kotlin/test/Annotation.kt @@ -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