[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)
|
* [packagePartClassName] - package-part class name (if there is any)
|
||||||
* [functionName] - name of test function
|
* [functionName] - name of test function
|
||||||
*/
|
*/
|
||||||
internal data class TestName(private val fqn: String) {
|
internal class TestName {
|
||||||
|
private val fqn: String
|
||||||
|
|
||||||
val packageName: PackageName
|
val packageName: PackageName
|
||||||
val packagePartClassName: String?
|
val packagePartClassName: String?
|
||||||
val functionName: 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()
|
val segments = fqn.split('.').toMutableList()
|
||||||
functionName = segments.removeLast()
|
functionName = segments.removeLast()
|
||||||
|
packagePartClassName = if (segments.lastOrNull().isPackagePartClassName()) segments.removeLast() else null
|
||||||
val maybePackagePartClassSegment = segments.lastOrNull()
|
|
||||||
packagePartClassName = if (maybePackagePartClassSegment?.firstOrNull()?.isEffectivelyUpperCase() == true
|
|
||||||
&& maybePackagePartClassSegment.endsWith("Kt")
|
|
||||||
) {
|
|
||||||
segments.removeLast()
|
|
||||||
} else
|
|
||||||
null
|
|
||||||
|
|
||||||
packageName = PackageName(segments)
|
packageName = PackageName(segments)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = fqn
|
override fun toString() = fqn
|
||||||
|
override fun equals(other: Any?) = fqn == (other as? TestName)?.fqn
|
||||||
|
override fun hashCode() = fqn.hashCode()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun Char.isEffectivelyUpperCase() = if (isUpperCase()) true else !isLowerCase()
|
private fun Char.isEffectivelyUpperCase() = if (isUpperCase()) true else !isLowerCase()
|
||||||
|
private fun String?.isPackagePartClassName() = this != null && firstOrNull()?.isEffectivelyUpperCase() == true && endsWith("Kt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-11
@@ -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.cast
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
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.
|
* Extracts [TestName]s from GTest listing.
|
||||||
*
|
*
|
||||||
@@ -23,20 +65,11 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
|||||||
*/
|
*/
|
||||||
internal object GTestListing {
|
internal object GTestListing {
|
||||||
fun parse(listing: String): Collection<TestName> = buildList {
|
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
|
var state: ParseState = ParseState.Begin
|
||||||
|
|
||||||
val lines = listing.lines()
|
val lines = listing.lines()
|
||||||
lines.forEachIndexed { index, line ->
|
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 {
|
state = when {
|
||||||
line.startsWith(STDLIB_TESTS_IGNORED_LINE_PREFIX) && state is ParseState.Begin -> ParseState.Begin
|
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)
|
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 {
|
private sealed interface ParseState {
|
||||||
@@ -80,3 +113,12 @@ internal object GTestListing {
|
|||||||
// The very first line of stdlib test output may contain seed of Random. Such line should be ignored.
|
// 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 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