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
@@ -29,7 +29,11 @@ dependencies {
testApi(projectTests(":compiler:tests-common-new"))
testApi(projectTests(":compiler:fir:analysis-tests:legacy-fir-tests"))
testApi(projectTests(":analysis:analysis-api-impl-base"))
testApi(projectTests(":analysis:decompiled:decompiler-to-file-stubs"))
testApi(project(":analysis:decompiled:decompiler-to-file-stubs"))
testApi(project(":analysis:decompiled:decompiler-to-psi"))
testApi(project(":kotlin-test:kotlin-test-junit"))
testApi(toolsJar())
testApiJUnit5()
testApi(project(":analysis:symbol-light-classes"))
@@ -22,7 +22,7 @@ import java.util.regex.Pattern;
@SuppressWarnings("all")
@TestMetadata("analysis/analysis-api/testData/referenceResolve")
@TestDataPath("$PROJECT_ROOT")
public class FirReferenceResolveTestGenerated extends AbstractReferenceResolveTest {
public class FirSourceReferenceResolveTestGenerated extends AbstractReferenceResolveTest {
@NotNull
@Override
public FrontendApiTestConfiguratorService getConfigurator() {
@@ -3,7 +3,7 @@
* 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.light.classes.symbol.decompiled.service
package org.jetbrains.kotlin.analysis.api.fir.utils.libraries.binary
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.mock.MockApplication
@@ -15,7 +15,7 @@ import com.intellij.psi.FileTypeFileViewProviders
import com.intellij.psi.compiled.ClassFileDecompilers
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.decompiled.light.classes.ClsJavaStubByVirtualFileCache
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.LibraryEnvironmentConfigurator
import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinBuiltInDecompiler
import org.jetbrains.kotlin.analysis.decompiler.psi.KotlinClassFileDecompiler
import org.jetbrains.kotlin.analysis.decompiler.stub.file.ClsKotlinBinaryClassCache
@@ -24,13 +24,15 @@ import org.jetbrains.kotlin.analysis.decompiler.stub.files.DummyFileAttributeSer
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
object SymbolLightClassesForLibraryFrontendApiTestConfiguratorService : FrontendApiTestConfiguratorService {
object LibraryFrontendApiTestConfiguratorService : FrontendApiTestConfiguratorService {
override val allowDependedAnalysisSession: Boolean get() = false
override fun TestConfigurationBuilder.configureTest(disposable: Disposable) {
useConfigurators(::LibraryEnvironmentConfigurator)
usePreAnalysisHandlers(::LibraryModuleRegistrarPreAnalysisHandler)
}
override fun registerProjectServices(project: MockProject) {
project.registerService(ClsJavaStubByVirtualFileCache::class.java)
}
@@ -39,14 +41,17 @@ object SymbolLightClassesForLibraryFrontendApiTestConfiguratorService : Frontend
if (application.getServiceIfCreated(ClsKotlinBinaryClassCache::class.java) != null) return
application.registerService(ClsKotlinBinaryClassCache::class.java)
application.registerService(FileAttributeService::class.java, DummyFileAttributeService)
FileTypeFileViewProviders.INSTANCE.addExplicitExtension(
JavaClassFileType.INSTANCE,
ClassFileViewProviderFactory()
)
@Suppress("DEPRECATION")
ClassFileDecompilers.getInstance().EP_NAME.point.registerExtension(KotlinClassFileDecompiler(), LoadingOrder.FIRST)
@Suppress("DEPRECATION")
ClassFileDecompilers.getInstance().EP_NAME.point.registerExtension(KotlinBuiltInDecompiler(), LoadingOrder.FIRST)
ClassFileDecompilers.getInstance().EP_NAME.point.apply {
registerExtension(KotlinClassFileDecompiler(), LoadingOrder.FIRST)
registerExtension(KotlinBuiltInDecompiler(), LoadingOrder.FIRST)
}
}
override fun doOutOfBlockModification(file: KtFile) {
@@ -0,0 +1,46 @@
/*
* 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.fir.utils.libraries.binary
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.TestKtLibraryModule
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.projectModuleProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.TestLibraryUtils
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.compiledLibraryProvider
import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based.registerTestServices
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.PreAnalysisHandler
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
class LibraryModuleRegistrarPreAnalysisHandler(
testServices: TestServices
) : PreAnalysisHandler(testServices) {
private val moduleInfoProvider = testServices.projectModuleProvider
override fun preprocessModuleStructure(moduleStructure: TestModuleStructure) {
val testModule = moduleStructure.modules.single()
val project = testServices.compilerConfigurationProvider.getProject(testModule)
val decompiledKtFiles = getDecompiledVirtualFilesFromLibrary(testModule, project)
moduleInfoProvider.registerModuleInfo(testModule, TestKtLibraryModule(project, testModule, decompiledKtFiles, testServices))
(project as MockProject).registerTestServices(testModule, decompiledKtFiles, testServices)
}
private fun getDecompiledVirtualFilesFromLibrary(module: TestModule, project: Project): List<KtClsFile> {
val library = testServices.compiledLibraryProvider.getCompiledLibrary(module).jar
val virtualFiles = TestLibraryUtils.getAllVirtualFilesFromJar(library)
return virtualFiles.mapNotNull { virtualFile ->
PsiManager.getInstance(project).findFile(virtualFile) as? KtClsFile
}
}
}
@@ -0,0 +1,39 @@
/*
* 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.analysis.api.fir.utils.libraries.source
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.CompiledLibraryProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.LibraryEnvironmentConfigurator
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.services.ServiceRegistrationData
object LibrarySourceFrontendApiTestConfiguratorService : FrontendApiTestConfiguratorService {
override val allowDependedAnalysisSession: Boolean get() = false
override fun TestConfigurationBuilder.configureTest(disposable: Disposable) {
useConfigurators(::LibraryEnvironmentConfigurator)
usePreAnalysisHandlers(::LibrarySourceModuleRegistrarPreAnalysisHandler)
useAdditionalServices(ServiceRegistrationData(CompiledLibraryProvider::class, ::CompiledLibraryProvider))
}
override fun registerProjectServices(project: MockProject) {
}
override fun registerApplicationServices(application: MockApplication) {
FirFrontendApiTestConfiguratorService.registerApplicationServices(application)
}
override fun doOutOfBlockModification(file: KtFile) {
FirFrontendApiTestConfiguratorService.doOutOfBlockModification(file)
}
}
@@ -0,0 +1,82 @@
/*
* 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.fir.utils.libraries.source
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.TestKtLibrarySourceModule
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.projectModuleProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.TestLibraryUtils
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.compiledLibraryProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based.registerTestServices
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtLibrarySourceModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import org.jetbrains.kotlin.test.frontend.fir.getAnalyzerServices
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.PreAnalysisHandler
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import java.nio.file.Path
class LibrarySourceModuleRegistrarPreAnalysisHandler(
testServices: TestServices
) : PreAnalysisHandler(testServices) {
private val moduleInfoProvider = testServices.projectModuleProvider
override fun preprocessModuleStructure(moduleStructure: TestModuleStructure) {
val testModule = moduleStructure.modules.single()
val project = testServices.compilerConfigurationProvider.getProject(testModule)
val (libraryJar, librarySourcesJar) = testServices.compiledLibraryProvider.getCompiledLibrary(testModule)
val libraryKtModule = KtTestLibraryModule(project, testModule, libraryJar)
val librarySourceKtFiles = TestLibraryUtils
.getAllVirtualFilesFromJar(librarySourcesJar)
.mapNotNull { PsiManager.getInstance(project).findFile(it) as? KtFile }
val sourcesKtModule = TestKtLibrarySourceModule(
project,
testModule,
librarySourceKtFiles.toSet(),
testServices,
libraryKtModule
)
libraryKtModule.librarySources = sourcesKtModule
moduleInfoProvider.registerModuleInfo(testModule, sourcesKtModule)
(project as MockProject).registerTestServices(testModule, librarySourceKtFiles, testServices)
}
}
private class KtTestLibraryModule(
override val project: Project,
private val testModule: TestModule,
private val jar: Path
) : KtLibraryModule {
override val directRegularDependencies: List<KtModule> get() = emptyList()
override val directRefinementDependencies: List<KtModule> get() = emptyList()
override val directFriendDependencies: List<KtModule> get() = emptyList()
override val contentScope: GlobalSearchScope by lazy {
GlobalSearchScope.filesScope(project, TestLibraryUtils.getAllVirtualFilesFromJar(jar))
}
override fun getBinaryRoots(): Collection<Path> = listOf(jar)
override val libraryName: String get() = testModule.name
override lateinit var librarySources: KtLibrarySourceModule
override val platform: TargetPlatform get() = testModule.targetPlatform
override val analyzerServices: PlatformDependentAnalyzerServices get() = testModule.targetPlatform.getAnalyzerServices()
}
@@ -158,7 +158,12 @@ abstract class AbstractFrontendApiTest : TestWithDisposable() {
testConfiguration.directives,
)
val singleModule = moduleStructure.modules.single()
val project = testServices.compilerConfigurationProvider.getProject(singleModule)
val project = try {
testServices.compilerConfigurationProvider.getProject(singleModule)
} catch (_: SkipTestException) {
return
}
val moduleInfoProvider = testServices.projectModuleProvider
with(project as MockProject) {
configurator.registerProjectServices(this)
@@ -183,6 +188,7 @@ abstract class AbstractFrontendApiTest : TestWithDisposable() {
val ktFiles = when (moduleInfo) {
is TestKtSourceModule -> moduleInfo.testFilesToKtFiles.filterKeys { testFile -> !testFile.isAdditional }.values.toList()
is TestKtLibraryModule -> moduleInfo.ktFiles.toList()
is TestKtLibrarySourceModule -> moduleInfo.ktFiles.toList()
else -> error("Unexpected $moduleInfo")
}
@@ -0,0 +1,8 @@
/*
* 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.barebone.test
abstract class SkipTestException : RuntimeException()
@@ -119,6 +119,19 @@ class TestKtLibraryModule(
GlobalSearchScope.filesScope(project, ktFiles.map { it.virtualFile })
}
class TestKtLibrarySourceModule(
project: Project,
testModule: TestModule,
ktFilesFromSourceJar: Set<KtFile>,
testServices: TestServices,
override val binaryLibrary: KtLibraryModule,
) : TestKtModule(project, testModule, ktFilesFromSourceJar, testServices), KtLibrarySourceModule {
override val ktModule: KtModule get() = this
override val contentScope: GlobalSearchScope get() = GlobalSearchScope.filesScope(project, ktFiles.map { it.virtualFile })
override val libraryName: String get() = testModule.name
}
private class LibraryByRoots(
private val roots: List<Path>,
private val module: KtModule,
@@ -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,
)
@@ -1,10 +1,11 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.light.classes.symbol.decompiled.service
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
@@ -17,7 +18,7 @@ import kotlin.io.path.exists
import kotlin.io.path.notExists
object CompilerExecutor {
fun compileLibrary(sourcesPath: Path, options: List<String>, compilationErrorExpected: Boolean): Path? {
fun compileLibrary(sourcesPath: Path, options: List<String>, compilationErrorExpected: Boolean): Path {
val library = sourcesPath / "library.jar"
val sourceFiles = sourcesPath.toFile().walkBottomUp()
val commands = buildList {
@@ -37,7 +38,10 @@ object CompilerExecutor {
if (library.exists() && compilationErrorExpected) {
error("Compilation error expected but, code was compiled successfully")
}
return library.takeIf(Path::exists)
if (library.notExists()) {
throw LibraryWasNotCompiledDueToExpectedCompilationError()
}
return library
}
fun parseCompilerOptionsFromTestdata(module: TestModule): List<String> = buildList {
@@ -56,4 +60,6 @@ object CompilerExecutor {
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()
}
}
@@ -10,9 +10,9 @@ import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.CompilerExecutor
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.light.classes.symbol.base.service.NullabilityAnnotationSourceProvider
import org.jetbrains.kotlin.light.classes.symbol.decompiled.service.CompilerExecutor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.model.DirectiveApplicability
@@ -1,79 +0,0 @@
/*
* 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.light.classes.symbol.decompiled.service
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.impl.jar.CoreJarFileSystem
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.TestKtLibraryModule
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.projectModuleProvider
import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtClsFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.compiler.based.registerTestServices
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.createFile
import kotlin.io.path.div
import kotlin.io.path.writeText
class LibraryModuleRegistrarPreAnalysisHandler(
testServices: TestServices
) : PreAnalysisHandler(testServices) {
private val moduleInfoProvider = testServices.projectModuleProvider
override fun preprocessModuleStructure(moduleStructure: TestModuleStructure) {
val testModule = moduleStructure.modules.single()
val project = testServices.compilerConfigurationProvider.getProject(testModule)
val decompiledKtFiles = getDecompiledVirtualFilesFromLibrary(testModule, project)
moduleInfoProvider.registerModuleInfo(testModule, TestKtLibraryModule(project, testModule, decompiledKtFiles, testServices))
(project as MockProject).registerTestServices(testModule, decompiledKtFiles, testServices)
}
private fun getDecompiledVirtualFilesFromLibrary(module: TestModule, project: Project): List<KtClsFile> {
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)
}
val library = CompilerExecutor.compileLibrary(
tmpDir,
CompilerExecutor.parseCompilerOptionsFromTestdata(module),
compilationErrorExpected = CompilerExecutor.Directives.COMPILATION_ERRORS in module.directives
) ?: return emptyList()
val virtualFiles = getAllVirtualFilesFromLibrary(library)
return virtualFiles.mapNotNull { virtualFile ->
PsiManager.getInstance(project).findFile(virtualFile) as? KtClsFile
}
}
private fun getAllVirtualFilesFromLibrary(library: Path): Collection<VirtualFile> {
val jarFileSystem = CoreJarFileSystem()
val root = jarFileSystem.refreshAndFindFileByPath(library.absolutePathString() + "!/")!!
val files = mutableSetOf<VirtualFile>()
VfsUtilCore.iterateChildrenRecursively(
root,
/*filter=*/{ true },
/*iterator=*/{ virtualFile ->
if (!virtualFile.isDirectory) {
files.add(virtualFile)
}
true
}
)
return files
}
}
@@ -0,0 +1,39 @@
/*
* 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.light.classes.symbol.decompiled.service
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.analysis.api.fir.utils.libraries.binary.LibraryFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.base.test.utils.libraries.CompiledLibraryProvider
import org.jetbrains.kotlin.analysis.decompiled.light.classes.ClsJavaStubByVirtualFileCache
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.services.ServiceRegistrationData
internal object SymbolLightClassesForLibraryFrontendApiTestConfiguratorService : FrontendApiTestConfiguratorService {
override fun TestConfigurationBuilder.configureTest(disposable: Disposable) {
with(LibraryFrontendApiTestConfiguratorService) {
configureTest(disposable)
}
useAdditionalServices(ServiceRegistrationData(CompiledLibraryProvider::class, ::CompiledLibraryProvider))
}
override fun registerProjectServices(project: MockProject) {
LibraryFrontendApiTestConfiguratorService.registerProjectServices(project)
project.registerService(ClsJavaStubByVirtualFileCache::class.java)
}
override fun registerApplicationServices(application: MockApplication) {
LibraryFrontendApiTestConfiguratorService.registerApplicationServices(application)
}
override fun doOutOfBlockModification(file: KtFile) {
LibraryFrontendApiTestConfiguratorService.doOutOfBlockModification(file)
}
}
@@ -37,6 +37,8 @@ import org.jetbrains.kotlin.analysis.api.impl.base.test.symbols.AbstractSymbolBy
import org.jetbrains.kotlin.analysis.api.impl.base.test.symbols.AbstractSymbolByPsiTest
import org.jetbrains.kotlin.analysis.api.impl.base.test.symbols.AbstractSymbolByReferenceTest
import org.jetbrains.kotlin.generators.TestGroupSuite
import org.jetbrains.kotlin.generators.tests.analysis.api.dsl.*
import org.jetbrains.kotlin.generators.tests.analysis.api.dsl.TestModuleKind
import org.jetbrains.kotlin.generators.tests.analysis.api.dsl.component
import org.jetbrains.kotlin.generators.tests.analysis.api.dsl.group
import org.jetbrains.kotlin.generators.tests.analysis.api.dsl.test
@@ -50,6 +52,7 @@ fun TestGroupSuite.generateAnalysisApiTests() {
private fun TestGroupSuite.generateAnalysisApiNonComponentsTests() {
test(
AbstractReferenceResolveTest::class,
testModuleKinds = TestModuleKind.SOURCES_AND_LIBRARIES_SOURCES,
addFe10 = false,
) {
model("referenceResolve", pattern = TestGeneratorUtil.KT_WITHOUT_DOTS_IN_NAME)
@@ -6,8 +6,6 @@
package org.jetbrains.kotlin.generators.tests.analysis.api.dsl
import org.jetbrains.annotations.NotNull
import org.jetbrains.kotlin.analysis.api.descriptors.test.KtFe10FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.generators.MethodGenerator
import org.jetbrains.kotlin.generators.model.MethodModel
@@ -25,7 +23,7 @@ object FrontendConfiguratorTestGenerator : MethodGenerator<FrontendConfiguratorT
override fun generateBody(method: FrontendConfiguratorTestModel, p: Printer): Unit = with(p) {
print("return ")
printWithNoIndent(method.frontendConfiguratorType.configuratorClass.simpleName)
printWithNoIndent(method.frontendConfiguratorClass.simpleName)
printWithNoIndent(".INSTANCE")
printlnWithNoIndent(";")
}
@@ -33,7 +31,7 @@ object FrontendConfiguratorTestGenerator : MethodGenerator<FrontendConfiguratorT
object FrontendConfiguratorTestModelKind : MethodModel.Kind()
class FrontendConfiguratorTestModel(val frontendConfiguratorType: FrontendConfiguratorType) : MethodModel {
class FrontendConfiguratorTestModel(val frontendConfiguratorClass: KClass<out FrontendApiTestConfiguratorService>) : MethodModel {
override val kind: MethodModel.Kind get() = FrontendConfiguratorTestModelKind
override val name: String get() = "getConfigurator"
override val dataString: String? get() = null
@@ -46,12 +44,7 @@ class FrontendConfiguratorTestModel(val frontendConfiguratorType: FrontendConfig
return buildList {
add(NotNull::class.java)
add(FrontendApiTestConfiguratorService::class.java)
add(frontendConfiguratorType.configuratorClass.java)
add(frontendConfiguratorClass.java)
}
}
enum class FrontendConfiguratorType(val configuratorClass: KClass<out FrontendApiTestConfiguratorService>) {
FIR(FirFrontendApiTestConfiguratorService::class),
FE10(KtFe10FrontendApiTestConfiguratorService::class);
}
}
@@ -5,16 +5,39 @@
package org.jetbrains.kotlin.generators.tests.analysis.api.dsl
import org.jetbrains.kotlin.analysis.api.descriptors.test.KtFe10FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.fir.utils.libraries.binary.LibraryFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.fir.utils.libraries.source.LibrarySourceFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.generators.TestGroup
import org.jetbrains.kotlin.generators.TestGroupSuite
import org.jetbrains.kotlin.generators.getDefaultSuiteTestClassName
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import kotlin.reflect.KClass
internal class FirAndFe10TestGroup(
val suite: TestGroupSuite,
val directory: String?
val directory: String?,
val testModuleKinds: List<TestModuleKind>,
)
internal sealed class TestModuleKind(val componentName: String) {
object SOURCE : TestModuleKind("source")
object LIBRARY : TestModuleKind("library")
object LIBRARY_SOURCE : TestModuleKind("librarySource")
companion object {
val SOURCES_ONLY = listOf(SOURCE)
val SOURCES_AND_LIBRARIES_SOURCES = listOf(SOURCE, LIBRARY_SOURCE)
}
}
internal enum class Frontend(val prefix: String) {
FIR("Fir"),
FE10("Fe10"),
}
internal fun FirAndFe10TestGroup.test(
baseClass: KClass<*>,
generateFe10: Boolean = true,
@@ -22,14 +45,14 @@ internal fun FirAndFe10TestGroup.test(
) {
analysisApiTest(
"analysis/analysis-api-fir/tests",
FrontendConfiguratorTestModel.FrontendConfiguratorType.FIR,
Frontend.FIR,
baseClass,
init
)
if (generateFe10) {
analysisApiTest(
"analysis/analysis-api-fe10/tests",
FrontendConfiguratorTestModel.FrontendConfiguratorType.FE10,
Frontend.FE10,
baseClass,
init
)
@@ -40,54 +63,95 @@ internal fun FirAndFe10TestGroup.test(
internal fun TestGroupSuite.test(
baseClass: KClass<*>,
addFe10: Boolean = true,
testModuleKinds: List<TestModuleKind> = TestModuleKind.SOURCES_ONLY,
init: TestGroup.TestClass.() -> Unit,
) {
FirAndFe10TestGroup(this, directory = null).test(baseClass, addFe10, init)
FirAndFe10TestGroup(this, directory = null, testModuleKinds).test(baseClass, addFe10, init)
}
internal fun TestGroupSuite.group(
directory: String,
testModuleKinds: List<TestModuleKind> = TestModuleKind.SOURCES_ONLY,
init: FirAndFe10TestGroup.() -> Unit,
) {
FirAndFe10TestGroup(this, directory).init()
FirAndFe10TestGroup(this, directory, testModuleKinds).init()
}
internal fun TestGroupSuite.component(
directory: String,
moduleKinds: List<TestModuleKind> = TestModuleKind.SOURCES_ONLY,
init: FirAndFe10TestGroup.() -> Unit,
) {
group("components/$directory", init)
group("components/$directory", moduleKinds, init)
}
private fun FirAndFe10TestGroup.analysisApiTest(
testRoot: String,
frontendConfiguratorType: FrontendConfiguratorTestModel.FrontendConfiguratorType,
frontend: Frontend,
testClass: KClass<*>,
init: TestGroup.TestClass.() -> Unit,
) {
with(suite) {
val fullTestPath = "analysis/analysis-api/testData" + directory?.let { "/$it" }.orEmpty()
testGroup(testRoot, fullTestPath) {
val prefix = when (frontendConfiguratorType) {
FrontendConfiguratorTestModel.FrontendConfiguratorType.FIR -> "Fir"
FrontendConfiguratorTestModel.FrontendConfiguratorType.FE10 -> "Fe10"
}
val fullPackage = getPackageName(prefix, testClass)
testClass(
testClass,
suiteTestClassName = fullPackage + prefix + getDefaultSuiteTestClassName(testClass.java.simpleName),
useJunit4 = false
) {
method(FrontendConfiguratorTestModel(frontendConfiguratorType))
init()
if (testModuleKinds.size == 1) {
analysisApiTestClass(frontend, moduleKind = TestModuleKind.SOURCE, testClass, prefixNeeded = false, init)
} else {
testModuleKinds.forEach { component ->
analysisApiTestClass(frontend, component, testClass, prefixNeeded = true, init)
}
}
}
}
}
private fun TestGroup.analysisApiTestClass(
frontend: Frontend,
moduleKind: TestModuleKind,
testClass: KClass<*>,
prefixNeeded: Boolean,
init: TestGroup.TestClass.() -> Unit
) {
val fullPackage = getPackageName(frontend.prefix, testClass)
val suiteTestClassName = fullPackage +
frontend.prefix +
moduleKind.componentName.capitalizeAsciiOnly().takeIf { prefixNeeded }.orEmpty() +
getDefaultSuiteTestClassName(testClass.java.simpleName)
getDefaultSuiteTestClassName(testClass.java.simpleName)
val configurator = createConfigurator(frontend, moduleKind)
testClass(
testClass,
suiteTestClassName = suiteTestClassName,
useJunit4 = false
) {
method(FrontendConfiguratorTestModel(configurator))
init()
}
}
private fun createConfigurator(
frontend: Frontend,
moduleKind: TestModuleKind
): KClass<out FrontendApiTestConfiguratorService> = when (frontend) {
Frontend.FIR -> when (moduleKind) {
TestModuleKind.SOURCE -> FirFrontendApiTestConfiguratorService::class
TestModuleKind.LIBRARY -> LibraryFrontendApiTestConfiguratorService::class
TestModuleKind.LIBRARY_SOURCE -> LibrarySourceFrontendApiTestConfiguratorService::class
}
Frontend.FE10 -> when (moduleKind) {
TestModuleKind.SOURCE -> KtFe10FrontendApiTestConfiguratorService::class
TestModuleKind.LIBRARY -> TODO("TestModuleKind.LIBRARY is unsupported for fe10")
TestModuleKind.LIBRARY_SOURCE -> TODO("TestModuleKind.LIBRARY_SOURCE is unsupported for fe10")
}
}
private fun getPackageName(prefix: String, testClass: KClass<*>): String {
val basePrefix = "org.jetbrains.kotlin.analysis.api.${prefix.lowercase()}"
val packagePrefix = testClass.java.name