[Native][tests] Support parsing dumped test listing
^KT-50316
This commit is contained in:
+30
-11
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
+54
-12
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user