FIR IDE: introduce memory leak checking in symbols test
This commit is contained in:
@@ -89,10 +89,7 @@ import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractMemberScopeByFqNameTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolFromLibraryPointerRestoreTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolsByFqNameBuildingTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolFromSourcePointerRestoreTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.AbstractSymbolsByPsiBuildingTest
|
||||
import org.jetbrains.kotlin.idea.frontend.api.symbols.*
|
||||
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest
|
||||
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyWithLibTest
|
||||
import org.jetbrains.kotlin.idea.highlighter.*
|
||||
@@ -1020,6 +1017,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractSymbolFromLibraryPointerRestoreTest> {
|
||||
model("resoreSymbolFromLibrary", extension = "txt")
|
||||
}
|
||||
|
||||
testClass<AbstractMemoryLeakInSymbolsTest> {
|
||||
model("symbolMemoryLeak")
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/testData") {
|
||||
|
||||
+7
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.idea.fir.low.level.api.trackers
|
||||
import com.intellij.ProjectTopics
|
||||
import com.intellij.lang.ASTNode
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ModuleRootEvent
|
||||
@@ -18,6 +19,7 @@ import com.intellij.pom.event.PomModelEvent
|
||||
import com.intellij.pom.event.PomModelListener
|
||||
import com.intellij.pom.tree.TreeAspect
|
||||
import com.intellij.pom.tree.events.TreeChangeEvent
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.element.builder.getNonLocalContainingInBodyDeclarationWith
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileElementFactory
|
||||
@@ -52,6 +54,11 @@ internal class KotlinFirModificationTrackerService(project: Project) : Disposabl
|
||||
moduleModificationsState.increaseModificationCountForAllModules()
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun incrementModificationsCount() {
|
||||
increaseModificationCountForAllModules()
|
||||
}
|
||||
|
||||
private inner class Listener : PomModelListener {
|
||||
override fun isAspectChangeInteresting(aspect: PomModelAspect): Boolean =
|
||||
treeAspect == aspect
|
||||
|
||||
+6
@@ -9,6 +9,7 @@ import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ModificationTracker
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
class KotlinFirOutOfBlockModificationTrackerFactory(private val project: Project) {
|
||||
fun createProjectWideOutOfBlockModificationTracker(): ModificationTracker =
|
||||
@@ -16,6 +17,11 @@ class KotlinFirOutOfBlockModificationTrackerFactory(private val project: Project
|
||||
|
||||
fun createModuleWithoutDependenciesOutOfBlockModificationTracker(module: Module): ModificationTracker =
|
||||
KotlinFirOutOfBlockModuleModificationTracker(module)
|
||||
|
||||
@TestOnly
|
||||
fun incrementModificationsCount() {
|
||||
project.service<KotlinFirModificationTrackerService>().incrementModificationsCount()
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.createProjectWideOutOfBlockModificationTracker() =
|
||||
|
||||
+5
@@ -42,4 +42,9 @@ internal class KtFirAnalysisSessionProvider(project: Project) : KtAnalysisSessio
|
||||
analysisSessionByModuleInfoCache.value.getOrPut(firModuleResolveState.moduleInfo) {
|
||||
KtFirAnalysisSession.createAnalysisSessionByResolveState(firModuleResolveState)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun clearCaches() {
|
||||
analysisSessionByModuleInfoCache.value.clear()
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.idea.frontend.api.fir.utils
|
||||
|
||||
internal class EntityWasGarbageCollectedException(entity: String) : IllegalStateException() {
|
||||
override val message: String = "$entity was garbage collected while KtAnalysisSession session is still valid"
|
||||
}
|
||||
+6
-6
@@ -21,9 +21,9 @@ internal class FirRefWithValidityCheck<D : FirDeclaration>(fir: D, resolveState:
|
||||
inline fun <R> withFir(phase: FirResolvePhase = FirResolvePhase.RAW_FIR, crossinline action: (fir: D) -> R): R {
|
||||
token.assertIsValid()
|
||||
val fir = firWeakRef.get()
|
||||
?: error("FirElement was garbage collected while analysis session is still valid")
|
||||
val resolveState =
|
||||
resolveStateWeakRef.get() ?: error("FirModuleResolveState was garbage collected while analysis session is still valid")
|
||||
?: throw EntityWasGarbageCollectedException("FirElement")
|
||||
val resolveState = resolveStateWeakRef.get()
|
||||
?: throw EntityWasGarbageCollectedException("FirModuleResolveState")
|
||||
LowLevelFirApiFacade.resolvedFirToPhase(fir, phase, resolveState)
|
||||
return resolveState.withFirDeclaration(fir) { action(it) }
|
||||
}
|
||||
@@ -31,9 +31,9 @@ internal class FirRefWithValidityCheck<D : FirDeclaration>(fir: D, resolveState:
|
||||
inline fun <R> withFirResolvedToBodyResolve(action: (fir: D) -> R): R {
|
||||
token.assertIsValid()
|
||||
val fir = firWeakRef.get()
|
||||
?: error("FirElement was garbage collected while analysis session is still valid")
|
||||
val resolveState =
|
||||
resolveStateWeakRef.get() ?: error("FirModuleResolveState was garbage collected while analysis session is still valid")
|
||||
?: throw EntityWasGarbageCollectedException("FirElement")
|
||||
val resolveState = resolveStateWeakRef.get()
|
||||
?: throw EntityWasGarbageCollectedException("FirModuleResolveState")
|
||||
LowLevelFirApiFacade.resolvedFirToPhase(fir, FirResolvePhase.BODY_RESOLVE, resolveState)
|
||||
return action(resolveState.withFirDeclaration(fir) { it })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
fun <R> x(p: R): Int {
|
||||
|
||||
}
|
||||
|
||||
class Y<T> {
|
||||
fun a() = 1
|
||||
}
|
||||
|
||||
var z: Int
|
||||
get = 10
|
||||
set(value) {}
|
||||
|
||||
object Q
|
||||
|
||||
val z: String = ""
|
||||
|
||||
fun yyy() {
|
||||
// val q = 10
|
||||
// fun aaa() {}
|
||||
//
|
||||
// class F {}
|
||||
}
|
||||
|
||||
typealias Str = String
|
||||
|
||||
interface I {
|
||||
fun str(): String
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.idea.frontend.api.symbols
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import junit.framework.Assert
|
||||
import org.jetbrains.kotlin.idea.executeOnPooledThreadInReadAction
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirOutOfBlockModificationTrackerFactory
|
||||
import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession
|
||||
import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.idea.frontend.api.analyze
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSessionProvider
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol
|
||||
import org.jetbrains.kotlin.idea.frontend.api.fir.utils.EntityWasGarbageCollectedException
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractMemoryLeakInSymbolsTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
override fun isFirPlugin() = true
|
||||
|
||||
protected fun doTest(path: String) {
|
||||
val testDataFile = File(path)
|
||||
val ktFile = myFixture.configureByText(testDataFile.name, FileUtil.loadFile(testDataFile)) as KtFile
|
||||
val symbols = executeOnPooledThreadInReadAction {
|
||||
analyze(ktFile) {
|
||||
ktFile.collectDescendantsOfType<KtDeclaration>().map { it.getSymbol() }
|
||||
}
|
||||
}
|
||||
|
||||
invalidateAllCaches(ktFile)
|
||||
System.gc()
|
||||
|
||||
val leakedSymbols = executeOnPooledThreadInReadAction {
|
||||
symbols.map { it.hasNoFirElementLeak() }.filterIsInstance<LeakCheckResult.Leak>()
|
||||
}
|
||||
if (leakedSymbols.isNotEmpty()) {
|
||||
Assert.fail(
|
||||
"""The following symbols leaked (${leakedSymbols.size}/${symbols.size})
|
||||
${leakedSymbols.joinToString(separator = "\n") { it.symbol }}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(InvalidWayOfUsingAnalysisSession::class)
|
||||
private fun invalidateAllCaches(ktFile: KtFile) {
|
||||
project.service<KotlinFirOutOfBlockModificationTrackerFactory>().incrementModificationsCount()
|
||||
(project.service<KtAnalysisSessionProvider>() as KtFirAnalysisSessionProvider).clearCaches()
|
||||
executeOnPooledThreadInReadAction { analyze(ktFile) {} }
|
||||
}
|
||||
|
||||
private fun KtSymbol.hasNoFirElementLeak(): LeakCheckResult {
|
||||
require(this is KtFirSymbol<*>)
|
||||
return try {
|
||||
firRef.withFir { LeakCheckResult.Leak(this::class.simpleName!!) }
|
||||
} catch (_: EntityWasGarbageCollectedException) {
|
||||
LeakCheckResult.NoLeak
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LeakCheckResult {
|
||||
object NoLeak : LeakCheckResult()
|
||||
data class Leak(val symbol: String) : LeakCheckResult()
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.idea.frontend.api.symbols;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/idea-frontend-fir/testData/symbolMemoryLeak")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class MemoryLeakInSymbolsTestGenerated extends AbstractMemoryLeakInSymbolsTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSymbolMemoryLeak() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/testData/symbolMemoryLeak"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("symbols.kt")
|
||||
public void testSymbols() throws Exception {
|
||||
runTest("idea/idea-frontend-fir/testData/symbolMemoryLeak/symbols.kt");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user