[Native][tests] Run stdlib tests using new test infrastructure

This commit is contained in:
Dmitriy Dolovov
2021-11-24 18:01:51 +03:00
parent 179d324444
commit 4a4760dc4b
7 changed files with 186 additions and 14 deletions
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package org.jetbrains.kotlin.konan.blackboxtest
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseId
import org.jetbrains.kotlin.konan.blackboxtest.support.group.PredefinedTestCase as TC
import org.jetbrains.kotlin.konan.blackboxtest.support.group.PredefinedTestCases
import org.junit.jupiter.api.TestFactory
@PredefinedTestCases(
TC(
name = "nativeStdlib",
freeCompilerArgs = [
"-Xmulti-platform",
"-friend-modules=$KOTLIN_NATIVE_STDLIB_PATH",
"-opt-in=kotlin.RequiresOptIn,kotlin.ExperimentalStdlibApi"
],
sourceLocations = [
"libraries/stdlib/test/**.kt",
"libraries/stdlib/common/test/**.kt",
"kotlin-native/backend.native/tests/stdlib_external/text/**.kt",
"kotlin-native/backend.native/tests/stdlib_external/utils.kt",
"kotlin-native/backend.native/tests/stdlib_external/jsCollectionFactoriesActuals.kt"
]
),
TC(
name = "kotlinTest",
freeCompilerArgs = ["-friend-modules=$KOTLIN_NATIVE_STDLIB_PATH"],
sourceLocations = ["libraries/kotlin.test/common/src/test/kotlin/**.kt"]
)
)
class NativeStdlibBlackBoxTest : AbstractNativeBlackBoxTest() {
@TestFactory
fun kotlinTest() = dynamicTestCase(TestCaseId.Named("kotlinTest"))
@TestFactory
fun nativeStdlib() = dynamicTestCase(TestCaseId.Named("nativeStdlib"))
}
private const val KOTLIN_NATIVE_STDLIB_PATH = "kotlin-native/dist/klib/common/stdlib"
@@ -154,23 +154,17 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
testSettings: TestSettings,
testSettingsAnnotation: Annotation?
): TestCaseGroupProvider {
// First, try to find two-argument constructor.
// Try to find a constructor that accepts the annotation.
if (testSettingsAnnotation != null) {
val testSettingsAnnotationClass = testSettingsAnnotation.annotationClass
testSettings.providerClass.constructors.asSequence()
.forEach { c ->
val (p1, p2) = c.parameters.takeIf { it.size == 2 } ?: return@forEach
@Suppress("Reformat") val provider = when {
p1.hasTypeOf<Settings>() && p2.hasTypeOf(testSettingsAnnotationClass) -> c.call(settings, testSettingsAnnotation)
p1.hasTypeOf(testSettingsAnnotationClass) && p2.hasTypeOf<Settings>() -> c.call(testSettingsAnnotation, settings)
else -> return@forEach
}
return provider.cast()
val p = c.parameters.singleOrNull() ?: return@forEach
if (p.hasTypeOf(testSettingsAnnotationClass)) return c.call(testSettingsAnnotation).cast()
}
}
// Next, try to find a single-argument constructor.
// ... or settings at least.
testSettings.providerClass.constructors.asSequence()
.forEach { c ->
val p = c.parameters.singleOrNull() ?: return@forEach
@@ -157,6 +157,11 @@ internal interface TestCaseId {
override val testCaseGroupId = TestCaseGroupId.TestDataDir(file.parentFile) // The directory, containing testData file.
override fun toString(): String = file.path
}
data class Named(val uniqueName: String) : TestCaseId {
override val testCaseGroupId = TestCaseGroupId.Named(uniqueName) // The single test case inside the test group.
override fun toString() = "[$uniqueName]"
}
}
/**
@@ -247,6 +252,7 @@ internal class TestCase(
*/
internal interface TestCaseGroupId {
data class TestDataDir(val dir: File) : TestCaseGroupId
data class Named(val uniqueName: String) : TestCaseGroupId
}
/**
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.konan.blackboxtest.support
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilerArgs.Companion.EXPLICITLY_FORBIDDEN_COMPILER_ARGS
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.ENTRY_POINT
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.FREE_COMPILER_ARGS
import org.jetbrains.kotlin.konan.blackboxtest.support.TestDirectives.INPUT_DATA_FILE
@@ -90,8 +89,10 @@ internal class TestCompilerArgs(val compilerArgs: List<String>) {
companion object {
val EMPTY = TestCompilerArgs(emptyList())
fun findForbiddenArgs(compilerArgs: Iterable<String>): Set<String> = compilerArgs intersect EXPLICITLY_FORBIDDEN_COMPILER_ARGS
/** The set of compiler args that are not permitted to be explicitly specified using [FREE_COMPILER_ARGS]. */
internal val EXPLICITLY_FORBIDDEN_COMPILER_ARGS = setOf(
private val EXPLICITLY_FORBIDDEN_COMPILER_ARGS = setOf(
"-trn", "-generate-no-exit-test-runner",
"-tr", "-generate-test-runner",
"-trw", "-generate-worker-test-runner",
@@ -170,7 +171,7 @@ internal fun parseFreeCompilerArgs(registeredDirectives: RegisteredDirectives, l
return TestCompilerArgs.EMPTY
val freeCompilerArgs = registeredDirectives[FREE_COMPILER_ARGS]
val forbiddenCompilerArgs = freeCompilerArgs intersect EXPLICITLY_FORBIDDEN_COMPILER_ARGS
val forbiddenCompilerArgs = TestCompilerArgs.findForbiddenArgs(freeCompilerArgs)
assertTrue(forbiddenCompilerArgs.isEmpty()) {
"""
$location: Forbidden compiler arguments found in $FREE_COMPILER_ARGS directive: $forbiddenCompilerArgs
@@ -0,0 +1,106 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseGroup
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseGroupId
import org.jetbrains.kotlin.konan.blackboxtest.support.TestFile
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeFactory
import org.jetbrains.kotlin.konan.blackboxtest.support.util.expandGlobTo
import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.junit.jupiter.api.fail
import java.io.File
internal class PredefinedTestCaseGroupProvider(annotation: PredefinedTestCases) : TestCaseGroupProvider {
private val testCaseIdToPredefinedTestCase: Map<TestCaseId.Named, PredefinedTestCase> = buildMap {
annotation.testCases.forEach { predefinedTestCase ->
val testCaseId = TestCaseId.Named(predefinedTestCase.name)
if (put(testCaseId, predefinedTestCase) != null)
fail { "Duplicated test cases found: $testCaseId" }
}
}
// Assumption: Every test case group contains exactly one test case.
private val testCaseGroupIdToTestCaseId: Map<TestCaseGroupId.Named, TestCaseId.Named> = buildMap {
testCaseIdToPredefinedTestCase.keys.forEach { testCaseId ->
val testCaseGroupId = testCaseId.testCaseGroupId
if (put(testCaseGroupId, testCaseId) != null)
fail { "Duplicated test case groups found: $testCaseGroupId" }
}
}
private val lazyTestCaseGroups = ThreadSafeFactory<TestCaseGroupId.Named, TestCaseGroup?> { testCaseGroupId ->
val testCaseId = testCaseGroupIdToTestCaseId[testCaseGroupId] ?: return@ThreadSafeFactory null
val predefinedTestCase = testCaseIdToPredefinedTestCase[testCaseId] ?: return@ThreadSafeFactory null
val module = TestModule.Exclusive(
name = testCaseId.uniqueName,
directDependencySymbols = emptySet(),
directFriendSymbols = emptySet()
)
predefinedTestCase.sourceLocations
.expandGlobs { "No files found for test case $testCaseId" }
.forEach { file -> module.files += TestFile.createCommitted(file, module) }
val testCase = TestCase(
id = testCaseId,
kind = TestKind.STANDALONE,
modules = setOf(module),
freeCompilerArgs = predefinedTestCase.freeCompilerArgs
.parseCompilerArgs { "Failed to parse free compiler arguments for test case $testCaseId" },
nominalPackageName = testCaseId.uniqueName,
expectedOutputDataFile = null,
extras = WithTestRunnerExtras
)
testCase.initialize(null)
TestCaseGroup.Default(disabledTestCaseIds = emptySet(), testCases = listOf(testCase))
}
override fun setPreprocessors(testDataDir: File, preprocessors: List<(String) -> String>) {
// do nothing
}
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId): TestCaseGroup? {
assertTrue(testCaseGroupId is TestCaseGroupId.Named)
return lazyTestCaseGroups[testCaseGroupId.cast()]
}
companion object {
private fun Array<String>.expandGlobs(noExpandedFilesErrorMessage: () -> String): Set<File> {
val files = buildSet {
this@expandGlobs.forEach { pathPattern -> expandGlobTo(getAbsoluteFile(pathPattern), this) }
}
assertTrue(files.isNotEmpty(), noExpandedFilesErrorMessage)
return files
}
private fun Array<String>.parseCompilerArgs(parsingErrorMessage: () -> String): TestCompilerArgs =
if (isEmpty())
TestCompilerArgs.EMPTY
else {
val freeCompilerArgs = asList()
val forbiddenCompilerArgs = TestCompilerArgs.findForbiddenArgs(freeCompilerArgs)
assertTrue(forbiddenCompilerArgs.isEmpty()) {
"""
${parsingErrorMessage()}
Forbidden compiler arguments found: $forbiddenCompilerArgs
All arguments: $this
""".trimIndent()
}
TestCompilerArgs(freeCompilerArgs)
}
}
}
@@ -0,0 +1,15 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestSettings
@Target(AnnotationTarget.CLASS)
@TestSettings(providerClass = PredefinedTestCaseGroupProvider::class)
annotation class PredefinedTestCases(vararg val testCases: PredefinedTestCase)
@Target()
annotation class PredefinedTestCase(val name: String, val freeCompilerArgs: Array<String>, val sourceLocations: Array<String>)
@@ -48,8 +48,9 @@ internal fun parseGTestReport(stdOut: String): GTestReport {
val cleanStdOut = StringBuilder()
var expectStatusLine = false
stdOut.lineSequence().forEach { line ->
stdOut.lineSequence().forEachIndexed { index, line ->
when {
index == 0 && line.startsWith(STDLIB_TESTS_IGNORED_LINE_PREFIX) -> Unit
expectStatusLine -> {
val matcher = STATUS_LINE_REGEX.matchEntire(line)
if (matcher != null) {
@@ -97,6 +98,7 @@ internal fun parseGTestListing(rawGTestListing: String): Collection<TestFunction
}
state = when {
index == 0 && line.startsWith(STDLIB_TESTS_IGNORED_LINE_PREFIX) -> state
line.isBlank() -> when (state) {
is NewTest, is End -> End
else -> parseError("Unexpected empty line")
@@ -128,6 +130,9 @@ internal fun parseGTestListing(rawGTestListing: String): Collection<TestFunction
private val RUN_LINE_REGEX = Regex("""^\[\s+RUN\s+]\s+.*""")
private val STATUS_LINE_REGEX = Regex("""^\[\s+([A-Z]+)\s+]\s+(\S+)\s+.*""")
// 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 sealed interface GTestListingParseState {
object Begin : GTestListingParseState
object End : GTestListingParseState