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.file.structure.AbstractFileStructureTest
|
||||
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.frontend.api.fir.AbstractResolveCallTest
|
||||
import org.jetbrains.kotlin.idea.fir.low.level.api.trackers.AbstractProjectWideOutOfBlockKotlinModificationTrackerTest
|
||||
@@ -1049,6 +1050,9 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractFileStructureTest> {
|
||||
model("fileStructure")
|
||||
}
|
||||
testClass<AbstractSessionsInvalidationTest> {
|
||||
model("sessionInvalidation", recursive = false, extension = null)
|
||||
}
|
||||
}
|
||||
|
||||
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.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
|
||||
@@ -45,6 +46,12 @@ internal class KotlinFirModificationTrackerService(project: Project) : Disposabl
|
||||
moduleModificationsState.increaseModificationCountForAllModules()
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun increaseModificationCountForModule(module: Module) {
|
||||
moduleModificationsState.increaseModificationCountForModule(module)
|
||||
}
|
||||
|
||||
|
||||
private fun subscribeForRootChanges(project: Project) {
|
||||
project.messageBus.connect(this).subscribe(
|
||||
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))
|
||||
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
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.project.Project
|
||||
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.trackers.KotlinFirModificationTrackerService
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
|
||||
internal fun Project.allModules() = ModuleManager.getInstance(this).modules.toList()
|
||||
@@ -16,4 +19,8 @@ internal fun Project.allModules() = ModuleManager.getInstance(this).modules.toLi
|
||||
inline fun resolveWithClearCaches(context: KtElement, action: (FirModuleResolveState) -> Unit) {
|
||||
val resolveState = createResolveStateForNoCaching(context.getModuleInfo())
|
||||
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