[AA] Enable test-specific configuration of write access permissions

- In general, Analysis API tests forbid write access because the
  Analysis API should not be used from write actions. However, in some
  cases we might want tests to e.g. modify PSI, which requires write
  access. This change allows `AnalysisApiTestConfigurator`s to enable
  write access for the specific test.
This commit is contained in:
Marco Pennekamp
2023-11-13 15:17:59 +01:00
committed by Space Team
parent 6189d68c3c
commit 03a7162f37
7 changed files with 108 additions and 32 deletions
@@ -69,14 +69,15 @@ object StandaloneProjectFactory {
fun createProjectEnvironment(
projectDisposable: Disposable,
applicationDisposable: Disposable,
unitTestMode: Boolean = false,
applicationEnvironmentMode: KotlinCoreApplicationEnvironmentMode,
compilerConfiguration: CompilerConfiguration = CompilerConfiguration(),
classLoader: ClassLoader = MockProject::class.java.classLoader,
): KotlinCoreProjectEnvironment {
val applicationEnvironment = if (unitTestMode)
KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForTests(applicationDisposable, compilerConfiguration)
else
KotlinCoreEnvironment.getOrCreateApplicationEnvironmentForProduction(applicationDisposable, compilerConfiguration)
val applicationEnvironment = KotlinCoreEnvironment.getOrCreateApplicationEnvironment(
applicationDisposable,
compilerConfiguration,
applicationEnvironmentMode,
)
registerApplicationExtensionPoints(applicationEnvironment, applicationDisposable)
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.analysis.project.structure.impl.getPsiFilesFromPaths
import org.jetbrains.kotlin.analysis.project.structure.impl.getSourceFilePaths
import org.jetbrains.kotlin.analysis.providers.*
import org.jetbrains.kotlin.analysis.providers.impl.*
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironmentMode
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.setupIdeaStandaloneExecution
import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -61,7 +62,7 @@ public class StandaloneAnalysisAPISessionBuilder(
StandaloneProjectFactory.createProjectEnvironment(
projectDisposable,
applicationDisposable,
unitTestMode,
KotlinCoreApplicationEnvironmentMode.fromUnitTestModeFlag(unitTestMode),
classLoader = classLoader
)
@@ -26,7 +26,7 @@ fun TestConfigurationBuilder.registerAnalysisApiBaseTestServices(
) {
useAdditionalService<TestDisposableProvider>(::TestDisposableProviderImpl)
useAdditionalService<AnalysisApiKtModuleProvider>(::AnalysisApiKtModuleProviderImpl)
useAdditionalService<AnalysisApiEnvironmentManager>(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable))
useAdditionalService<AnalysisApiEnvironmentManager>(::AnalysisApiEnvironmentManagerImpl.bind(testDisposable, configurator))
useAdditionalService<ApplicationDisposableProvider> { ExecutionListenerBasedDisposableProvider() }
useAdditionalService<KotlinStandardLibrariesPathProvider> { StandardLibrariesPathProviderForKotlinProject }
@@ -12,7 +12,9 @@ import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.KtSta
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.project.structure.KtBuiltinsModule
import org.jetbrains.kotlin.analysis.test.framework.project.structure.ktModuleProvider
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironmentMode
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreProjectEnvironment
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
@@ -38,12 +40,13 @@ abstract class AnalysisApiEnvironmentManager : TestService {
class AnalysisApiEnvironmentManagerImpl(
override val testServices: TestServices,
override val testRootDisposable: Disposable,
private val configurator: AnalysisApiTestConfigurator,
) : AnalysisApiEnvironmentManager() {
private val _projectEnvironment: KotlinCoreProjectEnvironment by lazy {
StandaloneProjectFactory.createProjectEnvironment(
testRootDisposable,
testServices.applicationDisposableProvider.getApplicationRootDisposable(),
unitTestMode = true
KotlinCoreApplicationEnvironmentMode.UnitTest(configurator.isWriteAccessAllowed),
)
}
@@ -36,6 +36,15 @@ abstract class AnalysisApiTestConfigurator {
abstract val serviceRegistrars: List<AnalysisApiTestServiceRegistrar>
/**
* Whether the associated [core application environment][org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment] should
* permit write access.
*
* Write access is disabled by default because the Analysis API should not be used during write actions. Nevertheless, some tests may
* need write access, for example to modify PSI.
*/
open val isWriteAccessAllowed get() = false
open fun prepareFilesInModule(files: List<PsiFile>, module: TestModule, testServices: TestServices) {}
open fun doGlobalModuleStateModification(project: Project) {
@@ -24,13 +24,31 @@ import org.jetbrains.kotlin.cli.jvm.compiler.IdeaExtensionPoints.registerVersion
import org.jetbrains.kotlin.cli.jvm.compiler.jarfs.FastJarFileSystem
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
sealed interface KotlinCoreApplicationEnvironmentMode {
val isWriteAccessAllowed: Boolean
object Production : KotlinCoreApplicationEnvironmentMode {
override val isWriteAccessAllowed: Boolean get() = true
}
class UnitTest(override val isWriteAccessAllowed: Boolean) : KotlinCoreApplicationEnvironmentMode
companion object {
fun fromUnitTestModeFlag(isUnitTestMode: Boolean): KotlinCoreApplicationEnvironmentMode =
if (isUnitTestMode) UnitTest(isWriteAccessAllowed = false) else Production
}
}
class KotlinCoreApplicationEnvironment private constructor(
parentDisposable: Disposable, unitTestMode: Boolean
) : JavaCoreApplicationEnvironment(parentDisposable, unitTestMode) {
parentDisposable: Disposable,
private val environmentMode: KotlinCoreApplicationEnvironmentMode,
) : JavaCoreApplicationEnvironment(parentDisposable, environmentMode is KotlinCoreApplicationEnvironmentMode.UnitTest) {
init {
application.initializeEnvironmentMode(environmentMode)
registerApplicationService(JavaFileCodeStyleFacadeFactory::class.java, DummyJavaFileCodeStyleFacadeFactory())
registerFileType(JavaClassFileType.INSTANCE, "sig");
registerFileType(JavaClassFileType.INSTANCE, "sig")
}
override fun createJrtFileSystem(): VirtualFileSystem {
@@ -41,15 +59,11 @@ class KotlinCoreApplicationEnvironment private constructor(
val mock = super.createApplication(parentDisposable)
/**
* We can't use just [unitTestMode] from constructor, because at this moment
* the corresponding property is not yet initialized, so [unitTestMode] is effectively always false,
* because this function called from super class constructor
* We can't use [environmentMode] from the constructor to decide whether we're in unit test mode, because the corresponding property
* is not yet initialized when this function is called from the superclass constructor.
*/
return if (mock.isUnitTestMode) {
object : MockApplication(parentDisposable) {
override fun isUnitTestMode(): Boolean = true
override fun isWriteAccessAllowed(): Boolean = false
}
MockUnitTestApplication(parentDisposable)
} else {
mock
}
@@ -80,10 +94,22 @@ class KotlinCoreApplicationEnvironment private constructor(
}
companion object {
@Deprecated(
message = "The `unitTestMode` flag is deprecated in favor of `KotlinCoreApplicationEnvironmentMode` configuration.",
replaceWith = ReplaceWith("create(parentDisposable, KotlinCoreApplicationEnvironmentMode.fromUnitTestModeFlag(unitTestMode))"),
)
fun create(
parentDisposable: Disposable, unitTestMode: Boolean
parentDisposable: Disposable,
unitTestMode: Boolean,
): KotlinCoreApplicationEnvironment {
val environment = KotlinCoreApplicationEnvironment(parentDisposable, unitTestMode)
return create(parentDisposable, KotlinCoreApplicationEnvironmentMode.fromUnitTestModeFlag(unitTestMode))
}
fun create(
parentDisposable: Disposable,
environmentMode: KotlinCoreApplicationEnvironmentMode,
): KotlinCoreApplicationEnvironment {
val environment = KotlinCoreApplicationEnvironment(parentDisposable, environmentMode)
registerExtensionPoints()
return environment
}
@@ -102,3 +128,26 @@ class KotlinCoreApplicationEnvironment private constructor(
}
}
}
private class MockUnitTestApplication(parentDisposable: Disposable) : MockApplication(parentDisposable) {
/**
* We can't use [KotlinCoreApplicationEnvironment.environmentMode] in [KotlinCoreApplicationEnvironment.createApplication], because the
* corresponding property is not yet initialized when `createApplication` is called from the superclass constructor. So we have to
* initialize it later.
*/
lateinit var environmentMode: KotlinCoreApplicationEnvironmentMode
override fun isUnitTestMode(): Boolean = true
override fun isWriteAccessAllowed(): Boolean = environmentMode.isWriteAccessAllowed
}
private fun MockApplication.initializeEnvironmentMode(environmentMode: KotlinCoreApplicationEnvironmentMode) {
if (this !is MockUnitTestApplication) return
this.environmentMode = environmentMode
require(isWriteAccessAllowed == environmentMode.isWriteAccessAllowed) {
"The mock application's `isWriteAccessAllowed` should correspond to the environment mode configuration."
}
}
@@ -469,7 +469,9 @@ class KotlinCoreEnvironment private constructor(
// Tests are supposed to create a single project and dispose it right after use
val appEnv =
createApplicationEnvironment(
parentDisposable, configuration, unitTestMode = true
parentDisposable,
configuration,
KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false),
)
val projectEnv = ProjectEnvironment(parentDisposable, appEnv, configuration)
return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs)
@@ -499,7 +501,7 @@ class KotlinCoreEnvironment private constructor(
val appEnv = createApplicationEnvironment(
parentDisposable,
configuration,
unitTestMode = true
KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false),
)
return ProjectEnvironment(parentDisposable, appEnv, configuration)
}
@@ -509,14 +511,25 @@ class KotlinCoreEnvironment private constructor(
fun getOrCreateApplicationEnvironmentForProduction(
parentDisposable: Disposable, configuration: CompilerConfiguration
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = false)
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(
parentDisposable,
configuration,
KotlinCoreApplicationEnvironmentMode.Production,
)
fun getOrCreateApplicationEnvironmentForTests(
parentDisposable: Disposable, configuration: CompilerConfiguration
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(parentDisposable, configuration, unitTestMode = true)
parentDisposable: Disposable,
configuration: CompilerConfiguration,
): KotlinCoreApplicationEnvironment = getOrCreateApplicationEnvironment(
parentDisposable,
configuration,
KotlinCoreApplicationEnvironmentMode.UnitTest(isWriteAccessAllowed = false),
)
private fun getOrCreateApplicationEnvironment(
parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean
fun getOrCreateApplicationEnvironment(
parentDisposable: Disposable,
configuration: CompilerConfiguration,
environmentMode: KotlinCoreApplicationEnvironmentMode,
): KotlinCoreApplicationEnvironment {
synchronized(APPLICATION_LOCK) {
if (ourApplicationEnvironment == null) {
@@ -525,7 +538,7 @@ class KotlinCoreEnvironment private constructor(
createApplicationEnvironment(
disposable,
configuration,
unitTestMode
environmentMode,
)
ourProjectCount = 0
Disposer.register(disposable, Disposable {
@@ -606,11 +619,11 @@ class KotlinCoreEnvironment private constructor(
}
private fun createApplicationEnvironment(
parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean
parentDisposable: Disposable,
configuration: CompilerConfiguration,
environmentMode: KotlinCoreApplicationEnvironmentMode,
): KotlinCoreApplicationEnvironment {
val applicationEnvironment = KotlinCoreApplicationEnvironment.create(
parentDisposable, unitTestMode
)
val applicationEnvironment = KotlinCoreApplicationEnvironment.create(parentDisposable, environmentMode)
registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/compiler.xml")