Files
openide/java/java-tests/testSrc/com/intellij/roots/AutomaticModuleUnloaderTest.kt
Nikolay Chashnikov 3514846e47 [workspace model] fix automatic unloading of modules added on the fly (IDEA-266792)
Before AutomaticModuleUnloader was executed only during initial project loading, and wasn't executed if project configuration files were changed after the project is opened. Now it is executed in the both cases. Also handling of unloaded modules is simplified: since we load ModuleEntity instance for them anyway, there is no need to load them again in loadStateOfUnloadedModules, and there is no need to create UnloadedModuleDescription instances for AutomaticModuleUnloader.

GitOrigin-RevId: e025b344e0e7343a4740544bc47e7beca0c5bd0a
2021-04-14 19:56:42 +03:00

238 lines
8.5 KiB
Kotlin

// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.roots
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.StdModuleTypes
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.util.JDOMUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.project.ProjectStoreOwner
import com.intellij.testFramework.*
import com.intellij.testFramework.UsefulTestCase.assertSameElements
import com.intellij.testFramework.configurationStore.copyFilesAndReloadProject
import com.intellij.testFramework.rules.TempDirectory
import com.intellij.util.io.systemIndependentPath
import kotlinx.coroutines.runBlocking
import org.jdom.Element
import org.jetbrains.jps.model.serialization.JDomSerializationUtil
import org.jetbrains.jps.model.serialization.JpsProjectLoader
import org.junit.ClassRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
@RunsInEdt
@RunWith(Parameterized::class)
class AutomaticModuleUnloaderTest(private val reloadingMode: ReloadingMode) {
@Rule
@JvmField
val tempDir = TempDirectory()
@JvmField
@Rule
val disposableRule = DisposableRule()
@Test
fun `unload simple module`() {
val project = createProject()
createModule(project, "a")
createModule(project, "b")
val moduleManager = ModuleManager.getInstance(project)
moduleManager.setUnloadedModules(listOf("a"))
createModule(project, "c")
val moduleFiles = createNewModuleFiles(listOf("d")) {}
val newProject = reloadProjectWithNewModules(project, moduleFiles)
assertSameElements(ModuleManager.getInstance(newProject).unloadedModuleDescriptions.map { it.name }, "a", "d")
}
@Test
fun `unload modules with dependencies between them`() {
val project = createProject()
createModule(project, "a")
createModule(project, "b")
doTest(project, "a", listOf("c", "d"), { modules ->
ModuleRootModificationUtil.updateModel(modules.getValue("c")) {
it.addModuleOrderEntry(modules.getValue("d"))
}
}, "a", "c", "d")
}
@Test
fun `do not unload module if loaded module depends on it`() {
val project = createProject()
createModule(project, "a")
val b = createModule(project, "b")
ModuleRootModificationUtil.updateModel(b) {
it.addInvalidModuleEntry("d")
}
doTest(project, "a", listOf("d"), {}, "a")
}
@Test
fun `unload module if only unloaded module depends on it`() {
val project = createProject()
val a = createModule(project, "a")
createModule(project, "b")
ModuleRootModificationUtil.updateModel(a) {
it.addInvalidModuleEntry("d")
}
doTest(project, "a", listOf("d"), {}, "a", "d")
}
@Test
fun `do not unload modules if loaded module depends on them transitively`() {
val project = createProject()
createModule(project, "a")
val b = createModule(project, "b")
ModuleRootModificationUtil.updateModel(b) {
it.addInvalidModuleEntry("d")
}
doTest(project, "a", listOf("c", "d"), { modules ->
ModuleRootModificationUtil.updateModel(modules.getValue("d")) {
it.addModuleOrderEntry(modules.getValue("c"))
}
}, "a")
}
@Test
fun `unload module if loaded module transitively depends on it via previously unloaded module`() {
val project = createProject()
val a = createModule(project, "a")
val b = createModule(project, "b")
ModuleRootModificationUtil.addDependency(a, b)
ModuleRootModificationUtil.updateModel(b) {
it.addInvalidModuleEntry("c")
}
doTest(project, "b", listOf("c"), {}, "b", "c")
}
@Test
fun `deleted iml file`() {
val project = createProject()
createModule(project, "a")
createModule(project, "b")
val deletedIml = createModule(project, "deleted")
val moduleManager = ModuleManager.getInstance(project)
moduleManager.setUnloadedModules(listOf("a"))
createModule(project, "c")
val moduleFiles = createNewModuleFiles(listOf("d")) {}
val deletedImlFile = File(deletedIml.moduleFilePath)
val newProject = reloadProjectWithNewModules(project, moduleFiles) {
deletedImlFile.delete()
}
assertSameElements(ModuleManager.getInstance(newProject).unloadedModuleDescriptions.map { it.name }, "a", "d")
}
private fun doTest(project: Project,
initiallyUnloaded: String,
newModulesName: List<String>,
setup: (Map<String, Module>) -> Unit,
vararg expectedUnloadedModules: String) {
val moduleManager = ModuleManager.getInstance(project)
moduleManager.setUnloadedModules(listOf(initiallyUnloaded))
val moduleFiles = createNewModuleFiles(newModulesName, setup)
val newProject = reloadProjectWithNewModules(project, moduleFiles)
assertSameElements(ModuleManager.getInstance(newProject).unloadedModuleDescriptions.map { it.name }, *expectedUnloadedModules)
}
private fun createProject(): Project {
val project = ProjectManagerEx.getInstanceEx().openProject(tempDir.newDirectory("automaticReloaderTest").toPath(),
createTestOpenProjectOptions())
return project!!
}
private fun createModule(project: Project, moduleName: String): Module {
return runWriteAction { ModuleManager.getInstance(project).newModule("${project.basePath}/$moduleName.iml", "JAVA") }
}
private fun createNewModuleFiles(moduleNames: List<String>, setup: (Map<String, Module>) -> Unit): List<Path> {
val newModulesProjectDir = tempDir.newDirectory("newModules").toPath()
val moduleFiles = moduleNames.map { newModulesProjectDir.resolve("$it.iml") }
val project = ProjectManagerEx.getInstanceEx().newProject(newModulesProjectDir, createTestOpenProjectOptions())!!
try {
val moduleManager = ModuleManager.getInstance(project)
runWriteAction {
moduleFiles.map {
moduleManager.newModule(it.toAbsolutePath().toString(), StdModuleTypes.JAVA.id)
}
}
setup(moduleManager.modules.associateBy { it.name })
}
finally {
saveAndCloseProject(project)
}
return moduleFiles
}
private fun saveAndCloseProject(project: Project) {
PlatformTestUtil.saveProject(project, true)
ProjectManagerEx.getInstanceEx().forceCloseProject(project)
}
private fun reloadProjectWithNewModules(project: Project, moduleFiles: List<Path>, beforeReload: () -> Unit = {}): Project {
when (reloadingMode) {
ReloadingMode.ON_THE_FLY -> PlatformTestUtil.saveProject(project, true)
ReloadingMode.REOPEN -> saveAndCloseProject(project)
}
val modulesXmlFile = (project as ProjectStoreOwner).componentStore.getDirectoryStorePath()!!.resolve("modules.xml")
val rootElement = JDOMUtil.load(modulesXmlFile)
val moduleRootComponent = JDomSerializationUtil.findComponent(rootElement, JpsProjectLoader.MODULE_MANAGER_COMPONENT)
val modulesTag = moduleRootComponent!!.getChild("modules")!!
moduleFiles.forEach {
val filePath = it.systemIndependentPath
val fileUrl = VfsUtil.pathToUrl(filePath)
modulesTag.addContent(Element("module").setAttribute("fileurl", fileUrl).setAttribute("filepath", filePath))
}
when (reloadingMode) {
ReloadingMode.ON_THE_FLY -> {
val modulesXmlCopyDir = tempDir.newDirectory("modules-xml").toPath()
JDOMUtil.write(rootElement, modulesXmlCopyDir.resolve(".idea/modules.xml"))
beforeReload()
runBlocking {
copyFilesAndReloadProject(project, modulesXmlCopyDir)
}
disposableRule.register { PlatformTestUtil.forceCloseProjectWithoutSaving(project) }
return project
}
ReloadingMode.REOPEN -> {
JDOMUtil.write(rootElement, modulesXmlFile)
beforeReload()
return PlatformTestUtil.loadAndOpenProject(Paths.get(project.basePath!!), disposableRule.disposable)
}
}
}
companion object {
@ClassRule
@JvmField
val appRule = ApplicationRule()
@ClassRule
@JvmField
val edtRule = EdtRule()
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun modes(): Array<ReloadingMode> = ReloadingMode.values()
}
enum class ReloadingMode { ON_THE_FLY, REOPEN }
}