[AA] Support multi-module tests with mixed KtModule kinds

- This commit adds a `MODULE_KIND` directive to Analysis API tests which
  can be used to change a test module's `KtModule` kind from the default
  determined by the test's registered `KtModuleFactory` (which in turn
  depends on the `moduleKind` configured during test generation).
- The most important use case is the ability to have multi-module tests
  where a main module references symbols from a binary library module.
  This use case requires source configurations to compile libraries,
  which requires additional setup. This will be implemented in a
  following commit.

^KT-64468 Fixed
This commit is contained in:
Marco Pennekamp
2023-12-20 00:40:25 +01:00
committed by Space Team
parent 69a2bc9abc
commit d3d21b3f34
17 changed files with 187 additions and 103 deletions
@@ -5,10 +5,12 @@
package org.jetbrains.kotlin.analysis.test.framework
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
object AnalysisApiTestDirectives : SimpleDirectivesContainer() {
val MODULE_KIND by enumDirective<TestModuleKind>("Overrides the kind of `KtModule` that is built from the associated test module")
val DISABLE_DEPENDED_MODE by directive("Analysis in dependent mode should not be run in this test")
val IGNORE_FE10 by directive("FE10 Analysis API implementation test should mot be run")
val IGNORE_FIR by directive("FIR Analysis API implementation test should mot be run")
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2023 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.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.test.framework.services.libraries.compiledLibraryProvider
import org.jetbrains.kotlin.analysis.test.framework.services.libraries.testModuleDecompiler
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
/**
* @see org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind.LibraryBinary
*/
object KtLibraryBinaryModuleFactory : KtModuleFactory {
override fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles {
val library = testServices.compiledLibraryProvider.compileToLibrary(testModule).artifact
val decompiledFiles = testServices.testModuleDecompiler.getAllPsiFilesFromLibrary(library, project)
return KtModuleWithFiles(
KtLibraryModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, decompiledFiles.mapTo(mutableSetOf()) { it.virtualFile }),
project,
binaryRoots = listOf(library),
librarySources = null,
),
decompiledFiles
)
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2023 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.api.impl.base.util.LibraryUtils
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.analysis.test.framework.services.libraries.compiledLibraryProvider
import java.nio.file.Path
/**
* @see org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind.LibrarySource
*/
object KtLibrarySourceModuleFactory : KtModuleFactory {
override fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles {
val (libraryJar, librarySourcesJar) = testServices.compiledLibraryProvider.compileToLibrary(testModule)
require(librarySourcesJar != null)
return createKtLibrarySourceModule(
libraryJar = libraryJar,
librarySourcesJar = librarySourcesJar,
testModule = testModule,
project = project,
)
}
}
fun createKtLibrarySourceModule(libraryJar: Path, librarySourcesJar: Path, testModule: TestModule, project: Project): KtModuleWithFiles {
val libraryKtModule = KtLibraryModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, LibraryUtils.getAllVirtualFilesFromJar(libraryJar)),
project,
binaryRoots = listOf(libraryJar),
librarySources = null,
)
val decompiledPsiFilesFromSourceJar = LibraryUtils.getAllPsiFilesFromJar(librarySourcesJar, project)
val librarySourceKtModule = KtLibrarySourceModuleImpl(
testModule.name,
testModule.targetPlatform,
GlobalSearchScope.filesScope(project, decompiledPsiFilesFromSourceJar.map { it.virtualFile }),
project,
binaryLibrary = libraryKtModule,
)
libraryKtModule.librarySources = librarySourceKtModule
return KtModuleWithFiles(librarySourceKtModule, decompiledPsiFilesFromSourceJar)
}
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.analysis.test.framework.project.structure
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtModuleWithFiles
import org.jetbrains.kotlin.analysis.test.framework.AnalysisApiTestDirectives
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
@@ -15,4 +17,26 @@ fun interface KtModuleFactory : TestService {
fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles
}
val TestServices.ktModuleFactory: KtModuleFactory by TestServices.testServiceAccessor()
private val TestServices.ktModuleFactory: KtModuleFactory by TestServices.testServiceAccessor()
/**
* Returns the appropriate [KtModuleFactory] to build a [KtModule][org.jetbrains.kotlin.analysis.project.structure.KtModule] for the given
* [testModule].
*
* By default, the [KtModuleFactory] registered with these [TestServices] is returned. It may be overruled by the
* [MODULE_KIND][org.jetbrains.kotlin.analysis.test.framework.AnalysisApiTestDirectives.MODULE_KIND] directive for a specific test module.
*/
fun TestServices.getKtModuleFactoryForTestModule(testModule: TestModule): KtModuleFactory {
val explicitKinds = testModule.directives[AnalysisApiTestDirectives.MODULE_KIND]
if (explicitKinds.size > 1) {
throw IllegalArgumentException("A test module may only specify one `${AnalysisApiTestDirectives.MODULE_KIND.name}`.")
}
return when (explicitKinds.singleOrNull()) {
TestModuleKind.Source -> KtSourceModuleFactory
TestModuleKind.LibraryBinary -> KtLibraryBinaryModuleFactory
TestModuleKind.LibrarySource -> KtLibrarySourceModuleFactory
TestModuleKind.ScriptSource -> KtScriptModuleFactory
else -> ktModuleFactory
}
}
@@ -11,7 +11,10 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
class KtScriptModuleFactory : KtModuleFactory {
/**
* @see org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind.ScriptSource
*/
object KtScriptModuleFactory : KtModuleFactory {
override fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles {
val ktFile = TestModuleStructureFactory.createSourcePsiFiles(testModule, testServices, project).single() as KtFile
val module = KtScriptModuleImpl(
@@ -11,7 +11,10 @@ import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtMod
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
class KtSourceModuleFactory : KtModuleFactory {
/**
* @see org.jetbrains.kotlin.analysis.test.framework.test.configurators.TestModuleKind.Source
*/
object KtSourceModuleFactory : KtModuleFactory {
override fun createModule(testModule: TestModule, testServices: TestServices, project: Project): KtModuleWithFiles {
val psiFiles = TestModuleStructureFactory.createSourcePsiFiles(testModule, testServices, project)
@@ -25,4 +28,4 @@ class KtSourceModuleFactory : KtModuleFactory {
return KtModuleWithFiles(module, psiFiles)
}
}
}
@@ -43,8 +43,9 @@ object TestModuleStructureFactory {
testServices: TestServices,
project: Project
): KtModuleProjectStructure {
val moduleEntries = moduleStructure.modules
.map { testModule -> testServices.ktModuleFactory.createModule(testModule, testServices, project) }
val moduleEntries = moduleStructure.modules.map { testModule ->
testServices.getKtModuleFactoryForTestModule(testModule).createModule(testModule, testServices, project)
}
val moduleEntriesByName = moduleEntries.associateByName()
@@ -48,10 +48,3 @@ enum class FrontendKind(val suffix: String) {
Fir("Fir"),
Fe10("Fe10"),
}
enum class TestModuleKind(val suffix: String) {
Source("Source"),
LibraryBinary("LibraryBinary"),
LibrarySource("LibrarySource"),
ScriptSource("ScriptSource"),
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2023 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
/**
* In an Analysis API test configuration, the [TestModuleKind] determines the kind of the default
* [KtModule][org.jetbrains.kotlin.analysis.project.structure.KtModule]s used in the test. This essentially defines the context in which a
* test file is analyzed.
*
* The test module kind can also be overridden for a specific test module in multi-module tests using the
* [MODULE_KIND][org.jetbrains.kotlin.analysis.test.framework.AnalysisApiTestDirectives.MODULE_KIND] directive. This allows e.g. source
* module tests to refer to binary library dependencies.
*/
enum class TestModuleKind(val suffix: String) {
/**
* @see org.jetbrains.kotlin.analysis.test.framework.project.structure.KtSourceModuleFactory
*/
Source("Source"),
/**
* @see org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibraryBinaryModuleFactory
*/
LibraryBinary("LibraryBinary"),
/**
* @see org.jetbrains.kotlin.analysis.test.framework.project.structure.KtLibrarySourceModuleFactory
*/
LibrarySource("LibrarySource"),
/**
* @see org.jetbrains.kotlin.analysis.test.framework.project.structure.KtScriptModuleFactory
*/
ScriptSource("ScriptSource"),
}