[analysis api, test] rework test infrastructure, add multimodule tests

This commit is contained in:
Ilya Kirillov
2022-03-24 22:34:31 +01:00
parent 1e5cf80278
commit 4bd7fed1ea
79 changed files with 1786 additions and 510 deletions
@@ -14,6 +14,7 @@ dependencies {
testApi(projectTests(":compiler:tests-common-new"))
testApi(project(":analysis:analysis-api-providers"))
testApi(project(":analysis:analysis-api"))
testApi(project(":analysis:analysis-api-standalone:analysis-api-standalone-base"))
testApi(project(":analysis:analysis-api-impl-barebone"))
}
@@ -24,6 +25,7 @@ sourceSets {
projectTest(jUnitMode = JUnitMode.JUnit5) {
dependsOn(":dist")
dependsOn(":plugins:fir-plugin-prototype:plugin-annotations:jar")
workingDir = rootDir
useJUnitPlatform()
}
@@ -8,8 +8,7 @@ package org.jetbrains.kotlin.analysis.test.framework.base
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.analyseInDependedAnalysisSession
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
@@ -17,8 +16,9 @@ import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractAnalysisApiBasedSingleModuleTest : AbstractAnalysisApiBasedTest() {
final override fun doTestByFileStructure(ktFiles: List<KtFile>, moduleStructure: TestModuleStructure, testServices: TestServices) {
final override fun doTestByFileStructure(moduleStructure: TestModuleStructure, testServices: TestServices) {
val singleModule = moduleStructure.modules.single()
val ktFiles = testServices.ktModuleProvider.getModuleFiles(singleModule).filterIsInstance<KtFile>()
doTestByFileStructure(ktFiles, singleModule, testServices)
}
@@ -5,9 +5,6 @@
package org.jetbrains.kotlin.analysis.test.framework.base
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Disposer
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testFramework.TestDataFile
@@ -15,17 +12,13 @@ import junit.framework.ComparisonFailure
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.analyseInDependedAnalysisSession
import org.jetbrains.kotlin.analysis.test.framework.AnalysisApiTestConfigurator
import org.jetbrains.kotlin.analysis.test.framework.AnalysisApiTestDirectives
import org.jetbrains.kotlin.analysis.test.framework.TestWithDisposable
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KotlinProjectStructureProviderTestImpl
import org.jetbrains.kotlin.analysis.test.framework.project.structure.TestKtModuleProvider
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.analysis.test.framework.services.ExpressionMarkerProvider
import org.jetbrains.kotlin.analysis.test.framework.services.ExpressionMarkersSourceFilePreprocessor
import org.jetbrains.kotlin.analysis.test.framework.utils.SkipTestException
import org.jetbrains.kotlin.analysis.test.framework.project.structure.getKtFilesFromModule
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoots
import org.jetbrains.kotlin.analysis.test.framework.services.libraries.LibraryWasNotCompiledDueToExpectedCompilationError
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
@@ -45,7 +38,6 @@ import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurat
import org.jetbrains.kotlin.test.services.impl.TemporaryDirectoryManagerImpl
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestInfo
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
@@ -55,7 +47,6 @@ abstract class AbstractAnalysisApiBasedTest : TestWithDisposable() {
abstract val configurator: AnalysisApiTestConfigurator
private lateinit var testInfo: KotlinTestInfo
private set
protected lateinit var testDataPath: Path
private set
@@ -63,12 +54,10 @@ abstract class AbstractAnalysisApiBasedTest : TestWithDisposable() {
private lateinit var moduleStructure: TestModuleStructure
protected open fun configureTest(builder: TestConfigurationBuilder) {
with(configurator) {
builder.configureTest(disposable)
}
configurator.configureTest(builder, disposable)
}
protected abstract fun doTestByFileStructure(ktFiles: List<KtFile>, moduleStructure: TestModuleStructure, testServices: TestServices)
protected abstract fun doTestByFileStructure(moduleStructure: TestModuleStructure, testServices: TestServices)
protected fun AssertionsService.assertEqualsToTestDataFileSibling(actual: String, extension: String = ".txt") {
val testPrefix = configurator.testPrefix
@@ -123,10 +112,14 @@ abstract class AbstractAnalysisApiBasedTest : TestWithDisposable() {
useSourcePreprocessor(::ExpressionMarkersSourceFilePreprocessor)
useAdditionalService { ExpressionMarkerProvider() }
registerAnalysisApiBaseTestServices(disposable, configurator)
useAdditionalService(::TestKtModuleProvider)
useAdditionalService<ApplicationDisposableProvider> { ExecutionListenerBasedDisposableProvider() }
useAdditionalService<KotlinStandardLibrariesPathProvider> { StandardLibrariesPathProviderForKotlinProject }
useDirectives(AnalysisApiTestDirectives)
configureTest(this)
startingArtifactFactory = { ResultingArtifact.Source() }
@@ -145,59 +138,31 @@ abstract class AbstractAnalysisApiBasedTest : TestWithDisposable() {
val testConfiguration = testConfiguration(path, configure)
Disposer.register(disposable, testConfiguration.rootDisposable)
val testServices = testConfiguration.testServices
val moduleStructure = testConfiguration.moduleStructureExtractor.splitTestDataByModules(
path,
testConfiguration.directives,
)
val moduleStructure =
testConfiguration.moduleStructureExtractor.splitTestDataByModules(path, testConfiguration.directives)
testServices.register(TestModuleStructure::class, moduleStructure)
this.moduleStructure = moduleStructure
val singleModule = moduleStructure.modules.single()
val project = try {
testServices.compilerConfigurationProvider.getProject(singleModule)
} catch (_: SkipTestException) {
testConfiguration.testServices.register(TestModuleStructure::class, moduleStructure)
try {
testConfiguration.preAnalysisHandlers.forEach { preprocessor -> preprocessor.preprocessModuleStructure(moduleStructure) }
} catch(ignored: LibraryWasNotCompiledDueToExpectedCompilationError) {
return
}
registerApplicationServices()
testConfiguration.testServices.register(TestModuleStructure::class, moduleStructure)
testConfiguration.preAnalysisHandlers.forEach { preprocessor ->
try {
preprocessor.preprocessModuleStructure(moduleStructure)
} catch (exception: Throwable) {
when (handleInitializationError(exception, moduleStructure)) {
InitializationErrorAction.IGNORE -> {}
InitializationErrorAction.THROW -> throw exception
}
}
preprocessor.prepareSealedClassInheritors(moduleStructure)
}
val ktFiles = getKtFilesFromModule(testServices, singleModule)
with(project as MockProject) {
val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(singleModule)
compilerConfiguration.addJavaSourceRoots(ktFiles.map { File(it.virtualFilePath) })
configurator.registerProjectServices(
this,
compilerConfiguration,
ktFiles,
testServices.compilerConfigurationProvider.getPackagePartProviderFactory(singleModule),
KotlinProjectStructureProviderTestImpl(testServices)
)
moduleStructure.modules.forEach { module ->
val files = testServices.ktModuleProvider.getModuleFiles(module)
configurator.prepareFilesInModule(files, module, testServices)
}
testConfiguration.preAnalysisHandlers.forEach { preprocessor ->
try {
preprocessor.prepareSealedClassInheritors(moduleStructure)
} catch (exception: Throwable) {
when (handleInitializationError(exception, moduleStructure)) {
InitializationErrorAction.IGNORE -> {}
InitializationErrorAction.THROW -> throw exception
}
}
}
configurator.prepareTestFiles(ktFiles, singleModule, testServices)
doTestByFileStructure(ktFiles, moduleStructure, testServices)
doTestByFileStructure(moduleStructure, testServices)
}
protected fun <R> analyseForTest(contextElement: KtElement, action: KtAnalysisSession.() -> R): R {
return if (configurator.analyseInDependentSession
&& AnalysisApiTestDirectives.DISABLE_DEPENDED_MODE !in this.moduleStructure.allDirectives
@@ -210,13 +175,6 @@ abstract class AbstractAnalysisApiBasedTest : TestWithDisposable() {
}
}
private fun registerApplicationServices() {
val application = ApplicationManager.getApplication() as MockApplication
KotlinCoreEnvironment.underApplicationLock {
configurator.registerApplicationServices(application)
}
}
@BeforeEach
fun initTestInfo(testInfo: TestInfo) {
this.testInfo = KotlinTestInfo(
@@ -11,7 +11,7 @@ import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractAnalysisApiSingleFileTest : AbstractAnalysisApiBasedSingleModuleTest() {
final override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val singleFile = ktFiles.singleOrNull() ?: ktFiles.first { it.name == "main.kt" }
val singleFile = ktFiles.first()
doTestByFileStructure(singleFile, module, testServices)
}
@@ -0,0 +1,28 @@
/*
* 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.base
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.analysis.test.framework.project.structure.AnalysisApiKtModuleProvider
import org.jetbrains.kotlin.analysis.test.framework.project.structure.AnalysisApiKtModuleProviderImpl
import org.jetbrains.kotlin.analysis.test.framework.services.*
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.bind
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
@OptIn(TestInfrastructureInternals::class)
fun TestConfigurationBuilder.registerAnalysisApiBaseTestServices(
testDisposable: Disposable,
configurator: AnalysisApiTestConfigurator,
) {
useAdditionalService<TestDisposableProvider>(::TestDisposableProviderImpl)
useAdditionalService<AnalysisApiKtModuleProvider>(::AnalysisApiKtModuleProviderImpl)
useAdditionalService<AnalysisApiEnvironmentManager>(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable))
useCustomCompilerConfigurationProvider(::AnalysisApiTestCompilerConfiguratorProvider)
usePreAnalysisHandlers(::ProjectStructureInitialisationPreAnalysisHandler.bind(configurator))
}
@@ -0,0 +1,60 @@
/*
* 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.psi.PsiFile
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
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
abstract class AnalysisApiKtModuleProvider : TestService {
protected abstract val testServices: TestServices
abstract fun getModule(moduleName: String): KtModule
abstract fun getModuleFiles(module: TestModule): List<PsiFile>
abstract fun registerProjectStructure(modules: List<KtModuleWithFiles>)
protected abstract fun getModuleName(ktModule: KtModule): String
abstract fun getAllModules(): List<KtModuleWithFiles>
}
class AnalysisApiKtModuleProviderImpl(
override val testServices: TestServices,
) : AnalysisApiKtModuleProvider() {
private lateinit var modules: List<KtModuleWithFiles>
private lateinit var modulesByName: Map<String, KtModuleWithFiles>
override fun getModule(moduleName: String): KtModule {
return modulesByName.getValue(moduleName).ktModule
}
override fun getModuleFiles(module: TestModule): List<PsiFile> = modulesByName.getValue(module.name).files
override fun registerProjectStructure(modules: List<KtModuleWithFiles>) {
require(!this::modules.isInitialized)
require(!this::modulesByName.isInitialized)
this.modules = modules
this.modulesByName = modules.associateBy { getModuleName(it.ktModule) }
}
override fun getModuleName(ktModule: KtModule): String = when (ktModule) {
is KtLibraryModule -> ktModule.libraryName
is KtSdkModule -> ktModule.sdkName
is KtLibrarySourceModule -> ktModule.libraryName
is KtSourceModule -> ktModule.moduleName
is KtNotUnderContentRootModule -> TODO()
}
override fun getAllModules(): List<KtModuleWithFiles> = modules
}
val TestServices.ktModuleProvider: AnalysisApiKtModuleProvider by TestServices.testServiceAccessor()
@@ -0,0 +1,7 @@
/*
* 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
@@ -1,25 +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.psi.PsiElement
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.services.TestServices
class KotlinProjectStructureProviderTestImpl(private val testServices: TestServices) : ProjectStructureProvider() {
private val moduleInfoProvider = testServices.projectModuleProvider
override fun getKtModuleForKtElement(element: PsiElement): KtModule {
val containingFile = element.containingFile as KtFile
return moduleInfoProvider.getModuleInfoByKtFile(containingFile) as KtModule
}
override fun getKtLibraryModules(): Collection<TestKtLibraryModule> {
return moduleInfoProvider.getLibraryModules()
}
}
@@ -1,35 +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 org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
class TestKtModuleProvider(
private val testServices: TestServices
) : TestService {
private val cache = mutableMapOf<String, TestKtModule>()
fun registerModuleInfo(testModule: TestModule, ktModule: TestKtModule) {
cache[testModule.name] = ktModule
}
fun getModuleInfoByKtFile(ktFile: KtFile): TestKtModule =
cache.values.first { moduleSourceInfo ->
(if (ktFile.isPhysical) ktFile else ktFile.originalFile) in moduleSourceInfo.ktFiles
}
fun getModule(moduleName: String): TestKtModule =
cache.getValue(moduleName)
fun getLibraryModules(): Collection<TestKtLibraryModule> {
return cache.values.filterIsInstance<TestKtLibraryModule>()
}
}
val TestServices.projectModuleProvider: TestKtModuleProvider by TestServices.testServiceAccessor()
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.analysis.test.framework.project.structure
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.ProjectScope
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
@@ -21,7 +22,6 @@ 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.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
@@ -34,10 +34,10 @@ import java.nio.file.Paths
abstract class TestKtModule(
val project: Project,
val testModule: TestModule,
val ktFiles: Set<KtFile>,
val psiFiles: List<PsiFile>,
testServices: TestServices,
) {
private val moduleProvider = testServices.projectModuleProvider
private val moduleProvider = testServices.ktModuleProvider
private val compilerConfigurationProvider = testServices.compilerConfigurationProvider
private val configuration = compilerConfigurationProvider.getCompilerConfiguration(testModule)
@@ -47,7 +47,7 @@ abstract class TestKtModule(
val directRegularDependencies: List<KtModule> by lazy(LazyThreadSafetyMode.PUBLICATION) {
buildList {
testModule.allDependencies.mapTo(this) { moduleProvider.getModule(it.moduleName) as KtModule }
testModule.allDependencies.mapTo(this) { moduleProvider.getModule(it.moduleName) }
addIfNotNull(
libraryByRoots(
(configuration.jvmModularRoots + configuration.jvmClasspathRoots).map(File::toPath)
@@ -58,12 +58,12 @@ abstract class TestKtModule(
val directRefinementDependencies: List<KtModule> by lazy(LazyThreadSafetyMode.PUBLICATION) {
testModule.dependsOnDependencies
.map { moduleProvider.getModule(it.moduleName) as KtModule }
.map { moduleProvider.getModule(it.moduleName) }
}
val directFriendDependencies: List<KtModule> by lazy(LazyThreadSafetyMode.PUBLICATION) {
buildList {
testModule.friendDependencies.mapTo(this) { moduleProvider.getModule(it.moduleName) as KtModule }
testModule.friendDependencies.mapTo(this) { moduleProvider.getModule(it.moduleName) }
addIfNotNull(
libraryByRoots(configuration[JVMConfigurationKeys.FRIEND_PATHS].orEmpty().map(Paths::get))
)
@@ -94,40 +94,41 @@ abstract class TestKtModule(
class TestKtSourceModule(
project: Project,
testModule: TestModule,
val testFilesToKtFiles: Map<TestFile, KtFile>,
psiFiles: List<PsiFile>,
testServices: TestServices
) : TestKtModule(project, testModule, testFilesToKtFiles.values.toSet(), testServices), KtSourceModule {
) : TestKtModule(project, testModule, psiFiles, testServices), KtSourceModule {
override val ktModule: KtModule get() = this
override val contentScope: GlobalSearchScope =
TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, testFilesToKtFiles.values)
TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, psiFiles.filterIsInstance<KtFile>())
}
class TestKtLibraryModule(
project: Project,
testModule: TestModule,
ktFiles: Collection<KtFile>,
psiFiles: List<PsiFile>,
private val binaryRoots: List<Path>,
testServices: TestServices
) : TestKtModule(project, testModule, ktFiles.toSet(), testServices), KtLibraryModule {
) : TestKtModule(project, testModule, psiFiles, testServices), KtLibraryModule {
override val ktModule: KtModule get() = this
override val libraryName: String get() = testModule.name
override val librarySources: KtLibrarySourceModule? get() = null
override fun getBinaryRoots(): Collection<Path> = ktFiles.map { it.virtualFile.toNioPath() }
override fun getBinaryRoots(): Collection<Path> = binaryRoots
override val contentScope: GlobalSearchScope =
GlobalSearchScope.filesScope(project, ktFiles.map { it.virtualFile })
GlobalSearchScope.filesScope(project, psiFiles.map { it.virtualFile })
}
class TestKtLibrarySourceModule(
project: Project,
testModule: TestModule,
ktFilesFromSourceJar: Set<KtFile>,
psiFiles: List<PsiFile>,
testServices: TestServices,
override val binaryLibrary: KtLibraryModule,
) : TestKtModule(project, testModule, ktFilesFromSourceJar, testServices), KtLibrarySourceModule {
) : TestKtModule(project, testModule, psiFiles, testServices), KtLibrarySourceModule {
override val ktModule: KtModule get() = this
override val contentScope: GlobalSearchScope get() = GlobalSearchScope.filesScope(project, ktFiles.map { it.virtualFile })
override val contentScope: GlobalSearchScope get() = GlobalSearchScope.filesScope(project, psiFiles.map { it.virtualFile })
override val libraryName: String get() = testModule.name
}
@@ -5,16 +5,52 @@
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.TestServices
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.util.KtTestUtil
fun getKtFilesFromModule(testServices: TestServices, testModule: TestModule): List<KtFile> {
val moduleInfoProvider = testServices.projectModuleProvider
val moduleInfoProvider = testServices.ktModuleProvider
return when (val moduleInfo = moduleInfoProvider.getModule(testModule.name)) {
is TestKtSourceModule -> moduleInfo.testFilesToKtFiles.filterKeys { testFile -> !testFile.isAdditional }.values.toList()
is TestKtLibraryModule -> moduleInfo.ktFiles.toList()
is TestKtLibrarySourceModule -> moduleInfo.ktFiles.toList()
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
)
}
@@ -0,0 +1,80 @@
/*
* 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.services
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.Application
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.test.ApplicationEnvironmentDisposer
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import org.jetbrains.kotlin.test.services.moduleStructure
abstract class AnalysisApiEnvironmentManager : TestService {
abstract val testServices: TestServices
abstract val testRootDisposable: Disposable
abstract fun initializeEnvironment()
fun getProject(): Project =
getProjectEnvironment().project
fun getApplication(): Application =
getApplicationEnvironment().application
abstract fun initializeProjectStructure()
abstract fun getProjectEnvironment(): KotlinCoreProjectEnvironment
abstract fun getApplicationEnvironment(): KotlinCoreApplicationEnvironment
}
class AnalysisApiEnvironmentManagerImpl(
override val testServices: TestServices,
override val testRootDisposable: Disposable,
) : AnalysisApiEnvironmentManager() {
private val _projectEnvironment: KotlinCoreProjectEnvironment by lazy {
StandaloneProjectFactory.createProjectEnvironment(
testRootDisposable,
ApplicationEnvironmentDisposer.ROOT_DISPOSABLE
)
}
override fun initializeEnvironment() {
testServices.disposableProvider.registerDisposables(
projectDisposable = _projectEnvironment.parentDisposable,
applicationDisposable = _projectEnvironment.environment.parentDisposable,
)
}
override fun initializeProjectStructure() {
val ktModules = testServices.ktModuleProvider.getAllModules()
val useSiteModule = testServices.moduleStructure.modules.first()
val useSiteCompilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(useSiteModule)
StandaloneProjectFactory.registerServicesForProjectEnvironment(
_projectEnvironment,
ktModules,
useSiteCompilerConfiguration.languageVersionSettings,
useSiteCompilerConfiguration.get(JVMConfigurationKeys.JDK_HOME)?.toPath(),
)
testServices.compilerConfigurationProvider.registerCompilerExtensions(getProject(), useSiteModule)
}
override fun getProjectEnvironment(): KotlinCoreProjectEnvironment =
_projectEnvironment
override fun getApplicationEnvironment(): KotlinCoreApplicationEnvironment =
_projectEnvironment.environment as KotlinCoreApplicationEnvironment
}
val TestServices.environmentManager: AnalysisApiEnvironmentManager by TestServices.testServiceAccessor()
@@ -0,0 +1,68 @@
/*
* 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.services
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.compiler.*
import org.jetbrains.kotlin.cli.jvm.config.configureJdkClasspathRoots
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.*
class AnalysisApiTestCompilerConfiguratorProvider(
override val testServices: TestServices,
override val testRootDisposable: Disposable,
override val configurators: List<AbstractEnvironmentConfigurator>
) : CompilerConfigurationProvider() {
private val configurationCache: MutableMap<TestModule, CompilerConfiguration> = mutableMapOf()
private val allProjectBinaryRoots by lazy {
StandaloneProjectFactory.getAllBinaryRoots(
testServices.ktModuleProvider.getAllModules().map { it.ktModule },
testServices.environmentManager.getProjectEnvironment()
)
}
override fun getProject(module: TestModule): Project {
return testServices.environmentManager.getProject()
}
override fun getCompilerConfiguration(module: TestModule): CompilerConfiguration {
return configurationCache.getOrPut(module) {
createKotlinCompilerConfiguration(module)
}
}
override fun getPackagePartProviderFactory(module: TestModule): (GlobalSearchScope) -> JvmPackagePartProvider {
val configuration = getCompilerConfiguration(module)
return { scope ->
JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply {
addRoots(allProjectBinaryRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY))
(ModuleAnnotationsResolver
.getInstance(testServices.environmentManager.getProject()) as CliModuleAnnotationsResolver)
.addPackagePartProvider(this)
}
}
}
override fun getKotlinCoreEnvironment(module: TestModule): KotlinCoreEnvironment {
error("Should not be called")
}
@OptIn(TestInfrastructureInternals::class)
private fun createKotlinCompilerConfiguration(module: TestModule): CompilerConfiguration {
return createCompilerConfiguration(module, configurators)
}
}
@@ -0,0 +1,47 @@
/*
* 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.services
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
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 ProjectStructureInitialisationPreAnalysisHandler(
testServices: TestServices,
private val configurator: AnalysisApiTestConfigurator,
) : PreAnalysisHandler(testServices) {
override fun preprocessModuleStructure(moduleStructure: TestModuleStructure) {
checkAllModulesHaveTheSameProject(moduleStructure)
testServices.environmentManager.initializeEnvironment()
val project = testServices.environmentManager.getProject() as MockProject
val application = testServices.environmentManager.getApplication() as MockApplication
configurator.registerApplicationServices(application, testServices)
createAndRegisterKtModules(moduleStructure, project)
configurator.registerProjectExtensionPoints(project, testServices)
testServices.environmentManager.initializeProjectStructure()
configurator.registerProjectServices(project, testServices)
}
private fun createAndRegisterKtModules(moduleStructure: TestModuleStructure, project: MockProject) {
val ktModules = configurator.createModules(moduleStructure, testServices, project)
testServices.ktModuleProvider.registerProjectStructure(ktModules)
}
private fun checkAllModulesHaveTheSameProject(moduleStructure: TestModuleStructure) {
val modules = moduleStructure.modules
val project = testServices.compilerConfigurationProvider.getProject(moduleStructure.modules.first()) as MockProject
check(modules.all { testServices.compilerConfigurationProvider.getProject(it) == project })
}
}
@@ -0,0 +1,37 @@
/*
* 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.services
import com.intellij.openapi.Disposable
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
abstract class TestDisposableProvider : TestService {
abstract val testServices: TestServices
abstract fun registerDisposables(projectDisposable: Disposable, applicationDisposable: Disposable)
abstract fun getProjectDisposable(): Disposable
abstract fun getApplicationDisposable(): Disposable
}
class TestDisposableProviderImpl(override val testServices: TestServices) : TestDisposableProvider() {
private lateinit var _projectDisposable: Disposable
private lateinit var _applicationDisposable: Disposable
override fun registerDisposables(projectDisposable: Disposable, applicationDisposable: Disposable) {
require(!this::_projectDisposable.isInitialized)
require(!this::_applicationDisposable.isInitialized)
this._projectDisposable = projectDisposable
this._applicationDisposable = applicationDisposable
}
override fun getProjectDisposable(): Disposable = _projectDisposable
override fun getApplicationDisposable(): Disposable = _applicationDisposable
}
val TestServices.disposableProvider: TestDisposableProvider by TestServices.testServiceAccessor()
@@ -21,9 +21,6 @@ class CompiledLibraryProvider(private val testServices: TestServices) : TestServ
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()
@@ -1,19 +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.services.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())
}
}
@@ -43,7 +43,7 @@ internal object TestModuleCompiler {
JarOutputStream(librarySourcesPath.outputStream(), manifest).use { jarOutputStream ->
for (testFile in module.files) {
val text = testServices.sourceFileProvider.getContentOfSourceFile(testFile)
addFileToJar(testFile.name, text, jarOutputStream)
addFileToJar(testFile.relativePath, text, jarOutputStream)
}
}
return librarySourcesPath
@@ -0,0 +1,87 @@
/*
* 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.test.configurators
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
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.psi.KtFile
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import java.nio.file.Path
import java.util.concurrent.locks.ReadWriteLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.withLock
import kotlin.reflect.KClass
abstract class AnalysisApiTestConfigurator {
open val testPrefix: String? get() = null
abstract val analyseInDependentSession: Boolean
abstract fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable)
abstract val serviceRegistrars: List<AnalysisApiTestServiceRegistrar>
open fun prepareFilesInModule(files: List<PsiFile>, module: TestModule, testServices: TestServices) {}
abstract fun doOutOfBlockModification(file: KtFile)
open fun preprocessTestDataPath(path: Path): Path = path
abstract fun createModules(moduleStructure: TestModuleStructure, testServices: TestServices, project: Project): List<KtModuleWithFiles>
fun registerProjectExtensionPoints(project: MockProject, testServices: TestServices) {
serviceRegistrars.forEach { it.registerProjectExtensionPoints(project, testServices) }
}
fun registerProjectServices(project: MockProject, testServices: TestServices) {
serviceRegistrars.forEach { it.registerProjectServices(project, testServices) }
}
fun registerApplicationServices(application: MockApplication, testServices: TestServices) {
ApplicationServiceRegistrar.register(application, serviceRegistrars, testServices)
}
}
private object ApplicationServiceRegistrar {
fun register(application: MockApplication, registrars: List<AnalysisApiTestServiceRegistrar>, testServices: TestServices) {
for (registrar in registrars) {
val registered = application.lock.readLock().withLock {
application.serviceRegistered[registrar::class] == true
}
if (registered) {
continue
}
application.lock.writeLock().withLock {
registrar.registerApplicationServices(application, testServices)
application.serviceRegistered[registrar::class] = true
}
}
}
private val Application.lock
by NotNullableUserDataProperty<Application, ReadWriteLock>(
Key("TestApplicationServicesRegistrarLock"),
ReentrantReadWriteLock(),
)
private var Application.serviceRegistered
by NotNullableUserDataProperty<Application, MutableMap<KClass<out AnalysisApiTestServiceRegistrar>, Boolean>>(
Key("TestApplicationServicesRegistered"),
mutableMapOf(),
)
}
@@ -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.analysis.test.framework
package org.jetbrains.kotlin.analysis.test.framework.test.configurators
abstract class AnalysisApiTestConfiguratorFactory {
abstract fun createConfigurator(data: AnalysisApiTestConfiguratorFactoryData): AnalysisApiTestConfigurator
@@ -0,0 +1,18 @@
/*
* 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.test.configurators
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import org.jetbrains.kotlin.test.services.TestServices
abstract class AnalysisApiTestServiceRegistrar {
abstract fun registerProjectExtensionPoints(project: MockProject, testServices: TestServices)
abstract fun registerProjectServices(project: MockProject, testServices: TestServices)
abstract fun registerApplicationServices(application: MockApplication, testServices: TestServices)
}