[Native][tests] Support parsing dumped test listing

^KT-50316
This commit is contained in:
Dmitriy Dolovov
2022-02-11 16:56:04 +03:00
parent 5a26f58b24
commit 5500e3a332
2 changed files with 84 additions and 23 deletions
@@ -31,29 +31,48 @@ internal class PackageName private constructor(private val fqn: String, val segm
* [packagePartClassName] - package-part class name (if there is any)
* [functionName] - name of test function
*/
internal data class TestName(private val fqn: String) {
internal class TestName {
private val fqn: String
val packageName: PackageName
val packagePartClassName: String?
val functionName: String
init {
constructor(purePackageSegments: List<String>, classNames: List<String>, functionName: String) {
this.functionName = functionName
val segments = purePackageSegments.toMutableList()
if (classNames.lastOrNull().isPackagePartClassName()) {
packagePartClassName = classNames.last()
segments += classNames.dropLast(1)
} else {
packagePartClassName = null
segments += classNames
}
packageName = PackageName(segments)
fqn = buildString {
if (!packageName.isEmpty()) append(packageName).append('.')
if (packagePartClassName != null) append(packagePartClassName).append('.')
append(functionName)
}
}
constructor(fqn: String) {
this.fqn = fqn
val segments = fqn.split('.').toMutableList()
functionName = segments.removeLast()
val maybePackagePartClassSegment = segments.lastOrNull()
packagePartClassName = if (maybePackagePartClassSegment?.firstOrNull()?.isEffectivelyUpperCase() == true
&& maybePackagePartClassSegment.endsWith("Kt")
) {
segments.removeLast()
} else
null
packagePartClassName = if (segments.lastOrNull().isPackagePartClassName()) segments.removeLast() else null
packageName = PackageName(segments)
}
override fun toString() = fqn
override fun equals(other: Any?) = fqn == (other as? TestName)?.fqn
override fun hashCode() = fqn.hashCode()
companion object {
private fun Char.isEffectivelyUpperCase() = if (isUpperCase()) true else !isLowerCase()
private fun String?.isPackagePartClassName() = this != null && firstOrNull()?.isEffectivelyUpperCase() == true && endsWith("Kt")
}
}
@@ -10,6 +10,48 @@ import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
/**
* Extracts [TestName]s from the test listing produced immediately during the compilation (turned on with
* "-Xdump-tests-to=" compiler flag).
*
* Example:
* sample/test/SampleTestKt:one
* sample/test/SampleTestKt:two
*
* yields TestName(packageName = "sample.test", packagePartClassName = "SampleTestKt", functionName = "one")
* and TestName(packageName = "sample.test", packagePartClassName = "SampleTestKt", functionName = "two")
*/
internal object DumpedTestListing {
fun parse(listing: String): Collection<TestName> {
val lines = listing.lines()
var emptyLineEncountered = false
return lines.mapIndexedNotNull { index: Int, line: String ->
fun parseError(message: String): Nothing = parseError(message, index, line, listing)
when {
line.isBlank() -> {
emptyLineEncountered = true
null
}
emptyLineEncountered -> parseError("Unexpected empty line")
else -> {
val (packageAndClass, functionName) = line.trim()
.split(':')
.takeIf { items -> items.size == 2 && items.none(String::isBlank) }
?: parseError("Malformed test name")
with(packageAndClass.split('/')) {
val classNames = last().split('.')
val packageSegments = dropLast(1)
TestName(packageSegments, classNames, functionName)
}
}
}
}
}
}
/**
* Extracts [TestName]s from GTest listing.
*
@@ -23,20 +65,11 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
*/
internal object GTestListing {
fun parse(listing: String): Collection<TestName> = buildList {
fun parseError(message: String, index: Int, line: String): Nothing = fail {
buildString {
appendLine("$message at line #$index: \"$line\"")
appendLine()
appendLine("Full listing:")
appendLine(listing)
}
}
var state: ParseState = ParseState.Begin
val lines = listing.lines()
lines.forEachIndexed { index, line ->
fun parseError(message: String): Nothing = parseError(message, index, line)
fun parseError(message: String): Nothing = parseError(message, index, line, listing)
state = when {
line.startsWith(STDLIB_TESTS_IGNORED_LINE_PREFIX) && state is ParseState.Begin -> ParseState.Begin
@@ -63,7 +96,7 @@ internal object GTestListing {
}
if (state is ParseState.NewTestSuite)
parseError("Test name expected before test suite name", lines.lastIndex, lines.last())
parseError("Test name expected before test suite name", lines.lastIndex, lines.last(), listing)
}
private sealed interface ParseState {
@@ -79,4 +112,13 @@ internal object GTestListing {
// The very first line of stdlib test output may contain seed of Random. Such line should be ignored.
private const val STDLIB_TESTS_IGNORED_LINE_PREFIX = "Seed: "
}
}
private fun parseError(message: String, index: Int, line: String, listing: String): Nothing = fail {
buildString {
appendLine("$message at line #$index: \"$line\"")
appendLine()
appendLine("Full listing:")
appendLine(listing)
}
}