Skip to content

Commit

Permalink
unit-tests: Create a simple test runner implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
vvlevchenko authored and ilmat192 committed Sep 25, 2017
1 parent 77195e8 commit f26e5b5
Show file tree
Hide file tree
Showing 18 changed files with 550 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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 }

}
Original file line number Diff line number Diff line change
@@ -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 }

}
6 changes: 6 additions & 0 deletions backend.native/tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -2121,3 +2126,4 @@ if (isMac()) {
}
}
}

Loading

0 comments on commit f26e5b5

Please sign in to comment.