Analysis API FIR: introduce library resolve tests

This commit is contained in:
Ilya Kirillov
2022-01-25 16:46:43 +01:00
parent 96e1f1e7d4
commit caddbda01b
22 changed files with 1784 additions and 128 deletions
@@ -11,12 +11,10 @@ import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.components.KtDeclarationRendererOptions
import org.jetbrains.kotlin.analysis.api.components.RendererModifier
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMemberSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithKind
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.name.FqName
@@ -53,7 +51,7 @@ abstract class AbstractReferenceResolveTest : AbstractHLApiSingleModuleTest() {
PsiTreeUtil.findElementOfClassAtOffset(mainKtFile, caretPosition, KtDeclaration::class.java, false) ?: mainKtFile
) {
val symbols = ktReferences.flatMap { it.resolveToSymbols() }
checkReferenceResultForValidity(module, testServices, symbols)
checkReferenceResultForValidity(ktReferences, module, testServices, symbols)
renderResolvedTo(symbols)
}
@@ -69,6 +67,7 @@ abstract class AbstractReferenceResolveTest : AbstractHLApiSingleModuleTest() {
mainKtFile.findReferenceAt(caretPosition)?.unwrapMultiReferences().orEmpty().filterIsInstance<KtReference>()
private fun KtAnalysisSession.checkReferenceResultForValidity(
references: List<KtReference>,
module: TestModule,
testServices: TestServices,
resolvedTo: List<KtSymbol>
@@ -78,7 +77,9 @@ abstract class AbstractReferenceResolveTest : AbstractHLApiSingleModuleTest() {
"Reference should be unresolved, but was resolved to ${renderResolvedTo(resolvedTo)}"
}
} else {
testServices.assertions.assertTrue(resolvedTo.isNotEmpty()) { "Unresolved reference" }
if (resolvedTo.isEmpty()) {
testServices.assertions.fail { "Unresolved reference ${references.first().element.text}" }
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2022 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.analysis.api.impl.base.test.utils.libraries
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
import java.nio.file.Path
class CompiledLibraryProvider(private val testServices: TestServices) : TestService {
private val libraries = mutableMapOf<String, CompiledLibrary>()
fun compileToLibrary(module: TestModule): CompiledLibrary {
if (module.name in libraries) {
error("Library for module ${module.name} is already compiled")
}
val libraryJar = TestModuleCompiler.compileTestModuleToLibrary(module, testServices)
val librarySourcesJar = TestModuleCompiler.compileTestModuleToLibrarySources(module, testServices)
return CompiledLibrary(libraryJar, librarySourcesJar).also { libraries[module.name] = it }
}
fun getCompiledLibrary(module: TestModule): CompiledLibrary =
libraries.getValue(module.name)
}
val TestServices.compiledLibraryProvider: CompiledLibraryProvider by TestServices.testServiceAccessor()
data class CompiledLibrary(
val jar: Path,
val sourcesJar: Path,
)
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2022 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.analysis.api.impl.base.test.utils.libraries
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.SkipTestException
import org.jetbrains.kotlin.test.MockLibraryUtil
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.model.TestModule
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.notExists
object CompilerExecutor {
fun compileLibrary(sourcesPath: Path, options: List<String>, compilationErrorExpected: Boolean): Path {
val library = sourcesPath / "library.jar"
val sourceFiles = sourcesPath.toFile().walkBottomUp()
val commands = buildList {
sourceFiles.mapTo(this) { it.absolutePath }
addAll(options)
add("-d")
add(library.absolutePathString())
}
try {
MockLibraryUtil.runJvmCompiler(commands)
} catch (e: Throwable) {
if (!compilationErrorExpected) {
throw IllegalStateException("Unexpected compilation error while compiling library", e)
}
}
if (library.exists() && compilationErrorExpected) {
error("Compilation error expected but, code was compiled successfully")
}
if (library.notExists()) {
throw LibraryWasNotCompiledDueToExpectedCompilationError()
}
return library
}
fun parseCompilerOptionsFromTestdata(module: TestModule): List<String> = buildList {
module.directives[LanguageSettingsDirectives.API_VERSION].firstOrNull()?.let { apiVersion ->
addAll(listOf("-api-version", apiVersion.versionString))
}
module.directives[JvmEnvironmentConfigurationDirectives.JVM_TARGET].firstOrNull()?.let { jvmTarget ->
addAll(listOf("-jvm-target", jvmTarget.description))
}
addAll(module.directives[Directives.COMPILER_ARGUMENTS])
}
object Directives : SimpleDirectivesContainer() {
val COMPILER_ARGUMENTS by stringDirective("List of additional compiler arguments")
val COMPILATION_ERRORS by directive("Is compilation errors expected in the file")
}
}
class LibraryWasNotCompiledDueToExpectedCompilationError : SkipTestException()
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2022 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.analysis.api.impl.base.test.utils.libraries
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.EnvironmentConfigurator
import org.jetbrains.kotlin.test.services.TestServices
class LibraryEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) {
override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) {
val library = testServices.compiledLibraryProvider.compileToLibrary(module).jar
configuration.addJvmClasspathRoot(library.toFile())
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2022 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.analysis.api.impl.base.test.utils.libraries
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
import java.nio.file.Path
import kotlin.io.path.absolutePathString
object TestLibraryUtils {
fun getAllVirtualFilesFromJar(jar: Path): Collection<VirtualFile> {
val jarFileSystem = CoreJarFileSystem()
val root = jarFileSystem.refreshAndFindFileByPath(jar.absolutePathString() + "!/")!!
val files = mutableSetOf<VirtualFile>()
VfsUtilCore.iterateChildrenRecursively(
root,
/*filter=*/{ true },
/*iterator=*/{ virtualFile ->
if (!virtualFile.isDirectory) {
files.add(virtualFile)
}
true
}
)
return files
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2022 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.analysis.api.impl.base.test.utils.libraries
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.sourceFileProvider
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.ByteArrayInputStream
import java.nio.file.Path
import java.util.jar.Attributes
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import kotlin.io.path.createFile
import kotlin.io.path.div
import kotlin.io.path.outputStream
import kotlin.io.path.writeText
internal object TestModuleCompiler {
fun compileTestModuleToLibrary(module: TestModule, testServices: TestServices): Path {
val tmpDir = KtTestUtil.tmpDir("testSourcesToCompile").toPath()
for (testFile in module.files) {
val text = testServices.sourceFileProvider.getContentOfSourceFile(testFile)
val tmpSourceFile = (tmpDir / testFile.name).createFile()
tmpSourceFile.writeText(text)
}
return CompilerExecutor.compileLibrary(
tmpDir,
CompilerExecutor.parseCompilerOptionsFromTestdata(module),
compilationErrorExpected = CompilerExecutor.Directives.COMPILATION_ERRORS in module.directives
)
}
fun compileTestModuleToLibrarySources(module: TestModule, testServices: TestServices): Path {
val tmpDir = KtTestUtil.tmpDir("testSourcesToCompile").toPath()
val librarySourcesPath = tmpDir / "library-sources.jar"
val manifest = Manifest().apply { mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0" }
JarOutputStream(librarySourcesPath.outputStream(), manifest).use { jarOutputStream ->
for (testFile in module.files) {
val text = testServices.sourceFileProvider.getContentOfSourceFile(testFile)
addFileToJar(testFile.name, text, jarOutputStream)
}
}
return librarySourcesPath
}
private fun addFileToJar(path: String, text: String, jarOutputStream: JarOutputStream) {
jarOutputStream.putNextEntry(JarEntry(path))
ByteArrayInputStream(text.toByteArray()).copyTo(jarOutputStream)
jarOutputStream.closeEntry()
}
}