[analysis api fir] put every library into a separate KtModule in tests

This commit is contained in:
Ilya Kirillov
2022-04-07 08:09:00 +02:00
parent 252d8e283b
commit 2649788b87
43 changed files with 778 additions and 399 deletions
@@ -9,6 +9,7 @@ dependencies {
testApiJUnit5()
testApi(project(":kotlin-test:kotlin-test-junit"))
testImplementation(project(":analysis:analysis-internal-utils"))
testApi(project(":compiler:psi"))
testApi(project(":analysis:kt-references"))
testApi(projectTests(":compiler:tests-common-new"))
@@ -104,10 +104,7 @@ abstract class AbstractAnalysisApiBasedTest : TestWithDisposable() {
targetPlatform = JvmPlatforms.defaultJvmPlatform
dependencyKind = DependencyKind.Source
}
useConfigurators(
::CommonEnvironmentConfigurator,
::JvmEnvironmentConfigurator,
)
assertions = JUnit5Assertions
useAdditionalService<TemporaryDirectoryManager>(::TemporaryDirectoryManagerImpl)
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.analysis.test.framework.project.structure
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.test.model.TestModule
@@ -18,17 +19,17 @@ abstract class AnalysisApiKtModuleProvider : TestService {
abstract fun getModuleFiles(module: TestModule): List<PsiFile>
abstract fun registerProjectStructure(modules: List<KtModuleWithFiles>)
abstract fun registerProjectStructure(modules: KtModuleProjectStructure)
protected abstract fun getModuleName(ktModule: KtModule): String
abstract fun getAllModules(): List<KtModuleWithFiles>
abstract fun getModuleStructure(): KtModuleProjectStructure
}
class AnalysisApiKtModuleProviderImpl(
override val testServices: TestServices,
) : AnalysisApiKtModuleProvider() {
private lateinit var modules: List<KtModuleWithFiles>
private lateinit var modulesStructure: KtModuleProjectStructure
private lateinit var modulesByName: Map<String, KtModuleWithFiles>
override fun getModule(moduleName: String): KtModule {
@@ -37,12 +38,12 @@ class AnalysisApiKtModuleProviderImpl(
override fun getModuleFiles(module: TestModule): List<PsiFile> = modulesByName.getValue(module.name).files
override fun registerProjectStructure(modules: List<KtModuleWithFiles>) {
require(!this::modules.isInitialized)
override fun registerProjectStructure(modules: KtModuleProjectStructure) {
require(!this::modulesStructure.isInitialized)
require(!this::modulesByName.isInitialized)
this.modules = modules
this.modulesByName = modules.associateBy { getModuleName(it.ktModule) }
this.modulesStructure = modules
this.modulesByName = modulesStructure.mainModules.associateBy { getModuleName(it.ktModule) }
}
override fun getModuleName(ktModule: KtModule): String = when (ktModule) {
@@ -54,7 +55,7 @@ class AnalysisApiKtModuleProviderImpl(
}
override fun getAllModules(): List<KtModuleWithFiles> = modules
override fun getModuleStructure(): KtModuleProjectStructure = modulesStructure
}
val TestServices.ktModuleProvider: AnalysisApiKtModuleProvider by TestServices.testServiceAccessor()
@@ -31,7 +31,7 @@ import java.nio.file.Path
import java.nio.file.Paths
@OptIn(ExperimentalStdlibApi::class)
abstract class TestKtModule(
abstract class KtModuleByCompilerConfiguration(
val project: Project,
val testModule: TestModule,
val psiFiles: List<PsiFile>,
@@ -91,25 +91,25 @@ abstract class TestKtModule(
get() = testModule.targetPlatform.getAnalyzerServices()
}
class TestKtSourceModule(
class KtSourceModuleByCompilerConfiguration(
project: Project,
testModule: TestModule,
psiFiles: List<PsiFile>,
testServices: TestServices
) : TestKtModule(project, testModule, psiFiles, testServices), KtSourceModule {
) : KtModuleByCompilerConfiguration(project, testModule, psiFiles, testServices), KtSourceModule {
override val ktModule: KtModule get() = this
override val contentScope: GlobalSearchScope =
TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, psiFiles.filterIsInstance<KtFile>())
}
class TestKtLibraryModule(
class KtLibraryModuleByCompilerConfiguration(
project: Project,
testModule: TestModule,
psiFiles: List<PsiFile>,
private val binaryRoots: List<Path>,
testServices: TestServices
) : TestKtModule(project, testModule, psiFiles, testServices), KtLibraryModule {
) : KtModuleByCompilerConfiguration(project, testModule, psiFiles, testServices), KtLibraryModule {
override val ktModule: KtModule get() = this
override val libraryName: String get() = testModule.name
override val librarySources: KtLibrarySourceModule? get() = null
@@ -120,13 +120,13 @@ class TestKtLibraryModule(
GlobalSearchScope.filesScope(project, psiFiles.map { it.virtualFile })
}
class TestKtLibrarySourceModule(
class KtLibrarySourceModuleByCompilerConfiguration(
project: Project,
testModule: TestModule,
psiFiles: List<PsiFile>,
testServices: TestServices,
override val binaryLibrary: KtLibraryModule,
) : TestKtModule(project, testModule, psiFiles, testServices), KtLibrarySourceModule {
) : KtModuleByCompilerConfiguration(project, testModule, psiFiles, testServices), KtLibrarySourceModule {
override val ktModule: KtModule get() = this
override val contentScope: GlobalSearchScope get() = GlobalSearchScope.filesScope(project, psiFiles.map { it.virtualFile })
@@ -0,0 +1,83 @@
/*
* 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.test.framework.project.structure
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.project.structure.*
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
import org.jetbrains.kotlin.test.frontend.fir.getAnalyzerServices
import java.nio.file.Path
interface KtModuleWithModifiableDependencies {
val directRegularDependencies: MutableList<KtModule>
val directRefinementDependencies: MutableList<KtModule>
val directFriendDependencies: MutableList<KtModule>
}
class KtSourceModuleImpl(
override val moduleName: String,
override val platform: TargetPlatform,
override val languageVersionSettings: LanguageVersionSettings,
override val project: Project,
override val contentScope: GlobalSearchScope,
) : KtSourceModule, KtModuleWithModifiableDependencies {
override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices()
override val directRegularDependencies: MutableList<KtModule> = mutableListOf()
override val directRefinementDependencies: MutableList<KtModule> = mutableListOf()
override val directFriendDependencies: MutableList<KtModule> = mutableListOf()
}
class KtJdkModuleImpl(
override val sdkName: String,
override val platform: TargetPlatform,
override val contentScope: GlobalSearchScope,
override val project: Project,
private val binaryRoots: Collection<Path>,
) : KtSdkModule, KtModuleWithModifiableDependencies {
override val analyzerServices: PlatformDependentAnalyzerServices
get() = platform.getAnalyzerServices()
override fun getBinaryRoots(): Collection<Path> = binaryRoots
override val directRegularDependencies: MutableList<KtModule> = mutableListOf()
override val directRefinementDependencies: MutableList<KtModule> = mutableListOf()
override val directFriendDependencies: MutableList<KtModule> = mutableListOf()
}
class KtLibraryModuleImpl(
override val libraryName: String,
override val platform: TargetPlatform,
override val contentScope: GlobalSearchScope,
override val project: Project,
private val binaryRoots: Collection<Path>,
override var librarySources: KtLibrarySourceModule?,
val isBuitinsContainingStdlib: Boolean,
) : KtLibraryModule, KtModuleWithModifiableDependencies {
override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices()
override fun getBinaryRoots(): Collection<Path> = binaryRoots
override val directRegularDependencies: MutableList<KtModule> = mutableListOf()
override val directRefinementDependencies: MutableList<KtModule> = mutableListOf()
override val directFriendDependencies: MutableList<KtModule> = mutableListOf()
}
class KtLibrarySourceModuleImpl(
override val libraryName: String,
override val platform: TargetPlatform,
override val contentScope: GlobalSearchScope,
override val project: Project,
override val binaryLibrary: KtLibraryModule,
) : KtLibrarySourceModule, KtModuleWithModifiableDependencies {
override val analyzerServices: PlatformDependentAnalyzerServices get() = platform.getAnalyzerServices()
override val directRegularDependencies: MutableList<KtModule> = mutableListOf()
override val directRefinementDependencies: MutableList<KtModule> = mutableListOf()
override val directFriendDependencies: MutableList<KtModule> = mutableListOf()
}
@@ -0,0 +1,249 @@
/*
* 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.test.framework.project.structure
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.ProjectScope
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure
import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.allDirectDependenciesOfType
import org.jetbrains.kotlin.analysis.test.framework.services.environmentManager
import org.jetbrains.kotlin.analysis.utils.errors.checkIsInstance
import org.jetbrains.kotlin.analysis.utils.errors.requireIsInstance
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.model.DependencyRelation
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator
import org.jetbrains.kotlin.test.util.KtTestUtil
import org.jetbrains.kotlin.utils.KotlinPaths
import org.jetbrains.kotlin.utils.PathUtil
import org.jetbrains.kotlin.utils.addIfNotNull
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension
fun interface KtMainModuleFactory {
fun createMainModule(
testModule: TestModule,
testServices: TestServices,
project: Project,
): KtModuleWithFiles
}
object KtMainModuleFactoryForSourceModules : KtMainModuleFactory {
override fun createMainModule(
testModule: TestModule,
testServices: TestServices,
project: Project,
): KtModuleWithFiles {
val psiFiles = TestModuleStructureFactory.createSourcePsiFiles(testModule, testServices, project)
return KtModuleWithFiles(
KtSourceModuleImpl(
testModule.name,
testModule.targetPlatform,
testModule.languageVersionSettings,
project,
GlobalSearchScope.filesScope(project, psiFiles.mapTo(mutableSetOf()) { it.virtualFile }),
),
psiFiles
)
}
}
object TestModuleStructureFactory {
@OptIn(TestInfrastructureInternals::class)
fun createProjectStructureByTestStructure(
moduleStructure: TestModuleStructure,
testServices: TestServices,
project: Project,
mainModuleFactory: KtMainModuleFactory
): KtModuleProjectStructure {
val modulesFromTestServices = moduleStructure.modules.map { testModule ->
testModule.toKtModule(testServices, project, mainModuleFactory)
}
val moduleByName = modulesFromTestServices.associateBy { getModuleName(it.ktModule) }
val binaryModulesBySourceRoots = mutableMapOf<Set<Path>, KtBinaryModule>()
moduleStructure.modules.forEach { testModule ->
val ktModule = moduleByName.getValue(testModule.name).ktModule
checkIsInstance<KtModuleWithModifiableDependencies>(ktModule)
addModuleDependencies(testModule, moduleByName, ktModule)
buildList {
addIfNotNull(getJdkModule(testModule, project))
addAll(getStdlibModules(testModule, project))
addAll(getLibraryModules(testServices, testModule, project))
addAll(createLibrariesByCompilerConfigurators(testModule, testServices, project))
}.forEach { library ->
val cachedLibrary = binaryModulesBySourceRoots.getOrPut(library.getBinaryRoots().toSet()) { library }
ktModule.directRegularDependencies.add(cachedLibrary)
}
}
return KtModuleProjectStructure(modulesFromTestServices, binaryModulesBySourceRoots.values) { module ->
module.allDirectDependenciesOfType<KtLibraryModuleImpl>().first { it.isBuitinsContainingStdlib }
}
}
@OptIn(TestInfrastructureInternals::class)
private fun createLibrariesByCompilerConfigurators(
testModule: TestModule,
testServices: TestServices,
project: Project
): List<KtLibraryModuleImpl> {
val compilerConfiguration = createCompilerConfiguration(testModule, testServices.environmentConfigurators)
val contentRoots = compilerConfiguration[CLIConfigurationKeys.CONTENT_ROOTS, emptyList()]
return contentRoots
.filterIsInstance<JvmClasspathRoot>()
.map { root -> createKtLibraryModuleByJar(root.file.toPath(), project, isBuitinsContainingStdlib = false) }
}
private fun getModuleName(ktModule: KtModule) = when (ktModule) {
is KtSourceModuleImpl -> ktModule.moduleName
is KtLibraryModuleImpl -> ktModule.libraryName
is KtLibrarySourceModuleImpl -> ktModule.libraryName
else -> error("Unknown module ${ktModule::class}")
}
private fun addModuleDependencies(
testModule: TestModule,
moduleByName: Map<String, KtModuleWithFiles>,
ktModule: KtModule
) {
requireIsInstance<KtModuleWithModifiableDependencies>(ktModule)
testModule.allDependencies.forEach { dependency ->
val dependencyKtModule = moduleByName.getValue(dependency.moduleName).ktModule
when (dependency.relation) {
DependencyRelation.RegularDependency -> ktModule.directRegularDependencies.add(dependencyKtModule)
DependencyRelation.FriendDependency -> ktModule.directFriendDependencies.add(dependencyKtModule)
DependencyRelation.DependsOnDependency -> ktModule.directRefinementDependencies.add(dependencyKtModule)
}
}
}
private fun getLibraryModules(
testServices: TestServices,
testModule: TestModule,
project: Project
): List<KtLibraryModuleImpl> {
val configurationKind = JvmEnvironmentConfigurator.extractConfigurationKind(testModule.directives)
return JvmEnvironmentConfigurator
.getLibraryFilesExceptRealRuntime( testServices, configurationKind, testModule.directives)
.map { it.toPath().toAbsolutePath() }
.map { jar ->
createKtLibraryModuleByJar(
jar,
project,
isBuitinsContainingStdlib = jar.nameWithoutExtension == "kotlin-stdlib-jvm-minimal-for-test"
)
}
}
private fun createKtLibraryModuleByJar(
jar: Path,
project: Project,
libraryName: String = jar.nameWithoutExtension,
isBuitinsContainingStdlib: Boolean = false,
): KtLibraryModuleImpl {
check(jar.extension == "jar")
check(jar.exists())
return KtLibraryModuleImpl(
libraryName,
JvmPlatforms.defaultJvmPlatform,
ProjectScope.getLibrariesScope(project),
project,
listOf(jar),
librarySources = null,
isBuitinsContainingStdlib,
)
}
private fun getStdlibModules(
testModule: TestModule,
project: Project
): List<KtLibraryModule> {
val configurationKind = JvmEnvironmentConfigurator.extractConfigurationKind(testModule.directives)
if (!configurationKind.withRuntime) return emptyList()
return listOf(
KotlinPaths.Jar.StdLib to PathUtil.KOTLIN_JAVA_STDLIB_NAME,
).map { (jar, name) ->
val lib = PathUtil.kotlinPathsForDistDirectory.jar(jar).toPath().toAbsolutePath()
createKtLibraryModuleByJar(lib, project, name, isBuitinsContainingStdlib = jar == KotlinPaths.Jar.StdLib)
}
}
private fun getJdkModule(
testModule: TestModule,
project: Project
): KtJdkModuleImpl? {
val jdkKind = JvmEnvironmentConfigurator.extractJdkKind(testModule.directives)
val jdkSourceRoots = buildList {
JvmEnvironmentConfigurator.getJdkHome(jdkKind)?.let { add(it.toPath()) }
JvmEnvironmentConfigurator.getJdkClasspathRoot(jdkKind)?.let { add(it.toPath()) }
}.mapTo(mutableListOf()) { it.toAbsolutePath() }
if (jdkSourceRoots.isEmpty()) return null
return KtJdkModuleImpl(
"jdk",
JvmPlatforms.defaultJvmPlatform,
ProjectScope.getLibrariesScope(project),
project,
jdkSourceRoots
)
}
private fun TestModule.toKtModule(
testServices: TestServices,
project: Project,
mainModuleFactory: KtMainModuleFactory,
): KtModuleWithFiles {
return mainModuleFactory.createMainModule(this, testServices, project)
}
fun createSourcePsiFiles(
testModule: TestModule,
testServices: TestServices,
project: Project
): List<PsiFile> {
return testModule.files.map { testFile ->
when {
testFile.isKtFile -> {
val fileText = testServices.sourceFileProvider.getContentOfSourceFile(testFile)
KtTestUtil.createFile(testFile.name, fileText, project)
}
testFile.isJavaFile -> {
val filePath = testServices.sourceFileProvider.getRealFileForSourceFile(testFile)
val virtualFile =
testServices.environmentManager.getApplicationEnvironment().localFileSystem.findFileByIoFile(filePath)
?: error("Virtual file not found for $filePath")
PsiManager.getInstance(project).findFile(virtualFile)
?: error("PsiFile file not found for $filePath")
}
else -> error("Unexpected file ${testFile.name}")
}
}
}
}
@@ -1,56 +0,0 @@
/*
* 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.test.framework.project.structure
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.test.framework.services.environmentManager
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.util.KtTestUtil
fun getKtFilesFromModule(testServices: TestServices, testModule: TestModule): List<KtFile> {
val moduleInfoProvider = testServices.ktModuleProvider
return when (val moduleInfo = moduleInfoProvider.getModule(testModule.name)) {
is TestKtSourceModule -> moduleInfo.psiFiles.filterIsInstance<KtFile>()
is TestKtLibraryModule -> moduleInfo.psiFiles.filterIsInstance<KtFile>()
is TestKtLibrarySourceModule -> moduleInfo.psiFiles.filterIsInstance<KtFile>()
else -> error("Unexpected $moduleInfo")
}
}
fun TestModuleStructure.toKtSourceModules(testServices: TestServices, project: Project) =
modules.map { testModule -> testModule.toKtSourceModule(testServices, project) }
fun TestModule.toKtSourceModule(testServices: TestServices, project: Project): KtModuleWithFiles {
val files = files.map { testFile ->
when {
testFile.isKtFile -> {
val fileText = testServices.sourceFileProvider.getContentOfSourceFile(testFile)
KtTestUtil.createFile(testFile.name, fileText, project)
}
testFile.isJavaFile -> {
val filePath = testServices.sourceFileProvider.getRealFileForSourceFile(testFile)
val virtualFile =
testServices.environmentManager.getApplicationEnvironment().localFileSystem.findFileByIoFile(filePath)
?: error("Virtual file not found for $filePath")
PsiManager.getInstance(project).findFile(virtualFile)
?: error("PsiFile file not found for $filePath")
}
else -> error("Unexpected file ${testFile.name}")
}
}
return KtModuleWithFiles(
TestKtSourceModule(project, this, files, testServices),
files
)
}
@@ -52,7 +52,7 @@ class AnalysisApiEnvironmentManagerImpl(
}
override fun initializeProjectStructure() {
val ktModules = testServices.ktModuleProvider.getAllModules()
val ktModules = testServices.ktModuleProvider.getModuleStructure()
val useSiteModule = testServices.moduleStructure.modules.first()
val useSiteCompilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(useSiteModule)
@@ -29,7 +29,7 @@ class AnalysisApiTestCompilerConfiguratorProvider(
private val allProjectBinaryRoots by lazy {
StandaloneProjectFactory.getAllBinaryRoots(
testServices.ktModuleProvider.getAllModules().map { it.ktModule },
testServices.ktModuleProvider.getModuleStructure().allKtModules(),
testServices.environmentManager.getProjectEnvironment()
)
}
@@ -12,7 +12,7 @@ import com.intellij.openapi.application.Application
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleProjectStructure
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
@@ -40,7 +40,7 @@ abstract class AnalysisApiTestConfigurator {
open fun preprocessTestDataPath(path: Path): Path = path
abstract fun createModules(moduleStructure: TestModuleStructure, testServices: TestServices, project: Project): List<KtModuleWithFiles>
abstract fun createModules(moduleStructure: TestModuleStructure, testServices: TestServices, project: Project): KtModuleProjectStructure
fun registerProjectExtensionPoints(project: MockProject, testServices: TestServices) {
serviceRegistrars.forEach { it.registerProjectExtensionPoints(project, testServices) }