unit-tests: Create a simple test runner implementation
This commit is contained in:
committed by
ilmat192
parent
77195e8e90
commit
f26e5b5f02
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.cli.common.CLICompiler
|
||||
import org.jetbrains.kotlin.cli.common.CLITool
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
@@ -133,6 +134,8 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
|
||||
|
||||
put(ENABLE_ASSERTIONS, arguments.enableAssertions)
|
||||
|
||||
put(GENERATE_TEST_RUNNER, arguments.generateTestRunner)
|
||||
|
||||
// We need to download dependencies only if we use them ( = there are files to compile).
|
||||
put(CHECK_DEPENDENCIES, if (configuration.kotlinSourceRoots.isNotEmpty()) {
|
||||
true
|
||||
|
||||
@@ -24,11 +24,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
|
||||
// Prepend them with a single dash.
|
||||
// Keep the list lexically sorted.
|
||||
|
||||
@Argument(value = "-enable_assertions", shortName = "-ea", description = "Enable runtime assertions in generated code")
|
||||
var enableAssertions: Boolean = false
|
||||
|
||||
@Argument(value = "-g", description = "Enable emitting debug information")
|
||||
var debug: Boolean = false
|
||||
|
||||
@Argument(value = "-enable_assertions", shortName = "-ea", description = "Enable runtime assertions in generated code")
|
||||
var enableAssertions: Boolean = false
|
||||
@Argument(value = "-generate_test_runner", shortName = "-tr", description = "Produce a runner for unit tests")
|
||||
var generateTestRunner: Boolean = false
|
||||
|
||||
@Argument(value = "-includeBinary", shortName = "-ib", valueDescription = "<path>", description = "Pack external binary within the klib")
|
||||
var includeBinaries: Array<String>? = null
|
||||
|
||||
+2
@@ -37,6 +37,8 @@ class KonanConfigKeys {
|
||||
= CompilerConfigurationKey.create("enable backend phases")
|
||||
val ENTRY: CompilerConfigurationKey<String?>
|
||||
= CompilerConfigurationKey.create("fully qualified main() name")
|
||||
val GENERATE_TEST_RUNNER: CompilerConfigurationKey<Boolean>
|
||||
= CompilerConfigurationKey.create("generate test runner")
|
||||
val INCLUDED_BINARY_FILES: CompilerConfigurationKey<List<String>>
|
||||
= CompilerConfigurationKey.create("included binary file paths")
|
||||
val LIBRARY_FILES: CompilerConfigurationKey<List<String>>
|
||||
|
||||
+4
@@ -40,6 +40,10 @@ internal class KonanLower(val context: Context) {
|
||||
fun lowerModule(irModule: IrModuleFragment) {
|
||||
val phaser = PhaseManager(context)
|
||||
|
||||
//phaser.phase(KonanPhase.TEST_PROCESSOR) {
|
||||
// TestProcessor(context).process(irModule)
|
||||
//}
|
||||
|
||||
phaser.phase(KonanPhase.LOWER_SPECIAL_CALLS) {
|
||||
irModule.files.forEach(SpecialCallsLowering(context)::lower)
|
||||
}
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ enum class KonanPhase(val description: String,
|
||||
/* */ SERIALIZER("Serialize descriptor tree and inline IR bodies"),
|
||||
/* */ BACKEND("All backend"),
|
||||
/* ... */ LOWER("IR Lowering"),
|
||||
/* ... ... */ TEST_PROCESSOR("Unit test processor"), // TODO: It is not a lowering. Move into a separate phase before lowering?
|
||||
/* ... ... */ LOWER_SPECIAL_CALLS("Special calls processing before inlining"),
|
||||
/* ... ... */ LOWER_INLINE_CONSTRUCTORS("Inline constructors transformation", LOWER_SPECIAL_CALLS),
|
||||
/* ... ... */ LOWER_INLINE("Functions inlining", LOWER_INLINE_CONSTRUCTORS, LOWER_SPECIAL_CALLS),
|
||||
|
||||
+15
-1
@@ -20,6 +20,8 @@ import org.jetbrains.kotlin.backend.konan.reportCompilationError
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
|
||||
import org.jetbrains.kotlin.backend.konan.report
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
@@ -36,7 +38,18 @@ internal fun findMainEntryPoint(context: Context): FunctionDescriptor? {
|
||||
val config = context.config.configuration
|
||||
if (config.get(KonanConfigKeys.PRODUCE) != CompilerOutputKind.PROGRAM) return null
|
||||
|
||||
val entryPoint = FqName(config.get(KonanConfigKeys.ENTRY) ?: defaultEntryName)
|
||||
val userEntryName = config.get(KonanConfigKeys.ENTRY)
|
||||
val entryPoint: FqName
|
||||
if (config.getBoolean(KonanConfigKeys.GENERATE_TEST_RUNNER)) {
|
||||
entryPoint = FqName(testEntryName)
|
||||
if (userEntryName != null) {
|
||||
config.report(CompilerMessageSeverity.WARNING,
|
||||
"Custom entry point is ignored if test runner is generated"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
entryPoint = FqName(userEntryName ?: defaultEntryName)
|
||||
}
|
||||
|
||||
val entryName = entryPoint.shortName()
|
||||
val packageName = entryPoint.parent()
|
||||
@@ -57,6 +70,7 @@ internal fun findMainEntryPoint(context: Context): FunctionDescriptor? {
|
||||
}
|
||||
|
||||
private val defaultEntryName = "main"
|
||||
private val testEntryName = "konan.test.main"
|
||||
|
||||
private val defaultEntryPackage = FqName.ROOT
|
||||
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
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.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.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.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
private inner class TestClass(val clazz: IrClassSymbol) {
|
||||
val testFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val beforeFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
val afterFunctions = mutableListOf<IrFunctionSymbol>()
|
||||
}
|
||||
|
||||
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 inner class AnnotationCollector : IrElementVisitorVoid {
|
||||
val testClasses = mutableMapOf<IrClassSymbol, TestClass>()
|
||||
val topLevelTestSuite = TopLevelTestSuite()
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
val descriptor = declaration.descriptor
|
||||
val symbol = declaration.symbol
|
||||
|
||||
// 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)
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.acceptChildrenVoid(AnnotationCollector())
|
||||
/**
|
||||
* TODO: for all test classes generate registration...
|
||||
*/
|
||||
}
|
||||
|
||||
private fun FunctionDescriptor.isTestFunction() =
|
||||
annotations.any { annotation ->
|
||||
hasTestAnnotation(annotation)
|
||||
}
|
||||
|
||||
|
||||
private fun hasTestAnnotation(annotation: AnnotationDescriptor): Boolean {
|
||||
return annotation.fqName == fqNameTestAnnotation ||
|
||||
annotation.fqName == fqNameBeforeAnnotation ||
|
||||
annotation.fqName == fqNameAfterAnnotation ||
|
||||
annotation.fqName == fqNameBeforeClassAnnotation ||
|
||||
annotation.fqName == fqNameAfterClassAnnotation
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
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 }
|
||||
|
||||
}
|
||||
@@ -1950,6 +1950,11 @@ task deserialized_members(type: LinkKonanTest) {
|
||||
source = "serialization/deserialize_members.kt"
|
||||
}
|
||||
|
||||
task testing_smoke(type: RunKonanTest) {
|
||||
source = "testing/smoke.kt"
|
||||
flags = [] // TODO: Add flag to generate test-runner
|
||||
}
|
||||
|
||||
// Just check that the driver is able to produce runnable binaries.
|
||||
task driver0(type: RunDriverKonanTest) {
|
||||
goldValue = "Hello, world!\n"
|
||||
@@ -2121,3 +2126,4 @@ if (isMac()) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import org.junit.*
|
||||
|
||||
|
||||
@Test
|
||||
fun simple() {}
|
||||
|
||||
class SimpleTestA{
|
||||
companion object {
|
||||
@BeforeClass
|
||||
fun beforeClass() {
|
||||
println("before class SimpleTestA")
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
fun afterClass() {
|
||||
println("after class SimpleTestA")
|
||||
}
|
||||
}
|
||||
@Before
|
||||
fun before() {
|
||||
println("SimpleTestA::before")
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
println("SimpleTestA::after")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun a() {
|
||||
println("SimpleTestA::a")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun b() {
|
||||
println("SimpleTestA::b")
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleTestB{
|
||||
companion object {
|
||||
@BeforeClass
|
||||
fun beforeClass() {
|
||||
println("before class SimpleTestB")
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
fun afterClass() {
|
||||
println("after class SimpleTestB")
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
println("SimpleTestB::before")
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
println("SimpleTestB::after")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun a() {
|
||||
println("SimpleTestB::a")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun b() {
|
||||
println("SimpleTestB::b")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ---- This should be generated...
|
||||
private val test = {
|
||||
val objA = SimpleTestA()
|
||||
|
||||
val objB = SimpleTestB()
|
||||
|
||||
TestRunner.register(listOf(
|
||||
TestSuite(
|
||||
beforeClass = { SimpleTestA.beforeClass() },
|
||||
afterClass = { SimpleTestA.afterClass() },
|
||||
before = { objA.before() },
|
||||
after = { objA.after() },
|
||||
funcs = arrayOf({ objA.a() }, { objA.b() })),
|
||||
TestSuite(
|
||||
beforeClass = { SimpleTestB.beforeClass() },
|
||||
afterClass = { SimpleTestB.afterClass() },
|
||||
before = { objB.before() },
|
||||
after = { objB.after() },
|
||||
funcs = arrayOf({ objB.a() }, { objB.b() }))
|
||||
|
||||
))
|
||||
}()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import kotlin.test.Test
|
||||
|
||||
class A {
|
||||
@Test
|
||||
fun test() {
|
||||
println("A.test")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
println("test")
|
||||
}
|
||||
|
||||
@@ -258,6 +258,10 @@ targetList.each { target ->
|
||||
rename("${target}Start.bc", 'start.bc')
|
||||
into("klib/stdlib/targets/$target/native")
|
||||
}
|
||||
from(project(':runtime').file("build/${target}Test.bc")) {
|
||||
rename("${target}Test.bc", 'test.bc')
|
||||
into("klib/stdlib/targets/$target/native")
|
||||
}
|
||||
if (target == 'wasm32') {
|
||||
from(project(':runtime').file('src/launcher/js')) {
|
||||
into('klib/stdlib/targets/wasm32/included')
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package konan.test
|
||||
|
||||
interface TestListener {
|
||||
fun pass(testCase: TestCase)
|
||||
fun fail(testCase: TestCase, e: AssertionError)
|
||||
fun error(testCase: TestCase, e: Throwable)
|
||||
fun ignore(testCase: TestCase)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package konan.test
|
||||
|
||||
class TestRunner(suites: Collection<TestSuite> = emptyList()) {
|
||||
|
||||
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")
|
||||
|
||||
}
|
||||
|
||||
private val _suites = mutableListOf<TestSuite>().apply { addAll(suites) }
|
||||
val suites: Collection<TestSuite> get() = _suites
|
||||
|
||||
fun register(suite: TestSuite) = _suites.add(suite)
|
||||
fun register(suites: Iterable<TestSuite>) = _suites.addAll(suites)
|
||||
fun register(vararg suites: TestSuite) = _suites.addAll(suites)
|
||||
|
||||
fun run() {
|
||||
suites.forEach {
|
||||
it.run(SimpleTestListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package konan.test
|
||||
|
||||
interface TestCase {
|
||||
val name: String
|
||||
}
|
||||
|
||||
interface TestSuite {
|
||||
val name: String
|
||||
val testCases: Map<String, TestCase>
|
||||
fun run(listener: TestListener)
|
||||
}
|
||||
|
||||
enum class FunctionKind {
|
||||
BEFORE,
|
||||
AFTER,
|
||||
BEFORE_CLASS,
|
||||
AFTER_CLASS
|
||||
}
|
||||
|
||||
abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String): TestSuite {
|
||||
override fun toString(): String = name
|
||||
|
||||
inner class BasicTestCase(override val name: String, val testFunction: F): TestCase {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
private val _testCases = mutableMapOf<String, BasicTestCase>()
|
||||
override val testCases: Map<String, BasicTestCase>
|
||||
get() = _testCases
|
||||
|
||||
private val specialFunctions = mutableMapOf<FunctionKind, MutableSet<F>>()
|
||||
private fun Map<FunctionKind, Set<F>>.getFunctions(type: FunctionKind) =
|
||||
specialFunctions.getOrPut(type) { mutableSetOf() }
|
||||
|
||||
val before: Collection<F> get() = specialFunctions.getFunctions(FunctionKind.BEFORE)
|
||||
val after: Collection<F> get() = specialFunctions.getFunctions(FunctionKind.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)
|
||||
|
||||
protected fun registerTestCase(testCase: BasicTestCase) = _testCases.put(testCase.name, testCase)
|
||||
protected fun registerTestCases(testCases: Collection<BasicTestCase>) = testCases.forEach { registerTestCase(it) }
|
||||
protected fun registerTestCases(vararg testCases: BasicTestCase) = registerTestCases(testCases.toList())
|
||||
|
||||
protected fun registerFunction(type: FunctionKind, function: F) =
|
||||
specialFunctions.getFunctions(type).add(function)
|
||||
|
||||
protected abstract fun doBeforeClass()
|
||||
protected abstract fun doAfterClass()
|
||||
|
||||
protected abstract fun doTest(testCase: BasicTestCase)
|
||||
|
||||
override fun run(listener: TestListener) {
|
||||
doBeforeClass()
|
||||
testCases.values.forEach {
|
||||
try {
|
||||
doTest(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) {
|
||||
|
||||
abstract fun createInstance(): T
|
||||
|
||||
// TODO: What about companions?
|
||||
override fun doBeforeClass() {} // = beforeClass.forEach { createInstance().it() }
|
||||
override fun doAfterClass() {} // = afterClass.forEach { createInstance().it() }
|
||||
|
||||
override fun doTest(testCase: BasicTestCase) {
|
||||
val instance = createInstance()
|
||||
val testFunction = testCase.testFunction
|
||||
before.forEach { instance.it() }
|
||||
instance.testFunction()
|
||||
after.forEach { instance.it() }
|
||||
}
|
||||
}
|
||||
|
||||
class TopLevelTestSuite(name: String): AbstractTestSuite<() -> Unit>(name) {
|
||||
override fun doBeforeClass() = beforeClass.forEach { it() }
|
||||
override fun doAfterClass() = afterClass.forEach { it() }
|
||||
|
||||
override fun doTest(testCase: BasicTestCase) {
|
||||
before.forEach { it() }
|
||||
testCase.testFunction()
|
||||
after.forEach { it() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package kotlin.test
|
||||
|
||||
// TODO: Move test code in a separate library.
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Test
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class BeforeClass
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class AfterClass
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Before
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class After
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
annotation class Ignore
|
||||
@@ -28,3 +28,4 @@ include ':backend.native:tests'
|
||||
include ':shared'
|
||||
include ':tools:kotlin-native-gradle-plugin'
|
||||
include ':utilities'
|
||||
include ':ext:junit'
|
||||
Reference in New Issue
Block a user