| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * 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 com.android.tools.idea.gradle.project; |
| |
| import com.android.SdkConstants; |
| import com.android.tools.idea.gradle.parser.GradleSettingsFile; |
| import com.android.tools.idea.gradle.util.GradleUtil; |
| import com.google.common.base.Function; |
| import com.google.common.base.Functions; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Maps; |
| import com.google.common.io.Files; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.options.ConfigurationException; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.project.ex.ProjectManagerEx; |
| import com.intellij.openapi.project.impl.ProjectManagerImpl; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.ThrowableComputable; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtil; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.testFramework.PlatformTestCase; |
| import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; |
| import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; |
| import com.intellij.testFramework.fixtures.JavaTestFixtureFactory; |
| import com.intellij.testFramework.fixtures.TestFixtureBuilder; |
| import com.intellij.util.PathUtil; |
| import org.jetbrains.android.AndroidTestBase; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.charset.Charset; |
| import java.util.*; |
| |
| /** |
| * <p>Test case for GradleProjectImport#importModules method. It requires substantially different setup then {@link }</p> |
| * <p/> |
| * To run this test case, configure: |
| * <ul><li>$ADT_TEST_SDK_PATH</li></ul> |
| */ |
| @SuppressWarnings("JUnitTestCaseWithNoTests") // Named differently, didn't want to do too much unnecessary setups |
| public final class GradleModuleImportTest extends AndroidTestBase { |
| public static final String MODULE_NAME = "guadeloupe"; |
| public static final String SAMPLE_PROJECT_PATH = "samples/sample1"; |
| public static final String SAMPLE_PROJECT_NAME = "sample1"; |
| public static final String BUILD_GRADLE_TEMPLATE = "apply plugin: 'android-library'\n\n" + |
| "dependencies {\n" + |
| " compile 'com.android.support:support-v4:13.0.+'\n" + |
| " %s\n" + |
| "}\n\n" + |
| "android {\n" + |
| " compileSdkVersion 19\n" + |
| " buildToolsVersion \"19\"\n\n" + |
| " defaultConfig {\n" + |
| " minSdkVersion 8\n" + |
| " targetSdkVersion 19\n" + |
| " }\n\n" + |
| " sourceSets {\n" + |
| " main {\n" + |
| " manifest.srcFile 'AndroidManifest.xml'\n" + |
| " java.srcDirs = ['src']\n" + |
| " res.srcDirs = ['res']\n" + |
| " }\n" + |
| " }\n" + |
| "}\n"; |
| private final static Function<String, String> pathToModuleName = new Function<String, String>() { |
| @Override |
| public String apply(String input) { |
| return pathToGradleName(input); |
| } |
| }; |
| private File dir; |
| |
| public static VirtualFile createGradleProjectToImport(File dir, String name, String... requiredProjects) throws IOException { |
| File moduleDir = new File(dir, name); |
| if (!moduleDir.mkdirs()) { |
| throw new IllegalStateException("Unable to create module"); |
| } |
| Iterable<String> projectDependencies = Iterables.transform(Arrays.asList(requiredProjects), new Function<String, String>() { |
| @Override |
| public String apply(String input) { |
| return String.format("\tcompile project('%s')", pathToGradleName(input)); |
| } |
| }); |
| String buildGradle = String.format(BUILD_GRADLE_TEMPLATE, Joiner.on("\n").join(projectDependencies)); |
| Files.write(buildGradle, new File(moduleDir, SdkConstants.FN_BUILD_GRADLE), Charset.defaultCharset()); |
| VirtualFile moduleFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(moduleDir.getAbsolutePath()); |
| if (moduleFile == null) { |
| throw new IllegalStateException("Cannot get virtual file for module we just created"); |
| } |
| else { |
| return moduleFile; |
| } |
| } |
| |
| private static void assertModuleInSettingsFile(Project project, String name) throws IOException { |
| GradleSettingsFile settingsFile = GradleSettingsFile.get(project); |
| assertNotNull("Missing " + SdkConstants.FN_SETTINGS_GRADLE, settingsFile); |
| Iterable<String> modules = settingsFile.getModules(); |
| if (!Iterables.contains(modules, name)) { |
| fail(String.format("Subproject %s is not in %s. Found subprojects: %s", name, SdkConstants.FN_SETTINGS_GRADLE, |
| Joiner.on(", ").join(modules))); |
| } |
| } |
| |
| private static void assertModuleImported(@NotNull Project project, @NotNull String relativePath, @NotNull VirtualFile moduleRoot) |
| throws IOException { |
| assertNotNull("Module sources were not copied", project.getBaseDir().findFileByRelativePath(relativePath)); |
| final VirtualFile[] moduleChildren = moduleRoot.getChildren(); |
| assertNoFilesAdded(moduleChildren); |
| assertEquals(SdkConstants.FN_BUILD_GRADLE, moduleChildren[0].getName()); |
| assertModuleInSettingsFile(project, pathToGradleName(relativePath)); |
| } |
| |
| private static void assertNoFilesAdded(VirtualFile[] moduleChildren) { |
| if (moduleChildren.length != 1) { |
| StringBuilder failure = new StringBuilder("Files were altered in the source directory:"); |
| Joiner.on(", ").appendTo(failure, Iterables.transform(Arrays.asList(moduleChildren), new Function<VirtualFile, String>() { |
| @Override |
| public String apply(VirtualFile input) { |
| return input.getName(); |
| } |
| })); |
| fail(failure.toString()); |
| } |
| } |
| |
| private static String module(int moduleNumber) { |
| return MODULE_NAME + moduleNumber; |
| } |
| |
| private static Map<String, String> projectsWithDefaultLocations(final String... paths) { |
| Iterable<String> names = Iterables.transform(Arrays.asList(paths), pathToModuleName); |
| return Maps.toMap(names, Functions.constant("")); |
| } |
| |
| private static String pathToGradleName(String input) { |
| return ":" + input.replaceAll("/", SdkConstants.GRADLE_PATH_SEPARATOR); |
| } |
| |
| private static void assertModuleRequiredButNotFound(String modulePath, Map<String, VirtualFile> projects) { |
| String moduleName = pathToGradleName(modulePath); |
| assertTrue(String.format("Project %s should be known but path not detected", modulePath), |
| projects.containsKey(moduleName) && projects.get(moduleName) == null); |
| } |
| |
| private static VirtualFile configureTopLevelProject(File projectRoot, |
| Iterable<String> allModules, |
| Iterable<String> customLocationStatements) throws IOException { |
| StringBuilder settingsGradle = new StringBuilder("include '"); |
| Joiner.on("', '").appendTo(settingsGradle, allModules).append("'\n"); |
| Joiner.on("\n").appendTo(settingsGradle, customLocationStatements).append("\n"); |
| |
| Files.write(settingsGradle.toString(), new File(projectRoot, SdkConstants.FN_SETTINGS_GRADLE), Charset.defaultCharset()); |
| VirtualFile vDir = VfsUtil.findFileByIoFile(projectRoot, true); |
| assert vDir != null; |
| System.out.printf("Multi-project root: %s\n", vDir.getPath()); |
| return vDir; |
| } |
| |
| @NotNull |
| private VirtualFile createProjectWithSubprojects(Map<String, String> modules, String... nonExistingReferencedModules) throws IOException { |
| Collection<String> customLocationStatements = new LinkedList<String>(); |
| |
| for (Map.Entry<String, String> module : modules.entrySet()) { |
| String path = module.getValue(); |
| if (Strings.isNullOrEmpty(path)) { |
| path = PathUtil.toSystemIndependentName(GradleUtil.getDefaultPhysicalPathFromGradlePath(module.getKey())); |
| } |
| else { |
| customLocationStatements.add(String.format("project('%s').projectDir = new File('%s')", module.getKey(), path)); |
| } |
| createGradleProjectToImport(dir, path); |
| } |
| Iterable<String> allModules = |
| Iterables.concat(modules.keySet(), Iterables.transform(Arrays.asList(nonExistingReferencedModules), pathToModuleName)); |
| return configureTopLevelProject(dir, allModules, customLocationStatements); |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| final TestFixtureBuilder<IdeaProjectTestFixture> projectBuilder = |
| IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName()); |
| myFixture = JavaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(projectBuilder.getFixture()); |
| myFixture.setUp(); |
| myFixture.setTestDataPath(getTestDataPath()); |
| |
| dir = new File(Files.createTempDir(), "project"); |
| |
| System.out.printf("Project location: %s\n", getProject().getBaseDir()); |
| } |
| |
| /** |
| * Test importing simple module into even simpler project |
| */ |
| public void testImportSimpleGradleProject() throws IOException, ConfigurationException { |
| VirtualFile moduleRoot = createGradleProjectToImport(dir, MODULE_NAME); |
| GradleModuleImporter.importModules(this, Collections.singletonMap(moduleRoot.getName(), moduleRoot), getProject(), null); |
| assertModuleImported(getProject(), MODULE_NAME, moduleRoot); |
| } |
| |
| /** |
| * Test importing a root project that has subprojects |
| */ |
| public void testImportSubprojects() throws IOException, ConfigurationException { |
| String[] paths = {module(1), module(2), SAMPLE_PROJECT_PATH}; |
| |
| VirtualFile projectRoot = createProjectWithSubprojects(projectsWithDefaultLocations(paths)); |
| Map<String, VirtualFile> toImport = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject())); |
| assertEquals(paths.length, toImport.size()); |
| for (String path : paths) { |
| assertEquals(projectRoot.findFileByRelativePath(path), toImport.get(pathToGradleName(path))); |
| } |
| |
| GradleModuleImporter.importModules(this, toImport, getProject(), null); |
| |
| for (String path : paths) { |
| VirtualFile moduleRoot = projectRoot.findFileByRelativePath(path); |
| assertNotNull(String.format("Module was not imported into %s\n", projectRoot.getPath() + "/" + path), moduleRoot); |
| assertModuleImported(getProject(), path, moduleRoot); |
| } |
| |
| System.out.println(); |
| } |
| |
| /** |
| * Missing sub-module will be on the list but with a <code>null</code> path. It is up to client code to decide what to do with it. |
| */ |
| public void testImportSubProjectsWithMissingSubModule() throws IOException, ConfigurationException { |
| VirtualFile projectRoot = createProjectWithSubprojects(projectsWithDefaultLocations(module(1)), module(2)); |
| Map<String, VirtualFile> toImport = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject())); |
| assertEquals(2, toImport.size()); |
| assertModuleRequiredButNotFound(module(2), toImport); |
| |
| try { |
| GradleModuleImporter.importModules(this, toImport, getProject(), null); |
| fail(); |
| } |
| catch (IOException e) { |
| // Expected |
| } |
| } |
| |
| /** |
| * Verify discovery of projects with non-default locations |
| */ |
| public void testImportSubProjectWithCustomLocation() throws IOException, ConfigurationException { |
| VirtualFile projectRoot = |
| createProjectWithSubprojects(Collections.singletonMap(pathToGradleName(SAMPLE_PROJECT_NAME), SAMPLE_PROJECT_PATH)); |
| Map<String, VirtualFile> subProjects = moduleListToMap(GradleModuleImporter.getRelatedProjects(projectRoot, getProject())); |
| assertEquals(1, subProjects.size()); |
| VirtualFile moduleLocation = projectRoot.findFileByRelativePath(SAMPLE_PROJECT_PATH); |
| assert moduleLocation != null; |
| assertEquals(moduleLocation, subProjects.get(pathToGradleName(SAMPLE_PROJECT_NAME))); |
| |
| GradleModuleImporter.importModules(this, subProjects, getProject(), null); |
| assertModuleImported(getProject(), SAMPLE_PROJECT_NAME, moduleLocation); |
| } |
| |
| private static Map<String, VirtualFile> moduleListToMap(Set<ModuleToImport> projects) { |
| HashMap<String, VirtualFile> map = Maps.newHashMapWithExpectedSize(projects.size()); |
| for (ModuleToImport project : projects) { |
| map.put(project.name, project.location); |
| } |
| return map; |
| } |
| |
| /** |
| * Verify basic case of importing a project that has source dependency |
| */ |
| public void testRequiredProjects() throws IOException { |
| VirtualFile project1 = createGradleProjectToImport(dir, module(1)); |
| VirtualFile project2 = createGradleProjectToImport(dir, module(2), module(1)); |
| assert project1 != null && project2 != null : "Something wrong with the setup"; |
| configureTopLevelProject(dir, Arrays.asList(module(1), module(2)), Collections.<String>emptySet()); |
| |
| Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project2, getProject())); |
| assertEquals(2, projects.size()); |
| assertEquals(project1, projects.get(pathToGradleName(module(1)))); |
| assertEquals(project2, projects.get(pathToGradleName(module(2)))); |
| } |
| |
| /** |
| * Test importing a project has source dependency but when that dependency directory is missing |
| */ |
| public void testMissingRequiredProjects() throws IOException { |
| VirtualFile project2 = createGradleProjectToImport(dir, module(2), module(1)); |
| assert project2 != null : "Something wrong with the setup"; |
| configureTopLevelProject(dir, Arrays.asList(module(1), module(2)), Collections.<String>emptySet()); |
| |
| Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project2, getProject())); |
| assertEquals(2, projects.size()); |
| assertModuleRequiredButNotFound(module(1), projects); |
| assertEquals(project2, projects.get(pathToGradleName(module(2)))); |
| } |
| |
| /** |
| * Test a project that requires another project but when no settings.gradle was found in the parent folder. |
| */ |
| public void testMissingEnclosingProject() throws IOException { |
| VirtualFile module = createGradleProjectToImport(dir, module(1), module(2)); |
| assert module != null; |
| |
| Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(module, getProject())); |
| assertEquals(2, projects.size()); |
| assertModuleRequiredButNotFound(module(2), projects); |
| assertEquals(module, projects.get(pathToGradleName(module(1)))); |
| } |
| |
| /** |
| * Make sure source dependencies are picked recursively |
| */ |
| public void testTransitiveDependencies() throws IOException { |
| VirtualFile project1 = createGradleProjectToImport(dir, module(1)); |
| VirtualFile project2 = createGradleProjectToImport(dir, module(2), module(1)); |
| VirtualFile project3 = createGradleProjectToImport(dir, module(3), module(2)); |
| configureTopLevelProject(dir, Arrays.asList(module(1), module(2), module(3)), Collections.<String>emptySet()); |
| |
| Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project3, getProject())); |
| assertEquals(3, projects.size()); |
| assertEquals(project1, projects.get(pathToGradleName(module(1)))); |
| assertEquals(project2, projects.get(pathToGradleName(module(2)))); |
| assertEquals(project3, projects.get(pathToGradleName(module(3)))); |
| } |
| |
| /** |
| * Make sure source dependencies are picked recursively |
| */ |
| public void testCircularDependencies() throws IOException { |
| VirtualFile project1 = createGradleProjectToImport(dir, module(1), module(3)); |
| VirtualFile project2 = createGradleProjectToImport(dir, module(2), module(1)); |
| VirtualFile project3 = createGradleProjectToImport(dir, module(3), module(2)); |
| configureTopLevelProject(dir, Arrays.asList(module(1), module(2), module(3)), Collections.<String>emptySet()); |
| |
| Map<String, VirtualFile> projects = moduleListToMap(GradleModuleImporter.getRelatedProjects(project3, getProject())); |
| assertEquals(3, projects.size()); |
| assertEquals(project1, projects.get(pathToGradleName(module(1)))); |
| assertEquals(project2, projects.get(pathToGradleName(module(2)))); |
| assertEquals(project3, projects.get(pathToGradleName(module(3)))); |
| } |
| |
| /** |
| * {@link ProjectManagerEx} |
| */ |
| @Override |
| protected void tearDown() throws Exception { |
| if (myFixture != null) { |
| myFixture.tearDown(); |
| myFixture = null; |
| } |
| ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); |
| Project[] openProjects = projectManager.getOpenProjects(); |
| if (openProjects.length > 0) { |
| final Project project = openProjects[0]; |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| Disposer.dispose(project); |
| ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); |
| if (projectManager instanceof ProjectManagerImpl) { |
| Collection<Project> projectsStillOpen = projectManager.closeTestProject(project); |
| if (!projectsStillOpen.isEmpty()) { |
| Project project = projectsStillOpen.iterator().next(); |
| projectsStillOpen.clear(); |
| throw new AssertionError("Test project is not disposed: " + project + ";\n created in: " + |
| PlatformTestCase.getCreationPlace(project)); |
| } |
| } |
| } |
| }); |
| } |
| if (dir != null && dir.isDirectory()) { |
| ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<Boolean, IOException>() { |
| @Override |
| public Boolean compute() throws IOException { |
| VirtualFile vfile = VfsUtil.findFileByIoFile(dir, true); |
| if (vfile != null) { |
| vfile.delete(GradleModuleImportTest.this); |
| } |
| return true; |
| } |
| }); |
| } |
| super.tearDown(); |
| } |
| } |