From 5819f4eaa29c4e5d8862bd3e5f496d7b66afbeac Mon Sep 17 00:00:00 2001 From: Marco Pennekamp Date: Mon, 21 Aug 2023 17:45:47 +0200 Subject: [PATCH] [LL FIR] Test resolve extension disposal after modification events - These tests simply ensure that a resolve extension is disposed after any kind of modification event is raised. It's not meant to test complex scenarios, but rather to detect when resolve extension disposal isn't invoked *at all*. - I tried implementing a similar test for resolve extension disposal after soft reference garbage collection, but the only way to force the GC to collect soft references is to allocate memory until an out-of-memory error occurs. That is bad for the test infrastructure, because it might allocate A LOT of memory (depending on the max heap), which is problematic for running tests locally. Also, our Kotlin tests stop on an OOM error altogether (so additional configuration would be required) and the heap is dumped into a 20GB file (on my machine), which is again problematic for local test runs. ^KT-61222 --- .../afterGlobalModuleStateModification.kt | 1 + ...fterGlobalSourceModuleStateModification.kt | 1 + ...afterGlobalSourceOutOfBlockModification.kt | 1 + .../afterModuleOutOfBlockModification.kt | 1 + .../afterModuleStateModification.kt | 1 + ...nsionDisposalAfterModificationEventTest.kt | 102 ++++++++++++++++++ ...alAfterModificationEventTestGenerated.java | 56 ++++++++++ .../tests/analysis/api/firLowLevel.kt | 5 + 8 files changed, 168 insertions(+) create mode 100644 analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalModuleStateModification.kt create mode 100644 analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceModuleStateModification.kt create mode 100644 analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceOutOfBlockModification.kt create mode 100644 analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleOutOfBlockModification.kt create mode 100644 analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleStateModification.kt create mode 100644 analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/AbstractResolveExtensionDisposalAfterModificationEventTest.kt create mode 100644 analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/ResolveExtensionDisposalAfterModificationEventTestGenerated.java diff --git a/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalModuleStateModification.kt b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalModuleStateModification.kt new file mode 100644 index 00000000000..02e6aa5877e --- /dev/null +++ b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalModuleStateModification.kt @@ -0,0 +1 @@ +// MODIFICATION_EVENT: GLOBAL_MODULE_STATE_MODIFICATION diff --git a/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceModuleStateModification.kt b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceModuleStateModification.kt new file mode 100644 index 00000000000..82a4ade3eb9 --- /dev/null +++ b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceModuleStateModification.kt @@ -0,0 +1 @@ +// MODIFICATION_EVENT: GLOBAL_SOURCE_MODULE_STATE_MODIFICATION diff --git a/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceOutOfBlockModification.kt b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceOutOfBlockModification.kt new file mode 100644 index 00000000000..d6c8b8c7ecb --- /dev/null +++ b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceOutOfBlockModification.kt @@ -0,0 +1 @@ +// MODIFICATION_EVENT: GLOBAL_SOURCE_OUT_OF_BLOCK_MODIFICATION diff --git a/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleOutOfBlockModification.kt b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleOutOfBlockModification.kt new file mode 100644 index 00000000000..c42affd7ebd --- /dev/null +++ b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleOutOfBlockModification.kt @@ -0,0 +1 @@ +// MODIFICATION_EVENT: MODULE_OUT_OF_BLOCK_MODIFICATION diff --git a/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleStateModification.kt b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleStateModification.kt new file mode 100644 index 00000000000..68cda00d932 --- /dev/null +++ b/analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleStateModification.kt @@ -0,0 +1 @@ +// MODIFICATION_EVENT: MODULE_STATE_MODIFICATION diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/AbstractResolveExtensionDisposalAfterModificationEventTest.kt b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/AbstractResolveExtensionDisposalAfterModificationEventTest.kt new file mode 100644 index 00000000000..a399fc33c1e --- /dev/null +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/AbstractResolveExtensionDisposalAfterModificationEventTest.kt @@ -0,0 +1,102 @@ +/* + * 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.low.level.api.fir.resolve.extensions + +import com.intellij.mock.MockApplication +import com.intellij.mock.MockProject +import com.intellij.openapi.Disposable +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtension +import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionFile +import org.jetbrains.kotlin.analysis.api.resolve.extensions.KtResolveExtensionProvider +import org.jetbrains.kotlin.analysis.low.level.api.fir.sessions.LLFirSessionCache +import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator +import org.jetbrains.kotlin.analysis.project.structure.KtModule +import org.jetbrains.kotlin.analysis.project.structure.ProjectStructureProvider +import org.jetbrains.kotlin.analysis.test.framework.base.AbstractAnalysisApiBasedTest +import org.jetbrains.kotlin.analysis.test.framework.directives.ModificationEventDirectives +import org.jetbrains.kotlin.analysis.test.framework.directives.publishModificationEventByDirective +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestConfigurator +import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestServiceRegistrar +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions + +/** + * A simple test which detects when resolve extension disposal after modification events/session invalidation doesn't work *at all*. + */ +abstract class AbstractResolveExtensionDisposalAfterModificationEventTest : AbstractAnalysisApiBasedTest() { + override fun doTestByMainFile( + mainFile: KtFile, + mainModule: TestModule, + testServices: TestServices, + ) { + val project = mainFile.project + val module = ProjectStructureProvider.getModule(project, mainFile, contextualModule = null) + val session = LLFirSessionCache.getInstance(project).getSession(module) + val resolveExtension = session.llResolveExtensionTool!!.extensions.single() as KtResolveExtensionWithDisposalTracker + + testServices.assertions.assertFalse(resolveExtension.isDisposed) { + "The resolve extension should not be disposed before the modification event is published." + } + + mainModule.publishModificationEventByDirective(project, module) + + testServices.assertions.assertTrue(resolveExtension.isDisposed) { + "The resolve extension should be disposed after the modification event has been published." + } + } + + override val configurator: AnalysisApiTestConfigurator + get() = ResolveExtensionDisposalTestConfigurator +} + +class KtResolveExtensionWithDisposalTracker() : KtResolveExtension() { + override fun getKtFiles(): List = emptyList() + override fun getContainedPackages(): Set = emptySet() + override fun getShadowedScope(): GlobalSearchScope = GlobalSearchScope.EMPTY_SCOPE + + var isDisposed: Boolean = false + + override fun dispose() { + isDisposed = true + } +} + +class KtResolveExtensionWithDisposalTrackerProvider() : KtResolveExtensionProvider() { + override fun provideExtensionsFor(module: KtModule): List = listOf(KtResolveExtensionWithDisposalTracker()) +} + +object ResolveExtensionDisposalTestConfigurator : AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false) { + override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) { + super.configureTest(builder, disposable) + + builder.useDirectives(ModificationEventDirectives) + } + + override val serviceRegistrars: List + get() = buildList { + addAll(super.serviceRegistrars) + add(ResolveExtensionDisposalTestServiceRegistrar) + } +} + +object ResolveExtensionDisposalTestServiceRegistrar : AnalysisApiTestServiceRegistrar() { + override fun registerApplicationServices(application: MockApplication, testServices: TestServices) {} + override fun registerProjectExtensionPoints(project: MockProject, testServices: TestServices) {} + override fun registerProjectModelServices(project: MockProject, testServices: TestServices) {} + + override fun registerProjectServices( + project: MockProject, + testServices: TestServices, + ) { + val extensionPoint = project.extensionArea.getExtensionPoint(KtResolveExtensionProvider.EP_NAME) + extensionPoint.registerExtension(KtResolveExtensionWithDisposalTrackerProvider(), project) + } +} diff --git a/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/ResolveExtensionDisposalAfterModificationEventTestGenerated.java b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/ResolveExtensionDisposalAfterModificationEventTestGenerated.java new file mode 100644 index 00000000000..9de40c42785 --- /dev/null +++ b/analysis/low-level-api-fir/tests/org/jetbrains/kotlin/analysis/low/level/api/fir/resolve/extensions/ResolveExtensionDisposalAfterModificationEventTestGenerated.java @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2024 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.low.level.api.fir.resolve.extensions; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("analysis/low-level-api-fir/testData/resolveExtensionDisposal") +@TestDataPath("$PROJECT_ROOT") +public class ResolveExtensionDisposalAfterModificationEventTestGenerated extends AbstractResolveExtensionDisposalAfterModificationEventTest { + @Test + @TestMetadata("afterGlobalModuleStateModification.kt") + public void testAfterGlobalModuleStateModification() { + runTest("analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalModuleStateModification.kt"); + } + + @Test + @TestMetadata("afterGlobalSourceModuleStateModification.kt") + public void testAfterGlobalSourceModuleStateModification() { + runTest("analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceModuleStateModification.kt"); + } + + @Test + @TestMetadata("afterGlobalSourceOutOfBlockModification.kt") + public void testAfterGlobalSourceOutOfBlockModification() { + runTest("analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterGlobalSourceOutOfBlockModification.kt"); + } + + @Test + @TestMetadata("afterModuleOutOfBlockModification.kt") + public void testAfterModuleOutOfBlockModification() { + runTest("analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleOutOfBlockModification.kt"); + } + + @Test + @TestMetadata("afterModuleStateModification.kt") + public void testAfterModuleStateModification() { + runTest("analysis/low-level-api-fir/testData/resolveExtensionDisposal/afterModuleStateModification.kt"); + } + + @Test + public void testAllFilesPresentInResolveExtensionDisposal() { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testData/resolveExtensionDisposal"), Pattern.compile("^(.+)\\.kt$"), null, true); + } +} diff --git a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt index 6f879efc90d..d3c22b1f061 100644 --- a/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt +++ b/generators/analysis-api-generator/tests/org/jetbrains/kotlin/generators/tests/analysis/api/firLowLevel.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractScriptLaz import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractScriptWholeFileResolvePhaseTest import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractSourceLazyDeclarationResolveScopeBasedTest import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractSourceWholeFileResolvePhaseTest +import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.extensions.AbstractResolveExtensionDisposalAfterModificationEventTest import org.jetbrains.kotlin.generators.TestGroup import org.jetbrains.kotlin.generators.TestGroupSuite import org.jetbrains.kotlin.generators.util.TestGeneratorUtil @@ -281,6 +282,10 @@ internal fun TestGroupSuite.generateFirLowLevelApiTests() { testClass { model("contextCollector", pattern = TestGeneratorUtil.KTS) } + + testClass { + model("resolveExtensionDisposal") + } } testGroup("analysis/low-level-api-fir/tests", "analysis/analysis-api/testData") {