FIR IDE: add tests for checking module invalidation
This commit is contained in:
@@ -85,6 +85,7 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyDeclarationRes
|
|||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest
|
import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureTest
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureTest
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.AbstractFileStructureAndOutOfBlockModificationTrackerConsistencyTest
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.sessions.AbstractSessionsInvalidationTest
|
||||||
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
|
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
|
||||||
import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest
|
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.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest
|
||||||
@@ -1049,6 +1050,9 @@ fun main(args: Array<String>) {
|
|||||||
testClass<AbstractFileStructureTest> {
|
testClass<AbstractFileStructureTest> {
|
||||||
model("fileStructure")
|
model("fileStructure")
|
||||||
}
|
}
|
||||||
|
testClass<AbstractSessionsInvalidationTest> {
|
||||||
|
model("sessionInvalidation", recursive = false, extension = null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testGroup("idea/idea-fir/tests", "idea") {
|
testGroup("idea/idea-fir/tests", "idea") {
|
||||||
|
|||||||
+7
@@ -18,6 +18,7 @@ import com.intellij.pom.event.PomModelEvent
|
|||||||
import com.intellij.pom.event.PomModelListener
|
import com.intellij.pom.event.PomModelListener
|
||||||
import com.intellij.pom.tree.TreeAspect
|
import com.intellij.pom.tree.TreeAspect
|
||||||
import com.intellij.pom.tree.events.TreeChangeEvent
|
import com.intellij.pom.tree.events.TreeChangeEvent
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
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.element.builder.getNonLocalContainingInBodyDeclarationWith
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileElementFactory
|
import org.jetbrains.kotlin.idea.fir.low.level.api.file.structure.FileElementFactory
|
||||||
@@ -45,6 +46,12 @@ internal class KotlinFirModificationTrackerService(project: Project) : Disposabl
|
|||||||
moduleModificationsState.increaseModificationCountForAllModules()
|
moduleModificationsState.increaseModificationCountForAllModules()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
fun increaseModificationCountForModule(module: Module) {
|
||||||
|
moduleModificationsState.increaseModificationCountForModule(module)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun subscribeForRootChanges(project: Project) {
|
private fun subscribeForRootChanges(project: Project) {
|
||||||
project.messageBus.connect(this).subscribe(
|
project.messageBus.connect(this).subscribe(
|
||||||
ProjectTopics.PROJECT_ROOTS,
|
ProjectTopics.PROJECT_ROOTS,
|
||||||
|
|||||||
idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/binaryTree/structure.json
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B", "C"] },
|
||||||
|
{ "name": "B", "dependsOn": ["D", "E"] },
|
||||||
|
{ "name": "C", "dependsOn": ["F", "G"] },
|
||||||
|
{ "name": "D", "dependsOn": [] },
|
||||||
|
{ "name": "E", "dependsOn": [] },
|
||||||
|
{ "name": "F", "dependsOn": [] },
|
||||||
|
{ "name": "G", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": ["F"],
|
||||||
|
"expectedInvalidatedModules": ["A", "C", "F"]
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B", "C"] },
|
||||||
|
{ "name": "B", "dependsOn": ["D", "E"] },
|
||||||
|
{ "name": "C", "dependsOn": ["F", "G"] },
|
||||||
|
{ "name": "D", "dependsOn": [] },
|
||||||
|
{ "name": "E", "dependsOn": [] },
|
||||||
|
{ "name": "F", "dependsOn": [] },
|
||||||
|
{ "name": "G", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": [],
|
||||||
|
"expectedInvalidatedModules": []
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B", "C"] },
|
||||||
|
{ "name": "B", "dependsOn": ["D", "E"] },
|
||||||
|
{ "name": "C", "dependsOn": ["F", "G"] },
|
||||||
|
{ "name": "D", "dependsOn": [] },
|
||||||
|
{ "name": "E", "dependsOn": [] },
|
||||||
|
{ "name": "F", "dependsOn": ["B"] },
|
||||||
|
{ "name": "G", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": ["E"],
|
||||||
|
"expectedInvalidatedModules": ["A", "B", "C", "E", "F"]
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B", "C"] },
|
||||||
|
{ "name": "B", "dependsOn": ["D", "E"] },
|
||||||
|
{ "name": "C", "dependsOn": ["F", "G"] },
|
||||||
|
{ "name": "D", "dependsOn": [] },
|
||||||
|
{ "name": "E", "dependsOn": [] },
|
||||||
|
{ "name": "F", "dependsOn": [] },
|
||||||
|
{ "name": "G", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": ["A"],
|
||||||
|
"expectedInvalidatedModules": ["A"]
|
||||||
|
}
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B"] },
|
||||||
|
{ "name": "B", "dependsOn": ["C"] },
|
||||||
|
{ "name": "C", "dependsOn": ["D"] },
|
||||||
|
{ "name": "D", "dependsOn": ["E"] },
|
||||||
|
{ "name": "E", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": ["C"],
|
||||||
|
"expectedInvalidatedModules": ["A", "B", "C"]
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B", "C"] },
|
||||||
|
{ "name": "B", "dependsOn": ["D"] },
|
||||||
|
{ "name": "C", "dependsOn": ["D"] },
|
||||||
|
{ "name": "D", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": ["D"],
|
||||||
|
"expectedInvalidatedModules": ["A", "B", "C", "D"]
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"modules" : [
|
||||||
|
{ "name": "A", "dependsOn": ["B", "C"] },
|
||||||
|
{ "name": "B", "dependsOn": ["D"] },
|
||||||
|
{ "name": "C", "dependsOn": ["D"] },
|
||||||
|
{ "name": "D", "dependsOn": [] }
|
||||||
|
],
|
||||||
|
"rootModule": "A",
|
||||||
|
"modulesToMakeOOBM": ["C", "D"],
|
||||||
|
"expectedInvalidatedModules": ["A", "B", "C", "D"]
|
||||||
|
}
|
||||||
+6
@@ -55,4 +55,10 @@ internal object TestProjectStructureReader {
|
|||||||
val json = JsonParser().parse(FileUtil.loadFile(jsonFile.toFile(), /*convertLineSeparators=*/true))
|
val json = JsonParser().parse(FileUtil.loadFile(jsonFile.toFile(), /*convertLineSeparators=*/true))
|
||||||
return TestProjectStructure.parse(json)
|
return TestProjectStructure.parse(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> readToTestStructure(
|
||||||
|
testDirectory: Path,
|
||||||
|
jsonFileName: String = "structure.json",
|
||||||
|
toTestStructure: (TestProjectStructure) -> T,
|
||||||
|
): T = read(testDirectory, jsonFileName).let(toTestStructure)
|
||||||
}
|
}
|
||||||
+7
@@ -5,10 +5,13 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.fir.low.level.api
|
package org.jetbrains.kotlin.idea.fir.low.level.api
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
import com.intellij.openapi.module.ModuleManager
|
import com.intellij.openapi.module.ModuleManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||||
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
|
import org.jetbrains.kotlin.idea.fir.low.level.api.api.FirModuleResolveState
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.KotlinFirModificationTrackerService
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
|
||||||
internal fun Project.allModules() = ModuleManager.getInstance(this).modules.toList()
|
internal fun Project.allModules() = ModuleManager.getInstance(this).modules.toList()
|
||||||
@@ -17,3 +20,7 @@ inline fun resolveWithClearCaches(context: KtElement, action: (FirModuleResolveS
|
|||||||
val resolveState = createResolveStateForNoCaching(context.getModuleInfo())
|
val resolveState = createResolveStateForNoCaching(context.getModuleInfo())
|
||||||
action(resolveState)
|
action(resolveState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Module.incModificationTracker() {
|
||||||
|
project.service<KotlinFirModificationTrackerService>().increaseModificationCountForModule(this)
|
||||||
|
}
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.fir.low.level.api.sessions
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.vfs.LocalFileSystem
|
||||||
|
import com.intellij.testFramework.PsiTestUtil
|
||||||
|
import junit.framework.Assert
|
||||||
|
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
|
||||||
|
import org.jetbrains.kotlin.idea.caches.project.productionSourceInfo
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.TestProjectModule
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.TestProjectStructure
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.TestProjectStructureReader
|
||||||
|
import org.jetbrains.kotlin.idea.fir.low.level.api.incModificationTracker
|
||||||
|
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||||
|
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
|
||||||
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
|
||||||
|
abstract class AbstractSessionsInvalidationTest : AbstractMultiModuleTest() {
|
||||||
|
override fun getTestDataPath(): String =
|
||||||
|
"${KotlinTestUtils.getHomeDirectory()}/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/"
|
||||||
|
|
||||||
|
protected fun doTest(path: String) {
|
||||||
|
val testStructure = TestProjectStructureReader.readToTestStructure(
|
||||||
|
Paths.get(path),
|
||||||
|
toTestStructure = MultiModuleTestProjectStructure.Companion::fromTestProjectStructure
|
||||||
|
)
|
||||||
|
val modulesByNames = testStructure.modules.associate { moduleData ->
|
||||||
|
moduleData.name to createEmptyModule(moduleData.name)
|
||||||
|
}
|
||||||
|
testStructure.modules.forEach { moduleData ->
|
||||||
|
val module = modulesByNames.getValue(moduleData.name)
|
||||||
|
moduleData.dependsOnModules.forEach { dependencyName ->
|
||||||
|
module.addDependency(modulesByNames.getValue(dependencyName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val rootModule = modulesByNames[testStructure.rootModule]
|
||||||
|
?: error("${testStructure.rootModule} is not present in the list of modules")
|
||||||
|
val modulesToMakeOOBM = testStructure.modulesToMakeOOBM.map {
|
||||||
|
modulesByNames[it]
|
||||||
|
?: error("$it is not present in the list of modules")
|
||||||
|
}
|
||||||
|
|
||||||
|
val rootModuleSourceInfo = rootModule.productionSourceInfo()!!
|
||||||
|
|
||||||
|
val storage = FirIdeSessionProviderStorage(project)
|
||||||
|
|
||||||
|
val initialSessions = storage.getFirSessions(rootModuleSourceInfo)
|
||||||
|
modulesToMakeOOBM.forEach { it.incModificationTracker() }
|
||||||
|
val sessionsAfterOOBM = storage.getFirSessions(rootModuleSourceInfo)
|
||||||
|
|
||||||
|
val changedSessions = Sets.symmetricDifference(initialSessions, sessionsAfterOOBM)
|
||||||
|
val changedSessionsModulesNamesSorted = changedSessions.map { (it.moduleInfo as ModuleSourceInfo).module.name }.distinct().sorted()
|
||||||
|
|
||||||
|
Assert.assertEquals(testStructure.expectedInvalidatedModules, changedSessionsModulesNamesSorted)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FirIdeSessionProviderStorage.getFirSessions(rootModuleInfo: ModuleSourceInfo): Set<FirIdeSession> {
|
||||||
|
val sessionProvider = getSessionProvider(rootModuleInfo)
|
||||||
|
return sessionProvider.sessions.values.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEmptyModule(name: String): Module {
|
||||||
|
val tmpDir = createTempDirectory().toPath()
|
||||||
|
val module: Module = createModule("$tmpDir/$name", moduleType)
|
||||||
|
val root = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tmpDir.toFile())!!
|
||||||
|
WriteCommandAction.writeCommandAction(module.project).run<RuntimeException> {
|
||||||
|
root.refresh(false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
PsiTestUtil.addSourceContentToRoots(module, root)
|
||||||
|
return module
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class MultiModuleTestProjectStructure(
|
||||||
|
val modules: List<TestProjectModule>,
|
||||||
|
val rootModule: String,
|
||||||
|
val modulesToMakeOOBM: List<String>,
|
||||||
|
val expectedInvalidatedModules: List<String>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun fromTestProjectStructure(testProjectStructure: TestProjectStructure): MultiModuleTestProjectStructure {
|
||||||
|
val json = testProjectStructure.json
|
||||||
|
|
||||||
|
return MultiModuleTestProjectStructure(
|
||||||
|
testProjectStructure.modules,
|
||||||
|
json.getString(ROOT_MODULE_FIELD),
|
||||||
|
json.getAsJsonArray(MODULES_TO_MAKE_OOBM_IN_FIELD).map { it.asString }.sorted(),
|
||||||
|
json.getAsJsonArray(EXPECTED_INVALIDATED_MODULES_FIELD).map { it.asString }.sorted(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val ROOT_MODULE_FIELD = "rootModule"
|
||||||
|
private const val MODULES_TO_MAKE_OOBM_IN_FIELD = "modulesToMakeOOBM"
|
||||||
|
private const val EXPECTED_INVALIDATED_MODULES_FIELD = "expectedInvalidatedModules"
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.fir.low.level.api.sessions;
|
||||||
|
|
||||||
|
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/idea-fir-low-level-api/testdata/sessionInvalidation")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public class SessionsInvalidationTestGenerated extends AbstractSessionsInvalidationTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInSessionInvalidation() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation"), Pattern.compile("^([^\\.]+)$"), null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("binaryTree")
|
||||||
|
public void testBinaryTree() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/binaryTree/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("binaryTreeNoInvalidated")
|
||||||
|
public void testBinaryTreeNoInvalidated() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/binaryTreeNoInvalidated/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("binaryTreeWithAdditionalEdge")
|
||||||
|
public void testBinaryTreeWithAdditionalEdge() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/binaryTreeWithAdditionalEdge/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("binaryTreeWithInvalidInRoot")
|
||||||
|
public void testBinaryTreeWithInvalidInRoot() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/binaryTreeWithInvalidInRoot/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("linear")
|
||||||
|
public void testLinear() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/linear/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("rhombus")
|
||||||
|
public void testRhombus() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/rhombus/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("rhombusWithTwoInvalid")
|
||||||
|
public void testRhombusWithTwoInvalid() throws Exception {
|
||||||
|
runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/sessionInvalidation/rhombusWithTwoInvalid/");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user