diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppHighlightingTestDataWithGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppHighlightingTestDataWithGradleIT.kt index fffa25a1ba5..1e0db6065dc 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppHighlightingTestDataWithGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MppHighlightingTestDataWithGradleIT.kt @@ -5,146 +5,107 @@ package org.jetbrains.kotlin.gradle -import org.jetbrains.kotlin.gradle.testbase.enableCacheRedirector +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.modify -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import org.jetbrains.kotlin.gradle.util.capitalize +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.ArgumentsSource import java.io.File -import java.util.* +import java.nio.file.Files +import java.util.stream.Stream +import kotlin.io.path.appendText +import kotlin.streams.asStream +import kotlin.streams.toList -@RunWith(Parameterized::class) -class MppHighlightingTestDataWithGradleIT : BaseGradleIT() { - override val defaultGradleVersion: GradleVersionRequired = GradleVersionRequired.FOR_MPP_SUPPORT +@DisplayName("Check Multiplatform IDE highlighting projects") +@MppGradlePluginTests +internal class MppHighlightingTestDataWithGradleIT : KGPBaseTest() { - @Test - fun runTestK2NativeCli() = doTest(CliCompiler.NATIVE) - - @Test - fun runTestK2MetadataCli() = doTest(CliCompiler.K2METADATA) - - @Before - fun cleanup() { - project.setupWorkingDir(false) - project.gradleSettingsScript().modify { it.lines().filter { !it.startsWith("include") }.joinToString("\n") } - project.projectDir.resolve("src").deleteRecursively() - project.gradleBuildScript().modify { line -> - line.lines().dropLastWhile { it != buildScriptCustomizationMarker }.joinToString("\n") - } - - project.projectDir.toPath().enableCacheRedirector() - } - - private val project by lazy { Project("mpp-source-set-hierarchy-analysis") } - - private fun doTest(cliCompiler: CliCompiler) = with(project) { - val expectedErrorsPerSourceSetName = sourceRoots.associate { sourceRoot -> - sourceRoot.kotlinSourceSetName to testDataDir.resolve(sourceRoot.directoryName).walkTopDown() - .filter { it.extension == "kt" } - .map { CodeWithErrorInfo.parse(it.readText()) }.toList() - .flatMap { it.errorInfo } - } - - // put sources into project dir: - sourceRoots.forEach { sourceRoot -> - val sourceSetDir = projectDir.resolve(sourceRoot.gradleSrcDir) - testDataDir.resolve(sourceRoot.directoryName).copyRecursively(sourceSetDir) - sourceSetDir.walkTopDown().filter { it.isFile }.forEach { file -> - file.modify { CodeWithErrorInfo.parse(file.readText()).code } + @GradleWithMppHighlightingTest + fun runTest( + gradleVersion: GradleVersion, + cliCompiler: CliCompiler, + testDataDir: File, + sourceRoots: List + ) { + project("mpp-source-set-hierarchy-analysis", gradleVersion) { + val expectedErrorsPerSourceSetName = sourceRoots.associate { sourceRoot -> + sourceRoot.kotlinSourceSetName to testDataDir.resolve(sourceRoot.directoryName).walkTopDown() + .filter { it.extension == "kt" } + .map { CodeWithErrorInfo.parse(it.readText()) }.toList() + .flatMap { it.errorInfo } } - } - // create Gradle Kotlin source sets for project roots: - val scriptCustomization = buildString { - appendLine() - appendLine("kotlin {\n sourceSets {") + // put sources into project dir: sourceRoots.forEach { sourceRoot -> - if (sourceRoot.kotlinSourceSetName != "commonMain") { - appendLine( - """ create("${sourceRoot.kotlinSourceSetName}") { - | dependsOn(getByName("commonMain")) - | listOf(${cliCompiler.targets.joinToString { "$it()" }}).forEach { - | it.compilations["main"].defaultSourceSet.dependsOn(this@create) - | } - | } - | - """.trimMargin() - ) - } else { - appendLine(" // commonMain source set used for common module") + val sourceSetDir = projectPath.resolve(sourceRoot.gradleSrcDir).toFile() + testDataDir.resolve(sourceRoot.directoryName).copyRecursively(sourceSetDir) + sourceSetDir.walkTopDown().filter { it.isFile }.forEach { file -> + file.modify { CodeWithErrorInfo.parse(file.readText()).code } } } - // Add dependencies using dependsOn: - sourceRoots.forEach { sourceRoot -> - sourceRoot.dependencies.forEach { dependency -> - sourceRoots.find { it.qualifiedName == dependency }?.let { depSourceRoot -> - val depSourceSet = depSourceRoot.kotlinSourceSetName - appendLine(""" getByName("${sourceRoot.kotlinSourceSetName}").dependsOn(getByName("$depSourceSet"))""") + // create Gradle Kotlin source sets for project roots: + val scriptCustomization = buildString { + appendLine() + appendLine("kotlin {\n sourceSets {") + sourceRoots.forEach { sourceRoot -> + if (sourceRoot.kotlinSourceSetName != "commonMain") { + appendLine( + """ + | create("${sourceRoot.kotlinSourceSetName}") { + | dependsOn(getByName("commonMain")) + | listOf(${cliCompiler.targets.joinToString { "$it()" }}).forEach { + | it.compilations["main"].defaultSourceSet.dependsOn(this@create) + | } + | } + | + """.trimMargin() + ) + } else { + appendLine(" // commonMain source set used for common module") } } + + // Add dependencies using dependsOn: + sourceRoots.forEach { sourceRoot -> + sourceRoot.dependencies.forEach { dependency -> + sourceRoots.find { it.qualifiedName == dependency }?.let { depSourceRoot -> + val depSourceSet = depSourceRoot.kotlinSourceSetName + appendLine(""" getByName("${sourceRoot.kotlinSourceSetName}").dependsOn(getByName("$depSourceSet"))""") + } + } + } + appendLine(" }\n}") } - appendLine(" }\n}") - } - gradleBuildScript().appendText("\n" + scriptCustomization) + buildGradleKts.appendText("\n" + scriptCustomization) - val tasks = sourceRoots.map { "compile" + it.kotlinSourceSetName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + "KotlinMetadata" } + val tasks = sourceRoots.map { + "compile" + it.kotlinSourceSetName.capitalize() + "KotlinMetadata" + } - build(*tasks.toTypedArray()) { if (expectedErrorsPerSourceSetName.values.all { it.all(ErrorInfo::isAllowedInCli) }) { - assertSuccessful() + build(*tasks.toTypedArray()) } else { - assertFailed() // TODO: check the exact error message in the output, not just that the build failed + buildAndFail(*tasks.toTypedArray()) } } } - companion object { - private val testDataRoot = - File("../../../idea/testData/multiModuleHighlighting/multiplatform") - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun testData() = testDataRoot.listFiles()!!.filter { it.isDirectory }.mapNotNull { testDataDir -> - val testDataSourceRoots = checkNotNull(testDataDir.listFiles()) - val sourceRoots = testDataSourceRoots.map { TestCaseSourceRoot.parse(it.name) } - - arrayOf(testDataDir.name, testDataDir, sourceRoots).takeIf { isTestSuiteValidForCommonCode(testDataDir, sourceRoots) } - } - - private val bannedDependencies = setOf("fulljdk", "stdlib", "coroutines") - - const val testSourceRootSuffix = "tests" - - private const val buildScriptCustomizationMarker = "// customized content below" - - private fun isTestSuiteValidForCommonCode(testDataDir: File, sourceRoots: List): Boolean { - sourceRoots.forEach { - val bannedDepsFound = bannedDependencies.intersect(it.dependencies) - if (bannedDepsFound.isNotEmpty()) - return false - } - - // Java sources can't be used in intermediate source sets - if (testDataDir.walkTopDown().any { it.extension == "java" }) - return false - - // Cannot test !CHECK_HIGHLIGHTING in CLI - if (testDataDir.walkTopDown().filter { it.isFile }.any { "!CHECK_HIGHLIGHTING" in it.readText() }) - return false - - return true - } - } - data class TestCaseSourceRoot( val directoryName: String, val qualifiedNameParts: Iterable, val dependencies: Iterable, ) { companion object { + private const val TEST_SOURCE_ROOT_SUFFIX = "tests" + private const val COMMON_SOURCE_ROOT_NAME = "common" + fun parse(directoryName: String): TestCaseSourceRoot { val parts = directoryName.split("_") @@ -156,21 +117,19 @@ class MppHighlightingTestDataWithGradleIT : BaseGradleIT() { val platformIndex = when (nameParts.size) { 1 -> 0 - else -> if (nameParts.last() == testSourceRootSuffix) 0 else 1 + else -> if (nameParts.last() == TEST_SOURCE_ROOT_SUFFIX) 0 else 1 } val additionalDependencies = mutableListOf().apply { - if (nameParts[platformIndex] != commonSourceRootName) - add(partsToQualifiedName(nameParts.take(platformIndex) + commonSourceRootName + nameParts.drop(platformIndex + 1))) - if (nameParts.last() == testSourceRootSuffix) + if (nameParts[platformIndex] != COMMON_SOURCE_ROOT_NAME) + add(partsToQualifiedName(nameParts.take(platformIndex) + COMMON_SOURCE_ROOT_NAME + nameParts.drop(platformIndex + 1))) + if (nameParts.last() == TEST_SOURCE_ROOT_SUFFIX) add(partsToQualifiedName(nameParts.dropLast(1))) } return TestCaseSourceRoot(directoryName, nameParts, deps + additionalDependencies) } - private const val commonSourceRootName = "common" - private fun partsToQualifiedName(parts: Iterable) = parts.joinToString("") } @@ -178,7 +137,7 @@ class MppHighlightingTestDataWithGradleIT : BaseGradleIT() { get() = partsToQualifiedName(qualifiedNameParts) val kotlinSourceSetName - get() = "intermediate${qualifiedName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}" + get() = "intermediate${qualifiedName.capitalize()}" val gradleSrcDir get() = "src/$kotlinSourceSetName/kotlin" @@ -210,22 +169,74 @@ class MppHighlightingTestDataWithGradleIT : BaseGradleIT() { ) { val isAllowedInCli get() = when (expectedErrorKind) { - "NO_ACTUAL_FOR_EXPECT", null /*TODO are some nulls better than others?*/ -> true + "NO_ACTUAL_FOR_EXPECT", null -> true else -> false } } - private enum class CliCompiler(val targets: List) { - K2METADATA(listOf("jvm", "js")), NATIVE(listOf("linuxX64", "linuxArm64")) + internal enum class CliCompiler( + val targets: List + ) { + K2METADATA(listOf("jvm", "js")), + NATIVE(listOf("linuxX64", "linuxArm64")); } - @Suppress("unused") // used for parameter string representation in test output - @Parameterized.Parameter(0) - lateinit var testCaseName: String + class GradleAndMppHighlightingProvider : GradleArgumentsProvider() { + private val testDataRoot = File("../../../idea/testData/multiModuleHighlighting/multiplatform") - @Parameterized.Parameter(1) - lateinit var testDataDir: File + private val bannedDependencies = setOf("fulljdk", "stdlib", "coroutines") - @Parameterized.Parameter(2) - lateinit var sourceRoots: List + private fun isTestSuiteValidForCommonCode( + testDataDir: File, + sourceRoots: List + ): Boolean = when { + sourceRoots.any { bannedDependencies.intersect(it.dependencies.toSet()).isNotEmpty() } -> false + // Java sources can't be used in intermediate source sets + testDataDir.walkTopDown().any { it.extension == "java" } -> false + // Cannot test !CHECK_HIGHLIGHTING in CLI + testDataDir.walkTopDown().filter { it.isFile }.any { "!CHECK_HIGHLIGHTING" in it.readText() } -> false + else -> true + } + + private val testData = testDataRoot + .walkTopDown() + .maxDepth(1) + .filter { it.isDirectory && Files.newDirectoryStream(it.toPath()).use { stream -> stream.toList().isNotEmpty() } } + .map { testDataDir -> + Pair( + testDataDir, + Files.newDirectoryStream(testDataDir.toPath()).use { stream -> + stream.map { TestCaseSourceRoot.parse(it.fileName.toString()) } + } + ) + } + .filter { + isTestSuiteValidForCommonCode(it.first, it.second) + } + .toList() + + override fun provideArguments( + context: ExtensionContext + ): Stream { + val gradleVersions = super.provideArguments(context).map { it.get().first() as GradleVersion }.toList() + + return gradleVersions + .flatMap { gradleVersion -> + CliCompiler.entries.flatMap { cliCompiler -> + testData.map { + Arguments.of(gradleVersion, cliCompiler, it.first, it.second) + } + } + } + .asSequence() + .asStream() + } + } + + @Target(AnnotationTarget.FUNCTION) + @Retention(AnnotationRetention.RUNTIME) + @GradleTestVersions + @ParameterizedTest(name = "{0} - {1} - {2} - {3}: {displayName}") + @ArgumentsSource(GradleAndMppHighlightingProvider::class) + annotation class GradleWithMppHighlightingTest }