From 02d68d7b1245f9cf7f19d69d6593bdfe50b88a41 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Thu, 16 Dec 2021 18:22:12 +0300 Subject: [PATCH] Introduce cls stub building tests (mostly migrated from intellij) --- build.gradle.kts | 5 + .../build.gradle.kts | 13 +- .../builder/AbstractClsStubBuilderTest.kt | 156 ++++++++++++++++++ generators/build.gradle.kts | 1 + .../kotlin/generators/tests/GenerateTests.kt | 14 ++ 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 compiler/psi/cls-psi-file-stub-builder/tests/org/jetbrains/kotlin/psi/stubs/file/builder/AbstractClsStubBuilderTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index c19faa61c25..03b6afb5bce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -750,6 +750,7 @@ tasks { dependsOn("examplesTest") dependsOn("nativeCompilerTest") + dependsOn("psiStubTests") dependsOn(":kotlin-daemon-tests:test") dependsOn("scriptingTest") @@ -769,6 +770,10 @@ tasks { dependsOn(":generators:test") } + register("psiStubTests") { + dependsOn( ":compiler:psi:cls-psi-file-stub-builder:test") + } + register("toolsTest") { dependsOn(":tools:kotlinp:test") dependsOn(":native:kotlin-klib-commonizer:test") diff --git a/compiler/psi/cls-psi-file-stub-builder/build.gradle.kts b/compiler/psi/cls-psi-file-stub-builder/build.gradle.kts index 293624131c4..b80062b9769 100644 --- a/compiler/psi/cls-psi-file-stub-builder/build.gradle.kts +++ b/compiler/psi/cls-psi-file-stub-builder/build.gradle.kts @@ -13,9 +13,20 @@ dependencies { implementation(project(":core:compiler.common.jvm")) api(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) } + + testImplementation(projectTests(":compiler:tests-common")) + } sourceSets { "main" { projectDefault() } - "test" {} + "test" { projectDefault() } } + +projectTest { + dependsOn(":dist") + workingDir = rootDir +} + + +testsJar() \ No newline at end of file diff --git a/compiler/psi/cls-psi-file-stub-builder/tests/org/jetbrains/kotlin/psi/stubs/file/builder/AbstractClsStubBuilderTest.kt b/compiler/psi/cls-psi-file-stub-builder/tests/org/jetbrains/kotlin/psi/stubs/file/builder/AbstractClsStubBuilderTest.kt new file mode 100644 index 00000000000..8a5533a464a --- /dev/null +++ b/compiler/psi/cls-psi-file-stub-builder/tests/org/jetbrains/kotlin/psi/stubs/file/builder/AbstractClsStubBuilderTest.kt @@ -0,0 +1,156 @@ +/* + * 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.psi.stubs.file.builder + +import com.intellij.core.CoreApplicationEnvironment +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem +import com.intellij.psi.PsiElement +import com.intellij.psi.impl.DebugUtil +import com.intellij.psi.stubs.StubElement +import com.intellij.util.indexing.FileContentImpl +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.name.SpecialNames +import org.jetbrains.kotlin.psi.stubs.impl.STUB_TO_STRING_PREFIX +import org.jetbrains.kotlin.test.* +import org.jetbrains.kotlin.utils.addIfNotNull +import java.io.DataInput +import java.io.DataOutput +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.absolutePathString +import kotlin.io.path.name +import kotlin.io.path.readText +import kotlin.streams.toList + +abstract class AbstractClsStubBuilderTest : KotlinTestWithEnvironment() { + override fun createEnvironment(): KotlinCoreEnvironment { + return KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(testRootDisposable, ConfigurationKind.JDK_NO_RUNTIME) + } + + override fun setUp() { + super.setUp() + + with(environment.projectEnvironment.environment) { + registerApplicationServices() + } + } + + private fun CoreApplicationEnvironment.registerApplicationServices() { + registerApplicationService(FileAttributeService::class.java, DummyFileAttributeService) + registerApplicationService(ClsKotlinBinaryClassCache::class.java, ClsKotlinBinaryClassCache()) + } + + fun doTest(testDirectory: String) { + val testDirectoryPath = Paths.get(testDirectory) + val testData = TestData.createFromDirectory(testDirectoryPath) + + doTest(testData, useStringTable = true) + doTest(testData, useStringTable = false) + } + + + private fun doTest(testData: TestData, useStringTable: Boolean) { + val classFile = getClassFileToDecompile(testData, useStringTable) + testClsStubsForFile(classFile, testData) + } + + private fun testClsStubsForFile(classFile: VirtualFile, testData: TestData) { + val stubTreeFromCls = KotlinClsStubBuilder().buildFileStub(FileContentImpl.createByFile(classFile))!! + KotlinTestUtils.assertEqualsToFile(testData.expectedFile, stubTreeFromCls.serializeToString()) + } + + private fun getClassFileToDecompile(testData: TestData, useStringTable: Boolean): VirtualFile { + val extraOptions = buildList { + add("-Xallow-kotlin-package") + if (useStringTable) { + add("-Xuse-type-table") + } + } + + val library = CompilerTestUtil.compileJvmLibrary( + src = testData.directory.toFile(), + extraOptions = extraOptions, + ).toPath() + + return findClassFileByName(library, testData.jvmFileName) + } + + private fun findClassFileByName(library: Path, className: String): VirtualFile { + val jarFileSystem = environment.projectEnvironment.environment.jarFileSystem as CoreJarFileSystem + val root = jarFileSystem.refreshAndFindFileByPath(library.absolutePathString() + "!/")!! + val files = mutableSetOf() + VfsUtilCore.iterateChildrenRecursively( + root, + /*filter=*/{ virtualFile -> + virtualFile.isDirectory || virtualFile.name == "$className.class" + }, + /*iterator=*/{ virtualFile -> + if (!virtualFile.isDirectory) { + files.addIfNotNull(virtualFile) + } + true + }) + + return files.single() + } + +} + +private fun StubElement.serializeToString(): String { + return serializeStubToString(this) +} + +private fun serializeStubToString(stubElement: StubElement<*>): String { + val treeStr = DebugUtil.stubTreeToString(stubElement).replace(SpecialNames.SAFE_IDENTIFIER_FOR_NO_NAME.asString(), "") + + // Nodes are stored in form "NodeType:Node" and have too many repeating information for Kotlin stubs + // Remove all repeating information (See KotlinStubBaseImpl.toString()) + return treeStr.lines().joinToString(separator = "\n") { + if (it.contains(STUB_TO_STRING_PREFIX)) { + it.takeWhile(Char::isWhitespace) + it.substringAfter("KotlinStub$") + } else { + it + } + }.replace(", [", "[") +} + + +private data class TestData( + val directory: Path, + val mainKotlinFile: Path, + val expectedFile: Path, + val jvmFileName: String +) { + companion object { + fun createFromDirectory(directory: Path): TestData { + val allFiles = Files.list(directory).toList() + val mainKotlinFile = allFiles.single { path -> + path.name.replaceFirstChar { it.uppercaseChar() } == "${directory.name.removeSuffix("Kt")}.kt" + } + val jvmFileName = InTextDirectivesUtils.findStringWithPrefixes(mainKotlinFile.readText(), "JVM_FILE_NAME:") + ?: directory.name + return TestData( + directory = directory, + mainKotlinFile = mainKotlinFile, + expectedFile = allFiles.single { it.name == "${directory.name}.txt" }, + jvmFileName = jvmFileName, + ) + } + } +} + +private object DummyFileAttributeService : FileAttributeService { + override fun write(file: VirtualFile, id: String, value: T, writeValueFun: (DataOutput, T) -> Unit): CachedAttributeData { + return CachedAttributeData(value, 0) + } + + override fun read(file: VirtualFile, id: String, readValueFun: (DataInput) -> T): CachedAttributeData? { + return null + } +} diff --git a/generators/build.gradle.kts b/generators/build.gradle.kts index 26f2bb8b4b3..f28293a2488 100644 --- a/generators/build.gradle.kts +++ b/generators/build.gradle.kts @@ -76,6 +76,7 @@ dependencies { testImplementation(projectTests(":compiler:test-infrastructure")) testImplementation(projectTests(":compiler:tests-common-new")) testImplementation(projectTests(":js:js.tests")) + testImplementation(projectTests(":compiler:psi:cls-psi-file-stub-builder")) testApiJUnit5() if (Ide.IJ()) { diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index d1c96ef3638..ea5ce9b964b 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.android.synthetic.test.AbstractAndroidSyntheticPrope import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirPluginBlackBoxCodegenTest import org.jetbrains.kotlin.fir.plugin.runners.AbstractFirPluginDiagnosticTest import org.jetbrains.kotlin.generators.TestGroup +import org.jetbrains.kotlin.generators.TestGroupSuite import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5 import org.jetbrains.kotlin.generators.impl.generateTestGroupSuite import org.jetbrains.kotlin.incremental.* @@ -31,6 +32,7 @@ import org.jetbrains.kotlin.kapt3.test.AbstractKotlinKaptContextTest import org.jetbrains.kotlin.lombok.AbstractLombokCompileTest import org.jetbrains.kotlin.noarg.* import org.jetbrains.kotlin.parcelize.test.runners.* +import org.jetbrains.kotlin.psi.stubs.file.builder.AbstractClsStubBuilderTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverScriptTest import org.jetbrains.kotlin.samWithReceiver.AbstractSamWithReceiverTest import org.jetbrains.kotlin.test.TargetBackend @@ -42,6 +44,7 @@ import org.jetbrains.kotlinx.atomicfu.AbstractAtomicfuJsIrTest fun main(args: Array) { System.setProperty("java.awt.headless", "true") generateTestGroupSuite(args) { + generateStubBuilderTests() testGroup("compiler/incremental-compilation-impl/test", "jps-plugin/testData") { fun incrementalJvmTestData(targetBackend: TargetBackend): TestGroup.TestClass.() -> Unit = { model("incremental/pureKotlin", extension = null, recursive = false, targetBackend = targetBackend) @@ -409,3 +412,14 @@ fun main(args: Array) { } } } + +private fun TestGroupSuite.generateStubBuilderTests() { + testGroup( + "compiler/psi/cls-psi-file-stub-builder/tests", + "compiler/psi/cls-psi-file-stub-builder/testData", + ) { + testClass { + model("clsFileStubBuilder", extension = null, recursive = false) + } + } +} \ No newline at end of file