blob: 441e257b9121f5645f3bd48ca4babc87621a66cf [file]
/*
* Copyright 2000-2026 JetBrains s.r.o. and contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.grails;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModuleOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.SourceFolder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.UsefulTestCase;
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture;
import com.intellij.testFramework.fixtures.JavaTestFixtureFactory;
import com.intellij.testFramework.fixtures.TestFixtureBuilder;
import junit.framework.TestCase;
import org.jetbrains.plugins.grails.config.GrailsFramework;
import org.jetbrains.plugins.grails.structure.GrailsApplicationManager;
import org.jetbrains.plugins.grails.structure.GrailsApplicationProvider;
import org.jetbrains.plugins.groovy.mvc.MvcModuleStructureSynchronizer;
import org.jetbrains.plugins.groovy.mvc.MvcModuleStructureUtil;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Future;
@SuppressWarnings("GrMethodMayBeStatic")
public class GrailsProjectStructureTest extends UsefulTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
MvcModuleStructureSynchronizer.ourGrailsTestFlag = true;
final TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder =
IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName());
myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture());
String appDir = myFixture.getTempDirPath() + "/testApp";
new File(appDir).mkdirs();
final JavaModuleFixtureBuilder<?> moduleFixtureBuilder = projectBuilder.addModule(JavaModuleFixtureBuilder.class);
moduleFixtureBuilder.addSourceContentRoot(appDir);
myFixture.setUp();
myModule = moduleFixtureBuilder.getFixture().getModule();
PsiTestUtil.addLibrary(myModule, "Grails", GrailsTestUtil.getMockGrails11LibraryHome(), "/dist/grails-core-1.1.jar");
GrailsApplicationProvider.APPLICATION_PROVIDER.getPoint()
.registerExtension(new TestGrailsApplicationProvider(), myFixture.getTestRootDisposable());
}
@Override
protected void tearDown() throws Exception {
MvcModuleStructureSynchronizer.ourGrailsTestFlag = false;
try {
myFixture.tearDown();
}
catch (Throwable e) {
addSuppressedException(e);
}
finally {
myFixture = null;
myModule = null;
super.tearDown();
}
}
protected Project getProject() {
return myFixture.getProject();
}
private static String defStructure(String prefix) {
return "content:" + prefix + "\n" +
" source:" + prefix + "/grails-app/controllers\n" +
" source:" + prefix + "/grails-app/domain\n" +
" source:" + prefix + "/grails-app/services\n" +
" source:" + prefix + "/grails-app/taglib\n" +
" source:" + prefix + "/src/groovy\n" +
" source:" + prefix + "/src/java\n" +
" source:" + prefix + "/test/integration\n" +
" source:" + prefix + "/test/unit";
}
private void grailsApp(String prefix) throws IOException {
GrailsTestUtil.createGrailsApplication(myFixture, prefix, false);
}
private void grailsPlugin(String prefix) throws IOException {
grailsApp(prefix);
GrailsTestUtil.createPluginXml(myFixture, prefix);
}
private static Module[] sort(Module[] modules) {
Module[] res = modules.clone();
Arrays.sort(res, moduleComparator);
return res;
}
private void assertStructure(String s) {
updateStructure();
final VirtualFile rootFile = myFixture.getTempDirFixture().getFile("");
StringBuilder dump = new StringBuilder("\n");
ModuleManager moduleManager = ModuleManager.getInstance(getProject());
for (Module module : sort(moduleManager.getModules())) {
dump.append("module:").append(module.getName());
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
Map<Module, Boolean> dependencies = new TreeMap<>(moduleComparator);
for (OrderEntry oe : moduleRootManager.getOrderEntries()) {
if (oe instanceof ModuleOrderEntry) {
dependencies.put(((ModuleOrderEntry)oe).getModule(), ((ModuleOrderEntry)oe).isExported());
}
}
if (!dependencies.isEmpty()) {
dump.append(" [");
for (Map.Entry<Module, Boolean> dep : dependencies.entrySet()) {
if (dump.charAt(dump.length() - 1) != '[') {
dump.append(", ");
}
dump.append(dep.getKey().getName());
if (dep.getValue()) {
dump.append("*");
}
}
dump.append("]");
}
dump.append("\n");
Set<ContentEntry> contentEntries = new TreeSet<>(Comparator.comparing(it -> it.getFile().getPath()));
contentEntries.addAll(List.of(moduleRootManager.getContentEntries()));
for (ContentEntry entry : contentEntries) {
dump.append(" content:").append(prettyPath(rootFile, entry.getFile())).append("\n");
Set<SourceFolder> folders = new TreeSet<>(Comparator.comparing(it -> it.getFile().getPath()));
folders.addAll(List.of(entry.getSourceFolders()));
for (SourceFolder folder : folders) {
dump.append(" source:").append(prettyPath(rootFile, folder.getFile())).append("\n");
}
}
}
TestCase.assertEquals(s, dump.toString().trim());
}
private void updateStructure() {
Future<?> futureUpdate = GrailsApplicationManager.getInstance(getProject()).queueUpdate();
PlatformTestUtil.waitForFuture((Future<?>)futureUpdate, 5 * 60 * 1000);
MvcModuleStructureSynchronizer.forceUpdateProject(getProject());
}
private static String prettyPath(VirtualFile rootFile, final VirtualFile file) {
final String s = VfsUtilCore.getRelativePath(file, rootFile, '/');
if (StringUtil.isEmpty(s)) {
return ".";
}
return s;
}
private String getModuleName() {
return myModule.getName();
}
private void setProperties(String prefix, String text) {
myFixture.saveText(myFixture.getTempDirFixture().getFile(prefix + "/application.properties"), text);
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
}
public void createModule(final String moduleName, final String path) {
ApplicationManager.getApplication().runWriteAction(() -> {
Module newModule = MvcModuleStructureUtil.createAuxiliaryModule(myModule, moduleName, GrailsFramework.getInstance());
PsiTestUtil.addContentRoot(newModule, myFixture.getTempDirFixture().getFile(path));
MvcModuleStructureUtil.removeDependency(myModule, newModule);
});
}
public void testCommonPlugins() throws IOException {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.work.dir", "../grailsWD"));
setProperties("testApp", "plugins.zzz=1.0");
grailsPlugin("grailsWD/plugins/zzz-1.0");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("grailsWD/plugins/zzz-1.0"));
ApplicationManager.getApplication()
.runWriteAction(() -> {
try {
myFixture.getTempDirFixture().getFile("grailsWD/plugins/zzz-1.0").delete(null);
}
catch (IOException e) {
throw new RuntimeException(e);
}
});
setProperties("testApp", "");
assertStructure("module:" + getModuleName() + "\n " +
defStructure("testApp"));
}
public void testCommonPluginsInCustomPlace() throws IOException {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.plugins.dir", "../myplugins"));
setProperties("testApp", "plugins.zzz=1.2");
grailsPlugin("myplugins/zzz-1.2");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("myplugins/zzz-1.2"));
}
public void testMarkingTestSourceRootAsTestSourceRoot() throws IOException {
grailsApp("testApp");
VirtualFile testFolder = myFixture.getTempDirFixture().findOrCreateDir("testApp/test/unit");
ModuleRootManager rootManager = ModuleRootManager.getInstance(myFixture.getModule());
PsiTestUtil.addSourceRoot(myFixture.getModule(), testFolder);
updateStructure();
assertContainsElements(List.of(rootManager.getSourceRoots(true)), testFolder);
assertFalse(List.of(rootManager.getSourceRoots(false)).contains(testFolder));
}
public void testCustomGrailsWorkDir() throws Exception {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.work.dir", "../gwd"));
setProperties("testApp", "plugins.zzz=4.2");
grailsPlugin("gwd/projects/testApp/plugins/zzz-4.2");
grailsPlugin("gwd/global-plugins/glob");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, GrailsGlobalPlugins]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins [GrailsGlobalPlugins]\n " +
defStructure("gwd/projects/testApp/plugins/zzz-4.2") + "\n" +
"module:GrailsGlobalPlugins\n " +
defStructure("gwd/global-plugins/glob"));
}
public void testCustomGlobalPluginsDir() throws Exception {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.global.plugins.dir", "../gpd"));
grailsPlugin("gpd/glob1");
grailsPlugin("gpd/glob2");
assertStructure("module:" + getModuleName() + " [GrailsGlobalPlugins]\n " +
defStructure("testApp") + "\n" +
"module:GrailsGlobalPlugins\n " +
defStructure("gpd/glob1") + "\n " +
defStructure("gpd/glob2"));
}
public void testCustomPluginSomewhere() throws Exception {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of(), Map.of("paphos", "../paphos"));
grailsApp("paphos");
assertStructure("module:" + getModuleName() + " [paphos-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:paphos-inplacePlugin\n " +
defStructure("paphos"));
}
public void testAllowConfAsSourceRoot() throws Exception {
grailsApp("testApp");
PsiTestUtil.addSourceRoot(myFixture.getModule(), myFixture.getTempDirFixture().getFile("testApp/grails-app/conf"));
assertStructure("module:" + getModuleName() + "\n " +
defStructure("testApp").replaceFirst("\n", "\n source:testApp/grails-app/conf\n"));
}
public void testCustomPluginDependencies() throws Exception {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.work.dir", "../gwd"), Map.of("cust", "../cust"));
setProperties("testApp", "plugins.zzz=4.2");
grailsPlugin("gwd/projects/testApp/plugins/zzz-4.2");
grailsApp("cust");
setProperties("cust", "plugins.zzz=4.2");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, cust-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("gwd/projects/testApp/plugins/zzz-4.2") + "\n" +
"module:cust-inplacePlugin [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("cust"));
}
public void testCustomPluginCustomModuleDependencies() throws Exception {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.work.dir", "../gwd"), Map.of("cust", "../cust"));
grailsPlugin("gwd/projects/testApp/plugins/zzz-4.2");
grailsApp("cust");
createModule("Cust", "cust");
setProperties("cust", "plugins.zzz=4.2 ");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, Cust*]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("gwd/projects/testApp/plugins/zzz-4.2") + "\n" +
"module:Cust [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("cust"));
}
public void testRemoveCustomPlugin() throws IOException {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.work.dir", "../grailsWD"),
Map.of("myCustomPlugin1", "../customPlugins/myCustomPlugin1",
"myCustomPlugin2", "../customPlugins/my-custom-plugin-2"));
grailsApp("customPlugins/myCustomPlugin1");
grailsApp("customPlugins/my-custom-plugin-2");
grailsPlugin("grailsWD/projects/testApp/plugins/zzz-4.2");
myFixture.addFileToProject("customPlugins/myCustomPlugin1/MyCustomPlugin1GrailsPlugin.groovy", "");
setProperties("testApp", "plugins.myCustomPlugin1=1.0\nplugins.myCustomPlugin2=1.0\nplugins.zzz=4.2");
assertStructure("module:" + getModuleName() + " [myCustomPlugin1-inplacePlugin*, myCustomPlugin2-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:myCustomPlugin1-inplacePlugin\n " +
defStructure("customPlugins/myCustomPlugin1") + "\n" +
"module:myCustomPlugin2-inplacePlugin\n " +
defStructure("customPlugins/my-custom-plugin-2"));
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.work.dir", "../grailsWD"));
setProperties("testApp", "plugins.zzz=4.2");
assertStructure("module:" + getModuleName() + " [myCustomPlugin2-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:myCustomPlugin1-inplacePlugin\n " +
defStructure("customPlugins/myCustomPlugin1") + "\n" +
"module:myCustomPlugin2-inplacePlugin\n " +
defStructure("customPlugins/my-custom-plugin-2"));
}
public void testRemovingCommonPluginModuleAfterRemovingMainModule() throws IOException {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.work.dir", "../grailsWD"),
Map.of("myCustomPlugin1", "../customPlugins/myCustomPlugin1"));
setProperties("testApp", "plugins.myCustomPlugin1=1.0\nplugins.zzz=1.0");
grailsApp("customPlugins/myCustomPlugin1");
grailsPlugin("grailsWD/plugins/zzz-1.0");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, myCustomPlugin1-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("grailsWD/plugins/zzz-1.0") + "\n" +
"module:myCustomPlugin1-inplacePlugin [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("customPlugins/myCustomPlugin1"));
ApplicationManager.getApplication().runWriteAction(() -> MvcModuleStructureUtil.removeAuxiliaryModule(myModule));
assertStructure("module:myCustomPlugin1-inplacePlugin\n " +
defStructure("customPlugins/myCustomPlugin1"));
}
public void testMultiTest() throws IOException {
grailsApp("testApp");
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.work.dir", "../grailsWD"),
Map.of("plugin1", "../customPlugins/plugin1",
"plugin2", "../customPlugins/plugin2"));
grailsPlugin("grailsWD/plugins/zzz-1.0");
grailsApp("customPlugins/plugin1");
myFixture.addFileToProject("customPlugins/plugin1/Plugin1GrailsPlugin.groovy", "");
grailsApp("customPlugins/plugin2");
myFixture.addFileToProject("customPlugins/plugin2/Plugin2GrailsPlugin.groovy", "");
setProperties("testApp", "plugins.plugin1=1.0\nplugins.plugin2=1.0\nplugins.zzz=1.0");
assertStructure(
"module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, plugin1-inplacePlugin*, plugin2-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("grailsWD/plugins/zzz-1.0") + "\n" +
"module:plugin1-inplacePlugin [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("customPlugins/plugin1") + "\n" +
"module:plugin2-inplacePlugin [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("customPlugins/plugin2"));
GrailsTestUtil.createBuildConfig(myFixture, "testApp", Map.of("grails.project.work.dir", "../grailsWD"),
Map.of("plugin1", "../customPlugins/plugin1"));
GrailsTestUtil.createBuildConfig(myFixture, "customPlugins/plugin1", Map.of(),
Map.of("plugin2", "../plugin2"));
setProperties("testApp", "plugins.plugin1=1.0\nplugins.plugin2=1.0\nplugins.zzz=1.0 ");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, plugin1-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("grailsWD/plugins/zzz-1.0") + "\n" +
"module:plugin1-inplacePlugin [" + getModuleName() + "-grailsPlugins, plugin2-inplacePlugin*]\n " +
defStructure("customPlugins/plugin1") + "\n" +
"module:plugin2-inplacePlugin [" + getModuleName() + "-grailsPlugins]\n " +
defStructure("customPlugins/plugin2"));
grailsApp("root2");
GrailsTestUtil.createBuildConfig(myFixture, "root2", Map.of("grails.project.work.dir", "../grailsWD2"),
Map.of("plugin1", "../customPlugins/plugin1"));
createModule("Root2", "root2");
grailsPlugin("grailsWD2/plugins/xxx-1.0");
setProperties("testApp", " ");
assertStructure("module:" + getModuleName() + " [" + getModuleName() + "-grailsPlugins, plugin1-inplacePlugin*]\n " +
defStructure("testApp") + "\n" +
"module:" + getModuleName() + "-grailsPlugins\n " +
defStructure("grailsWD/plugins/zzz-1.0") + "\n" +
"module:Root2 [Root2-grailsPlugins, plugin1-inplacePlugin*]\n " +
defStructure("root2") + "\n" +
"module:Root2-grailsPlugins\n " +
defStructure("grailsWD2/plugins/xxx-1.0") + "\n" +
"module:plugin1-inplacePlugin [" + getModuleName() + "-grailsPlugins, Root2-grailsPlugins, plugin2-inplacePlugin*]\n " +
defStructure("customPlugins/plugin1") + "\n" +
"module:plugin2-inplacePlugin [" + getModuleName() + "-grailsPlugins, Root2-grailsPlugins]\n " +
defStructure("customPlugins/plugin2"));
}
private JavaCodeInsightTestFixture myFixture;
private Module myModule;
private static final Comparator<Module> moduleComparator = Comparator.comparing(Module::getName);
}