Rework test runner to avoid mutable objects. (#1702)

This commit is contained in:
Nikolay Igotti
2018-06-19 14:31:31 +03:00
committed by GitHub
parent 67279c4148
commit 7074976f7f
6 changed files with 75 additions and 85 deletions
@@ -75,7 +75,7 @@ internal class FunctionInlining(val context: Context): IrElementTransformerVoidW
val functionDescriptor = irCall.descriptor
if (!functionDescriptor.needsInlining) return irCall // This call does not need inlining.
val functionDeclaration = getFunctionDeclaration(functionDescriptor) // Get declaration of the function to be inlined.
val functionDeclaration = getFunctionDeclaration(functionDescriptor) // Get declaration of the function to be inlined.
if (functionDeclaration == null) { // We failed to get the declaration.
val message = "Inliner failed to obtain function declaration: " +
functionDescriptor.fqNameSafe.toString()
@@ -83,9 +83,9 @@ internal class FunctionInlining(val context: Context): IrElementTransformerVoidW
return irCall
}
functionDeclaration.transformChildrenVoid(this) // Process recursive inline.
functionDeclaration.transformChildrenVoid(this) // Process recursive inline.
val inliner = Inliner(globalSubstituteMap, functionDeclaration, currentScope!!, context) // Create inliner for this scope.
return inliner.inline(irCall ) // Return newly created IrInlineBody instead of IrCall.
return inliner.inline(irCall) // Return newly created IrInlineBody instead of IrCall.
}
//-------------------------------------------------------------------------//
@@ -134,7 +134,7 @@ private class Inliner(val globalSubstituteMap: MutableMap<DeclarationDescriptor,
private fun inlineFunction(callee: IrCall, // Call to be substituted.
caller: IrFunction): IrReturnableBlockImpl { // Function to substitute.
val copyFunctionDeclaration = copyIrElement.copy( // Create copy of original function.
val copyFunctionDeclaration = copyIrElement.copy( // Create copy of original function.
irElement = caller, // Descriptors declared inside the function will be copied.
typeSubstitutor = createTypeSubstitutor(callee) // Type parameters will be substituted with type arguments.
) as IrFunction
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl
@@ -405,9 +406,10 @@ internal class TestProcessor (val context: KonanBackendContext) {
endOffset = UNDEFINED_OFFSET,
symbol = symbols.symbolTable.referenceConstructor(superConstructor),
descriptor = superConstructor,
typeArguments = mapOf(superConstructor.typeParameters[0] to testClassType,
superConstructor.typeParameters[1] to testCompanionType)
typeArgumentsCount = 2
).apply {
copyTypeArgumentsFrom(mapOf(superConstructor.typeParameters[0] to testClassType,
superConstructor.typeParameters[1] to testCompanionType))
putValueArgument(0, IrConstImpl.string(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
+1 -1
View File
@@ -10,5 +10,5 @@ fun test() {
fun main(args: Array<String>) {
println("Custom main")
TestRunner.run(args)
testLauncherEntryPoint(args)
}
+12 -3
View File
@@ -18,7 +18,16 @@ package konan.test
import kotlin.system.exitProcess
fun main(args:Array<String>) {
val exitCode = TestRunner.run(args)
exitProcess(exitCode)
private val _generatedSuites = mutableListOf<TestSuite>()
internal fun registerSuite(suite: TestSuite): Unit {
_generatedSuites.add(suite)
}
fun testLauncherEntryPoint(args: Array<String>): Int {
return TestRunner(_generatedSuites, args).run()
}
fun main(args: Array<String>) {
exitProcess(testLauncherEntryPoint(args))
}
@@ -21,22 +21,55 @@ import kotlin.system.getTimeMillis
import kotlin.system.measureTimeMillis
import kotlin.text.StringBuilder
object TestRunner {
private val suites_ = mutableListOf<TestSuite>()
val suites: Collection<TestSuite>
get() = suites_
var logger: TestLogger = GTestLogger()
class TestRunner(val suites: List<TestSuite>, args: Array<String>) {
private val filters = mutableListOf<(TestCase) -> Boolean>()
private val listeners = mutableSetOf<TestListener>()
private var logger: TestLogger = GTestLogger()
private var runTests = true
var iterations = 1
val listeners = mutableSetOf<TestListener>()
val filters = mutableListOf<(TestCase) -> Boolean>()
private set
var exitCode = 0
private set
class FilteredSuite(val innerSuite: TestSuite): TestSuite by innerSuite {
init {
args.filter {
it.startsWith("--gtest_") || it.startsWith("--ktest_") || it == "--help" || it == "-h"
}.forEach {
val arg = it.split('=')
when (arg.size) {
1 -> when (arg[0]) {
"--gtest_list_tests",
"--ktest_list_tests" -> {
logger.logTestList(this, filterSuites()); runTests = false
}
"-h",
"--help" -> {
logger.log(help); runTests = false
}
else -> throw IllegalArgumentException("Unknown option: $it\n$help")
}
2 -> {
val key = arg[0]
val value = arg[1]
when (key) {
"--ktest_logger" -> setLoggerFromArg(value)
"--gtest_filter",
"--ktest_filter" -> setGTestFilterFromArg(value)
"--ktest_regex_filter" -> setRegexFilterFromArg(value, true)
"--ktest_negative_regex_filter" -> setRegexFilterFromArg(value, false)
"--ktest_repeat",
"--gtest_repeat" -> iterations = value.toIntOrNull() ?: throw IllegalArgumentException("Cannot parse number: $value")
else -> if (key.startsWith("--ktest_")) {
throw IllegalArgumentException("Unknown option: $it\n$help")
}
}
}
else -> throw IllegalArgumentException("Unknown option: $it\n$help")
}
}
}
inner class FilteredSuite(val innerSuite: TestSuite) : TestSuite by innerSuite {
private val TestCase.matchFilters: Boolean
get() = filters.map { it(this) }.all { it }
@@ -50,10 +83,6 @@ object TestRunner {
private fun filterSuites(): Collection<TestSuite> = suites.map { FilteredSuite(it) }
fun register(suite: TestSuite) = suites_.add(suite)
fun register(suites: Iterable<TestSuite>) = suites_.addAll(suites)
fun register(vararg suites: TestSuite) = suites_.addAll(suites)
// TODO: Support short aliases.
// TODO: Support several test iterations.
/**
@@ -82,54 +111,9 @@ object TestRunner {
*
* --ktest_logger=GTEST|TEAMCITY|SIMPLE|SILENT - Use the specified output format. The default one is GTEST.
*/
fun useArgs(args: Array<String>): Boolean {
try {
return parseArgs(args)
} catch (e: IllegalArgumentException) {
logger.log("Error: ${e.message}")
return false
}
}
private fun parseArgs(args: Array<String>): Boolean {
var result = true
args.filter {
it.startsWith("--gtest_") || it.startsWith("--ktest_") || it == "--help" || it == "-h"
}.forEach {
val arg = it.split('=')
when (arg.size) {
1 -> when (arg[0]) {
"--gtest_list_tests",
"--ktest_list_tests" -> { logger.logTestList(this, filterSuites()); result = false }
"-h",
"--help" -> { logger.log(help); result = false }
else -> throw IllegalArgumentException("Unknown option: $it\n$help")
}
2 -> {
val key = arg[0]
val value = arg[1]
when (key) {
"--ktest_logger" -> setLoggerFromArg(value)
"--gtest_filter",
"--ktest_filter" -> setGTestFilterFromArg(value)
"--ktest_regex_filter" -> setRegexFilterFromArg(value, true)
"--ktest_negative_regex_filter" -> setRegexFilterFromArg(value, false)
"--ktest_repeat",
"--gtest_repeat" -> iterations = value.toIntOrNull() ?:
throw IllegalArgumentException("Cannot parse number: $value")
else -> if (key.startsWith("--ktest_")) {
throw IllegalArgumentException("Unknown option: $it\n$help")
}
}
}
else -> throw IllegalArgumentException("Unknown option: $it\n$help")
}
}
return result
}
private fun String.substringEscaped(range: IntRange) =
this.substring(range).let { if(it.isNotEmpty()) Regex.escape(it) else "" }
this.substring(range).let { if (it.isNotEmpty()) Regex.escape(it) else "" }
private fun String.toGTestPatterns() = splitToSequence(':').map { pattern ->
val result = StringBuilder()
@@ -137,8 +121,8 @@ object TestRunner {
pattern.forEachIndexed { index, c ->
if (c == '*' || c == '?') {
result.append(pattern.substringEscaped(prevIndex until index))
prevIndex = index+1
result.append( if (c == '*') ".*" else ".")
prevIndex = index + 1
result.append(if (c == '*') ".*" else ".")
}
}
result.append(pattern.substringEscaped(prevIndex until pattern.length))
@@ -159,7 +143,7 @@ object TestRunner {
this.filters.add { testCase ->
positivePatterns.any { testCase.prettyName.matches(it) } &&
negativePatterns.none { testCase.prettyName.matches(it) }
negativePatterns.none { testCase.prettyName.matches(it) }
}
}
@@ -174,7 +158,7 @@ object TestRunner {
}
private fun setLoggerFromArg(logger: String) {
when(logger.toUpperCase()) {
when (logger.toUpperCase()) {
"GTEST" -> this.logger = GTestLogger()
"TEAMCITY" -> this.logger = TeamCityLogger()
"SIMPLE" -> this.logger = SimpleTestLogger()
@@ -249,15 +233,9 @@ object TestRunner {
sendToListeners { finishIteration(this@TestRunner, iteration, iterationTime) }
}
fun run(args: Array<String>): Int {
return if (useArgs(args)) {
run()
} else {
0
}
}
fun run(): Int {
if (!runTests)
return 0
sendToListeners { startTesting(this@TestRunner) }
val totalTime = measureTimeMillis {
var i = 1
@@ -268,5 +246,6 @@ object TestRunner {
}
sendToListeners { finishTesting(this@TestRunner, totalTime) }
return exitCode
}
}
@@ -73,7 +73,7 @@ abstract class AbstractTestSuite<F: Function<Unit>>(override val name: String, o
abstract fun createTestCase(name: String, testFunction: F, ignored: Boolean): BasicTestCase<F>
init {
TestRunner.register(this)
registerSuite(this)
}
override val size: Int