From cdbbc701168948140d59ca559c7f96784417f67c Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Fri, 3 Dec 2021 12:19:20 +0300 Subject: [PATCH] [Native][tests] Drop TestFunction/FunctionName, introduce TestName instead --- .../blackboxtest/support/NamedEntities.kt | 35 +++++++++++++++ .../konan/blackboxtest/support/TestCase.kt | 7 --- .../blackboxtest/support/TestExecutable.kt | 34 ++++----------- .../blackboxtest/support/TestRunProvider.kt | 38 ++++++++-------- ...Extractor.kt => LocalTestNameExtractor.kt} | 12 +++--- .../support/runner/LocalTestRunner.kt | 1 - .../blackboxtest/support/util/TestListing.kt | 43 +++++++++---------- .../blackboxtest/support/util/TestOutput.kt | 30 ++++++------- 8 files changed, 103 insertions(+), 97 deletions(-) rename native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/{LocalTestFunctionExtractor.kt => LocalTestNameExtractor.kt} (78%) 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 453d6e294dd..7917ddc586e 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 @@ -22,3 +22,38 @@ internal class PackageName private constructor(private val fqn: String, val segm val EMPTY = PackageName("", emptyList()) } } + +/** + * Represents a single test name (i.e. a function annotated with [kotlin.test.Test]) inside of a [TestFile]. + * + * [packageName] - containing package name. For the sake of simplification [packageName] may also include top-level and nested class names, + * but it does not include package-part class name (i.e. "SomethingKt"). + * [packagePartClassName] - package-part class name (if there is any) + * [functionName] - name of test function + */ +internal data class TestName(private val fqn: String) { + val packageName: PackageName + val packagePartClassName: String? + val functionName: String + + init { + 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 + + packageName = PackageName(segments) + } + + override fun toString() = fqn + + companion object { + private fun Char.isEffectivelyUpperCase() = if (isUpperCase()) true else !isLowerCase() + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt index 7093d551359..92dd3ced937 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCase.kt @@ -15,13 +15,6 @@ import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail import java.io.File -internal typealias FunctionName = String - -/** - * Represents a single test function (i.e. a function annotated with [kotlin.test.Test]) inside of a [TestFile]. - */ -internal data class TestFunction(val packageName: PackageName, val functionName: FunctionName) - /** * Represents a single file that will be supplied to the compiler. */ diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt index 0d8d87a035b..0c074780333 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.konan.blackboxtest.support -import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestName +import org.jetbrains.kotlin.konan.blackboxtest.support.util.startsWith import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertFalse import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.io.File @@ -26,45 +26,27 @@ internal sealed interface TestRunParameter { fun applyTo(programArgs: MutableList) sealed class WithFilter : TestRunParameter { - abstract fun testMatches(testName: String): Boolean + abstract fun testMatches(testName: TestName): Boolean } - class WithPackageFilter(packageName: PackageName) : WithFilter() { + class WithPackageFilter(private val packageName: PackageName) : WithFilter() { init { assertFalse(packageName.isEmpty()) } - private val packagePrefix = "$packageName." - override fun applyTo(programArgs: MutableList) { - programArgs += "--ktest_filter=$packagePrefix*" + programArgs += "--ktest_filter=$packageName.*" } - override fun testMatches(testName: TestName) = testName.startsWith(packagePrefix) + override fun testMatches(testName: TestName) = testName.packageName.startsWith(packageName) } - class WithFunctionFilter(val testFunction: TestFunction) : WithFilter() { - private val packagePrefix = if (testFunction.packageName.isEmpty()) "" else "${testFunction.packageName}." - + class WithTestFilter(val testName: TestName) : WithFilter() { override fun applyTo(programArgs: MutableList) { - programArgs += "--ktest_regex_filter=${packagePrefix.replace(".", "\\.")}([^\\.]+)\\.${testFunction.functionName}" + programArgs += "--ktest_filter=$testName" } - override fun testMatches(testName: String): Boolean { - val remainder = if (packagePrefix.isNotEmpty()) { - if (!testName.startsWith(packagePrefix)) return false - testName.substringAfter(packagePrefix) - } else - testName - - val suffix = remainder - .split('.') - .takeIf { it.size == 2 } - ?.last() - ?: return false - - return suffix == testFunction.functionName - } + override fun testMatches(testName: TestName) = this.testName == testName } object WithTCTestLogger : TestRunParameter { diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt index 60707f59eb3..ec258b7fee6 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt @@ -12,7 +12,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtr import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilationResult.Companion.assertSuccess import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner -import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestFunctionExtractor +import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestNameExtractor import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestRunner import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GlobalSettings import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings @@ -32,7 +32,7 @@ internal class TestRunProvider( ) : ExtensionContext.Store.CloseableResource { private val compilationFactory = TestCompilationFactory(settings) private val cachedCompilations = ThreadSafeCache() - private val cachedTestFunctions = ThreadSafeCache>() + private val cachedTestNames = ThreadSafeCache>() fun setProcessors(testDataFile: File, sourceTransformers: List<(String) -> String>) { testCaseGroupProvider.setPreprocessors(testDataFile, sourceTransformers) @@ -64,7 +64,7 @@ internal class TestRunProvider( * } */ fun getSingleTestRun(testCaseId: TestCaseId): TestRun = withTestExecutable(testCaseId) { testCase, executable, _ -> - val runParameters = getRunParameters(testCase, testFunction = null) + val runParameters = getRunParameters(testCase, testName = null) TestRun(displayName = /* Unimportant. Used only in dynamic tests. */ "", executable, runParameters, testCase.id) } @@ -101,24 +101,24 @@ internal class TestRunProvider( fun getTestRuns( testCaseId: TestCaseId ): Collection> = withTestExecutable(testCaseId) { testCase, executable, cacheKey -> - fun createTestRun(testRunName: String, testFunction: TestFunction?): TestRun { - val runParameters = getRunParameters(testCase, testFunction) + fun createTestRun(testRunName: String, testName: TestName?): TestRun { + val runParameters = getRunParameters(testCase, testName) return TestRun(testRunName, executable, runParameters, testCase.id) } when (testCase.kind) { TestKind.STANDALONE_NO_TR -> { val testRunName = testCase.extras().entryPoint.substringAfterLast('.') - val testRun = createTestRun(testRunName, testFunction = null) + val testRun = createTestRun(testRunName, testName = null) TreeNode.oneLevel(testRun) } TestKind.REGULAR, TestKind.STANDALONE -> { - val testFunctions = cachedTestFunctions.computeIfAbsent(cacheKey) { - extractTestFunctions(executable) + val testNames = cachedTestNames.computeIfAbsent(cacheKey) { + extractTestNames(executable) }.filterIrrelevant(testCase) - testFunctions.buildTree(TestFunction::packageName) { testFunction -> - createTestRun(testFunction.functionName, testFunction) + testNames.buildTree(TestName::packageName) { testName -> + createTestRun(testName.functionName, testName) } } } @@ -159,10 +159,10 @@ internal class TestRunProvider( return action(testCase, executable, cacheKey) } - private fun getRunParameters(testCase: TestCase, testFunction: TestFunction?): List = with(testCase) { + private fun getRunParameters(testCase: TestCase, testName: TestName?): List = with(testCase) { when (kind) { TestKind.STANDALONE_NO_TR -> { - assertTrue(testFunction == null) + assertTrue(testName == null) listOfNotNull( extras().inputDataFile?.let(TestRunParameter::WithInputData), expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData) @@ -170,12 +170,12 @@ internal class TestRunProvider( } TestKind.STANDALONE -> listOfNotNull( TestRunParameter.WithTCTestLogger, - testFunction?.let(TestRunParameter::WithFunctionFilter), + testName?.let(TestRunParameter::WithTestFilter), expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData) ) TestKind.REGULAR -> listOfNotNull( TestRunParameter.WithTCTestLogger, - testFunction?.let(TestRunParameter::WithFunctionFilter) ?: TestRunParameter.WithPackageFilter(nominalPackageName), + testName?.let(TestRunParameter::WithTestFilter) ?: TestRunParameter.WithPackageFilter(nominalPackageName), expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData) ) } @@ -189,17 +189,17 @@ internal class TestRunProvider( runningAtNonHostTarget() } - // Currently, only local test function extractor is supported. - private fun extractTestFunctions(executable: TestExecutable): Collection = with(settings.get()) { + // Currently, only local test name extractor is supported. + private fun extractTestNames(executable: TestExecutable): Collection = with(settings.get()) { if (target == hostTarget) - LocalTestFunctionExtractor(executable, executionTimeout).run() + LocalTestNameExtractor(executable, executionTimeout).run() else runningAtNonHostTarget() } - private fun Collection.filterIrrelevant(testCase: TestCase) = + private fun Collection.filterIrrelevant(testCase: TestCase) = if (testCase.kind == TestKind.REGULAR) - filter { testFunction -> testFunction.packageName.startsWith(testCase.nominalPackageName) } + filter { testName -> testName.packageName.startsWith(testCase.nominalPackageName) } else this diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestFunctionExtractor.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt similarity index 78% rename from native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestFunctionExtractor.kt rename to native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt index 508d6c4c277..c9b3785c018 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestFunctionExtractor.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt @@ -7,17 +7,17 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner import org.jetbrains.kotlin.konan.blackboxtest.support.LoggedData import org.jetbrains.kotlin.konan.blackboxtest.support.TestExecutable -import org.jetbrains.kotlin.konan.blackboxtest.support.TestFunction +import org.jetbrains.kotlin.konan.blackboxtest.support.TestName import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter import org.jetbrains.kotlin.konan.blackboxtest.support.util.parseGTestListing import org.jetbrains.kotlin.test.services.JUnit5Assertions import kotlin.time.Duration -internal class LocalTestFunctionExtractor( +internal class LocalTestNameExtractor( override val executable: TestExecutable, executionTimeout: Duration -) : AbstractLocalProcessRunner>(executionTimeout) { - override val visibleProcessName get() = "Test function extractor" +) : AbstractLocalProcessRunner>(executionTimeout) { + override val visibleProcessName get() = "Test name extractor" override val programArgs = listOf(executable.executableFile.path, "--ktest_list_tests") override val outputFilter get() = TestOutputFilter.NO_FILTERING @@ -32,12 +32,12 @@ internal class LocalTestFunctionExtractor( override fun handleUnexpectedFailure(t: Throwable) = JUnit5Assertions.fail { LoggedData.TestRunUnexpectedFailure(getLoggedParameters(), t) - .withErrorMessage("Test function extraction failed with unexpected exception.") + .withErrorMessage("Test name extraction failed with unexpected exception.") } inner class ResultHandler( runResult: RunResult.Completed - ) : AbstractLocalProcessRunner>.ResultHandler(runResult) { + ) : AbstractLocalProcessRunner>.ResultHandler(runResult) { override fun getLoggedRun() = LoggedData.TestRun(getLoggedParameters(), runResult) override fun doHandle() = parseGTestListing(runResult.processOutput.stdOut.filteredOutput) } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestRunner.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestRunner.kt index ba04fc0ffb6..5400ddd815e 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestRunner.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestRunner.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.runner import com.intellij.openapi.util.text.StringUtilRt.convertLineSeparators import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.TCTestOutputFilter -import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestName import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestReport import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail 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 92631fe2ecd..5eb7883b0c7 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 @@ -5,31 +5,33 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.util -import org.jetbrains.kotlin.konan.blackboxtest.support.PackageName -import org.jetbrains.kotlin.konan.blackboxtest.support.TestFunction +import org.jetbrains.kotlin.konan.blackboxtest.support.TestName import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.konan.blackboxtest.support.util.GTestListingParseState as State /** - * Extracts [TestFunction]s from GTest listing. + * Extracts [TestName]s from GTest listing. * * Example: * sample.test.SampleTestKt. * one * two * - * yields TestFunction(sample.test, one) and TestFunction(sample.test, two). + * yields TestName(packageName = "sample.test", packagePartClassName = "SampleTestKt", functionName = "one") + * and TestName(packageName = "sample.test", packagePartClassName = "SampleTestKt", functionName = "two") */ -internal fun parseGTestListing(rawGTestListing: String): Collection = buildList { +internal fun parseGTestListing(listing: String): Collection = buildList { var state: State = State.Begin - rawGTestListing.lineSequence().forEachIndexed { index, line -> + listing.lineSequence().forEachIndexed { index, line -> fun parseError(message: String): Nothing = fail { buildString { appendLine("$message at line #$index: \"$line\"") appendLine() appendLine("Full listing:") - appendLine(rawGTestListing) + appendLine(listing) } } @@ -39,21 +41,18 @@ internal fun parseGTestListing(rawGTestListing: String): Collection State.End else -> parseError("Unexpected empty line") } - line[0].isWhitespace() -> when (val s = state) { - is State.HasPackageName -> { - this += TestFunction(s.packageName, line.trim()) - State.NewTest(s.packageName) + line[0].isWhitespace() -> when (state) { + is State.NewTestSuite, + is State.NewTest -> { + val testSuite = state.testSuite + this += TestName(testSuite.testSuiteNameWithDotSuffix + line.trim()) + State.NewTest(testSuite) } else -> parseError("Test name encountered before test suite name") } else -> when (state) { is State.Begin, is State.NewTest -> { - val packageSegments = line.trimEnd().removeSuffix(".").split('.') - if (packageSegments.isEmpty()) parseError("Malformed test suite name") - - // Drop the last part because it is related to class name (or file-class name). - // TODO: How to handle nested classes? - State.NewTestSuite(PackageName(packageSegments.dropLast(1))) + State.NewTestSuite(line.trimEnd()) } else -> parseError("Unexpected test suite name") } @@ -64,13 +63,13 @@ internal fun parseGTestListing(rawGTestListing: String): Collection() ?: cast().testSuite + // 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: " diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestOutput.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestOutput.kt index 29aa1d7b5d3..85e029569b5 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestOutput.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/TestOutput.kt @@ -6,15 +6,13 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.util import jetbrains.buildServer.messages.serviceMessages.* -import org.jetbrains.kotlin.konan.blackboxtest.support.FunctionName +import org.jetbrains.kotlin.konan.blackboxtest.support.TestName import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.konan.blackboxtest.support.util.TCTestReportParseState as State import java.text.ParseException -internal typealias TestName = String - internal class TestReport( val passedTests: Collection, val failedTests: Collection, @@ -120,25 +118,25 @@ private class TCTestMessageParserCallback : ServiceMessageParserCallback { is TestIgnored -> when (state) { is State.TestSuiteStarted, is State.TestIgnored, - is State.TestFinished -> State.TestIgnored(state.testSuite, message.functionName).also { ignoredTests += it.testName } + is State.TestFinished -> State.TestIgnored(state.testSuite, message.simpleTestName).also { ignoredTests += it.testName } else -> unexpectedMessage() } is TestStarted -> when (state) { is State.TestSuiteStarted, is State.TestIgnored, - is State.TestFinished -> State.TestStarted(state.testSuite, message.testName) + is State.TestFinished -> State.TestStarted(state.testSuite, message.simpleTestName) else -> unexpectedMessage() } is TestFailed -> when (val s = state) { is State.TestStarted -> { nonTestOutput.append(message.stacktrace) - State.TestFailed(s.testSuite, message.testName).also { failedTests += it.testName } + State.TestFailed(s.testSuite, message.simpleTestName).also { failedTests += it.testName } } else -> unexpectedMessage() } is TestFinished -> when (state) { - is State.TestStarted -> State.TestFinished(state.testSuite, message.testName).also { passedTests += it.testName } - is State.TestFailed -> State.TestFinished(state.testSuite, message.testName) + is State.TestStarted -> State.TestFinished(state.testSuite, message.simpleTestName).also { passedTests += it.testName } + is State.TestFailed -> State.TestFinished(state.testSuite, message.simpleTestName) else -> unexpectedMessage() } else -> { @@ -172,17 +170,17 @@ private sealed interface TCTestReportParseState { class TestSuiteStarted(val testSuiteName: String) : State object TestSuiteFinished : State - sealed class TestState(val testSuite: TestSuiteStarted, val functionName: FunctionName) : State { - val testName: TestName get() = "${testSuite.testSuiteName}.$functionName" + sealed class TestState(val testSuite: TestSuiteStarted, val simpleTestName: String) : State { + val testName: TestName get() = TestName("${testSuite.testSuiteName}.$simpleTestName") } - class TestIgnored(testSuite: TestSuiteStarted, functionName: FunctionName) : TestState(testSuite, functionName) - class TestStarted(testSuite: TestSuiteStarted, functionName: FunctionName) : TestState(testSuite, functionName) - class TestFailed(testSuite: TestSuiteStarted, functionName: FunctionName) : TestState(testSuite, functionName) - class TestFinished(testSuite: TestSuiteStarted, functionName: FunctionName) : TestState(testSuite, functionName) + class TestIgnored(testSuite: TestSuiteStarted, simpleTestName: String) : TestState(testSuite, simpleTestName) + class TestStarted(testSuite: TestSuiteStarted, simpleTestName: String) : TestState(testSuite, simpleTestName) + class TestFailed(testSuite: TestSuiteStarted, simpleTestName: String) : TestState(testSuite, simpleTestName) + class TestFinished(testSuite: TestSuiteStarted, simpleTestName: String) : TestState(testSuite, simpleTestName) } -private val State.testSuite: State.TestSuiteStarted +private inline val State.testSuite: State.TestSuiteStarted get() = if (this is State.TestSuiteStarted) this else cast().testSuite -private val BaseTestMessage.functionName: FunctionName get() = testName +private inline val BaseTestMessage.simpleTestName get() = testName