From 5500e3a3325cc8fcdd498cc4da0cd373b247a77c Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Fri, 11 Feb 2022 16:56:04 +0300 Subject: [PATCH] [Native][tests] Support parsing dumped test listing ^KT-50316 --- .../blackboxtest/support/NamedEntities.kt | 41 ++++++++---- .../blackboxtest/support/util/TestListing.kt | 66 +++++++++++++++---- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NamedEntities.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NamedEntities.kt index 7917ddc586e..5b284021613 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NamedEntities.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NamedEntities.kt @@ -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, classNames: List, 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") } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestListing.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestListing.kt index 2ad39bb5585..74665dd51fc 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestListing.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestListing.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 { + 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 = 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: " -} \ No newline at end of file +} + +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) + } +}