blob: d574ad995fbd9b8d36fbf18d11b99340e2da99a7 [file] [log] [blame]
/*
* 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.tests.gui.framework;
import com.android.SdkConstants;
import com.android.tools.idea.gradle.project.GradleExperimentalSettings;
import com.android.tools.idea.gradle.project.GradleProjectImporter;
import com.android.tools.idea.gradle.util.GradleUtil;
import com.android.tools.idea.gradle.util.LocalProperties;
import com.android.tools.idea.sdk.IdeSdks;
import com.android.tools.idea.templates.AndroidGradleTestCase;
import com.android.tools.idea.tests.gui.framework.fixture.FileChooserDialogFixture;
import com.android.tools.idea.tests.gui.framework.fixture.IdeFrameFixture;
import com.android.tools.idea.tests.gui.framework.fixture.WelcomeFrameFixture;
import com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.NewProjectWizardFixture;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.WindowManagerImpl;
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame;
import com.intellij.util.SystemProperties;
import com.intellij.util.net.HttpConfigurable;
import org.fest.swing.core.BasicRobot;
import org.fest.swing.core.Robot;
import org.fest.swing.edt.GuiQuery;
import org.fest.swing.edt.GuiTask;
import org.fest.swing.timing.Condition;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.xpath.XPath;
import org.jetbrains.android.AndroidPlugin.GuiTestSuiteState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.runner.RunWith;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
import static com.android.SdkConstants.GRADLE_LATEST_VERSION;
import static com.android.tools.idea.tests.gui.framework.GuiTestRunner.canRunGuiTests;
import static com.android.tools.idea.tests.gui.framework.GuiTests.*;
import static com.intellij.ide.impl.ProjectUtil.closeAndDispose;
import static com.intellij.openapi.project.ProjectCoreUtil.DIRECTORY_BASED_PROJECT_DIR;
import static com.intellij.openapi.util.io.FileUtil.*;
import static com.intellij.openapi.util.io.FileUtilRt.delete;
import static com.intellij.openapi.vfs.VfsUtil.findFileByIoFile;
import static com.intellij.openapi.vfs.VfsUtilCore.virtualToIoFile;
import static junit.framework.Assert.assertNotNull;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.swing.edt.GuiActionRunner.execute;
import static org.fest.swing.timing.Pause.pause;
import static org.fest.util.Strings.quote;
import static org.jetbrains.android.AndroidPlugin.getGuiTestSuiteState;
import static org.junit.Assert.assertTrue;
@RunWith(GuiTestRunner.class)
public abstract class GuiTestCase {
protected Robot myRobot;
@SuppressWarnings("UnusedDeclaration") // This field is set via reflection.
private String myTestName;
/**
* @return the name of the test method being executed.
*/
protected String getTestName() {
return myTestName;
}
@Before
public void setUp() throws Exception {
if (!canRunGuiTests()) {
// We currently do not support running UI tests in headless environments.
return;
}
Application application = ApplicationManager.getApplication();
assertNotNull(application); // verify that we are using the IDE's ClassLoader.
setUpDefaultProjectCreationLocationPath();
myRobot = BasicRobot.robotWithCurrentAwtHierarchy();
myRobot.settings().delayBetweenEvents(30);
setIdeSettings();
}
private static void setIdeSettings() {
GradleExperimentalSettings.getInstance().SELECT_MODULES_ON_PROJECT_IMPORT = false;
// Clear HTTP proxy settings, in case a test changed them.
HttpConfigurable ideSettings = HttpConfigurable.getInstance();
ideSettings.USE_HTTP_PROXY = false;
ideSettings.PROXY_HOST = "";
ideSettings.PROXY_PORT = 80;
GuiTestSuiteState state = getGuiTestSuiteState();
state.setSkipSdkMerge(false);
state.setUseCachedGradleModelOnly(false);
}
@After
public void tearDown() {
if (myRobot != null) {
myRobot.cleanUpWithoutDisposingWindows();
}
}
@AfterClass
public static void tearDownPerClass() {
boolean inSuite = SystemProperties.getBooleanProperty(GUI_TESTS_RUNNING_IN_SUITE_PROPERTY, false);
if (!inSuite) {
IdeTestApplication.disposeInstance();
}
}
@NotNull
protected WelcomeFrameFixture findWelcomeFrame() {
return WelcomeFrameFixture.find(myRobot);
}
@NotNull
protected NewProjectWizardFixture findNewProjectWizard() {
return NewProjectWizardFixture.find(myRobot);
}
@NotNull
protected IdeFrameFixture findIdeFrame(@NotNull String projectName, @NotNull File projectPath) {
return IdeFrameFixture.find(myRobot, projectPath, projectName);
}
@SuppressWarnings("UnusedDeclaration")
// Called by GuiTestRunner via reflection.
protected void closeAllProjects() {
pause(new Condition("Close all projects") {
@Override
public boolean test() {
final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
execute(new GuiTask() {
@Override
protected void executeInEDT() throws Throwable {
for (Project project : openProjects) {
assertTrue("Failed to close project " + quote(project.getName()), closeAndDispose(project));
}
}
});
return ProjectManager.getInstance().getOpenProjects().length == 0;
}
}, SHORT_TIMEOUT);
//noinspection ConstantConditions
boolean welcomeFrameShown = execute(new GuiQuery<Boolean>() {
@Override
protected Boolean executeInEDT() throws Throwable {
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
if (openProjects.length == 0) {
WelcomeFrame.showNow();
WindowManagerImpl windowManager = (WindowManagerImpl)WindowManager.getInstance();
windowManager.disposeRootFrame();
return true;
}
return false;
}
});
if (welcomeFrameShown) {
pause(new Condition("'Welcome' frame to show up") {
@Override
public boolean test() {
for (Frame frame : Frame.getFrames()) {
if (frame instanceof WelcomeFrame && frame.isShowing()) {
return true;
}
}
return false;
}
}, SHORT_TIMEOUT);
}
}
@NotNull
protected IdeFrameFixture importSimpleApplication() throws IOException {
return importProjectAndWaitForProjectSyncToFinish("SimpleApplication");
}
@NotNull
protected IdeFrameFixture importProjectAndWaitForProjectSyncToFinish(@NotNull String projectDirName) throws IOException {
File projectPath = setUpProject(projectDirName, false, true, null);
VirtualFile toSelect = findFileByIoFile(projectPath, true);
assertNotNull(toSelect);
GuiTestSuiteState testSuiteState = getGuiTestSuiteState();
if (!testSuiteState.isImportProjectWizardAlreadyTested()) {
testSuiteState.setImportProjectWizardAlreadyTested(true);
findWelcomeFrame().clickImportProjectButton();
FileChooserDialogFixture importProjectDialog = FileChooserDialogFixture.findImportProjectDialog(myRobot);
return openProjectAndWaitUntilOpened(toSelect, importProjectDialog);
}
doImportProject(toSelect);
IdeFrameFixture projectFrame = findIdeFrame(projectPath);
projectFrame.waitForGradleProjectSyncToFinish();
return projectFrame;
}
@NotNull
protected File importProject(@NotNull String projectDirName) throws IOException {
File projectPath = setUpProject(projectDirName, false, true, null);
VirtualFile toSelect = findFileByIoFile(projectPath, true);
assertNotNull(toSelect);
doImportProject(toSelect);
return projectPath;
}
private static void doImportProject(@NotNull final VirtualFile projectDir) {
execute(new GuiTask() {
@Override
protected void executeInEDT() throws Throwable {
GradleProjectImporter.getInstance().importProject(projectDir);
}
});
}
@NotNull
private IdeFrameFixture openProject(@NotNull String projectDirName) throws IOException {
File projectPath = setUpProject(projectDirName, true, true, null);
return openProject(projectPath);
}
@NotNull
private IdeFrameFixture openProject(@NotNull final File projectPath) {
VirtualFile toSelect = findFileByIoFile(projectPath, true);
assertNotNull(toSelect);
GuiTestSuiteState state = getGuiTestSuiteState();
if (!state.isOpenProjectWizardAlreadyTested()) {
state.setOpenProjectWizardAlreadyTested(true);
findWelcomeFrame().clickOpenProjectButton();
FileChooserDialogFixture openProjectDialog = FileChooserDialogFixture.findOpenProjectDialog(myRobot);
return openProjectAndWaitUntilOpened(toSelect, openProjectDialog);
}
execute(new GuiTask() {
@Override
protected void executeInEDT() throws Throwable {
ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
projectManager.loadAndOpenProject(projectPath.getPath());
}
});
IdeFrameFixture projectFrame = findIdeFrame(projectPath);
projectFrame.waitForGradleProjectSyncToFinish();
return projectFrame;
}
/**
* Sets up a project before using it in a UI test:
* <ul>
* <li>Makes a copy of the project in testData/guiTests/newProjects (deletes any existing copy of the project first.) This copy is
* the one the test will use.</li>
* <li>Creates a Gradle wrapper for the test project.</li>
* <li>Updates the version of the Android Gradle plug-in used by the project, if applicable</li>
* <li>Creates a local.properties file pointing to the Android SDK path specified by the system property (or environment variable)
* 'ADT_TEST_SDK_PATH'</li>
* <li>Copies over missing files to the .idea directory (if the project will be opened, instead of imported.)</li>
* <li>Deletes .idea directory, .iml files and build directories, if the project will be imported.</li>
* <p/>
* </ul>
*
* @param projectDirName the name of the project's root directory. Tests are located in testData/guiTests.
* @param forOpen indicates whether the project will be opened by the IDE, or imported.
* @param updateAndroidPluginVersion indicates if the latest supported version of the Android Gradle plug-in should be set in the
* project.
* @param gradleVersion the Gradle version to use in the wrapper. If {@code null} is passed, this method will use the latest supported
* version of Gradle.
* @return the path of project's root directory (the copy of the project, not the original one.)
* @throws IOException if an unexpected I/O error occurs.
*/
@NotNull
private File setUpProject(@NotNull String projectDirName,
boolean forOpen,
boolean updateAndroidPluginVersion,
@Nullable String gradleVersion) throws IOException {
File projectPath = copyProjectBeforeOpening(projectDirName);
File gradlePropertiesFilePath = new File(projectPath, SdkConstants.FN_GRADLE_PROPERTIES);
if (gradlePropertiesFilePath.isFile()) {
delete(gradlePropertiesFilePath);
}
if (gradleVersion == null) {
createGradleWrapper(projectPath, GRADLE_LATEST_VERSION);
}
else {
createGradleWrapper(projectPath, gradleVersion);
}
if (updateAndroidPluginVersion) {
updateGradleVersions(projectPath);
}
updateLocalProperties(projectPath);
if (forOpen) {
File toDotIdea = new File(projectPath, DIRECTORY_BASED_PROJECT_DIR);
ensureExists(toDotIdea);
File fromDotIdea = new File(getTestProjectsRootDirPath(), join("commonFiles", DIRECTORY_BASED_PROJECT_DIR));
assertThat(fromDotIdea).isDirectory();
for (File from : notNullize(fromDotIdea.listFiles())) {
if (from.isDirectory()) {
File destination = new File(toDotIdea, from.getName());
if (!destination.isDirectory()) {
copyDirContent(from, destination);
}
continue;
}
File to = new File(toDotIdea, from.getName());
if (!to.isFile()) {
copy(from, to);
}
}
}
else {
cleanUpProjectForImport(projectPath);
}
return projectPath;
}
@NotNull
protected File copyProjectBeforeOpening(@NotNull String projectDirName) throws IOException {
File masterProjectPath = getMasterProjectDirPath(projectDirName);
File projectPath = getTestProjectDirPath(projectDirName);
delete(projectPath);
copyDir(masterProjectPath, projectPath);
return projectPath;
}
protected boolean createGradleWrapper(@NotNull File projectDirPath, @NotNull String gradleVersion) throws IOException {
return GradleUtil.createGradleWrapper(projectDirPath, gradleVersion);
}
protected void updateLocalProperties(File projectPath) throws IOException {
File androidHomePath = IdeSdks.getAndroidSdkPath();
assertNotNull(androidHomePath);
LocalProperties localProperties = new LocalProperties(projectPath);
localProperties.setAndroidSdkPath(androidHomePath);
localProperties.save();
}
protected void updateGradleVersions(@NotNull File projectPath) throws IOException {
AndroidGradleTestCase.updateGradleVersions(projectPath);
}
@NotNull
protected File getMasterProjectDirPath(@NotNull String projectDirName) {
return new File(getTestProjectsRootDirPath(), projectDirName);
}
@NotNull
protected File getTestProjectDirPath(@NotNull String projectDirName) {
return new File(getProjectCreationDirPath(), projectDirName);
}
protected void cleanUpProjectForImport(@NotNull File projectPath) {
File dotIdeaFolderPath = new File(projectPath, DIRECTORY_BASED_PROJECT_DIR);
if (dotIdeaFolderPath.isDirectory()) {
File modulesXmlFilePath = new File(dotIdeaFolderPath, "modules.xml");
if (modulesXmlFilePath.isFile()) {
SAXBuilder saxBuilder = new SAXBuilder();
try {
Document document = saxBuilder.build(modulesXmlFilePath);
XPath xpath = XPath.newInstance("//*[@fileurl]");
//noinspection unchecked
List<Element> modules = xpath.selectNodes(document);
int urlPrefixSize = "file://$PROJECT_DIR$/".length();
for (Element module : modules) {
String fileUrl = module.getAttributeValue("fileurl");
if (!StringUtil.isEmpty(fileUrl)) {
String relativePath = toSystemDependentName(fileUrl.substring(urlPrefixSize));
File imlFilePath = new File(projectPath, relativePath);
if (imlFilePath.isFile()) {
delete(imlFilePath);
}
// It is likely that each module has a "build" folder. Delete it as well.
File buildFilePath = new File(imlFilePath.getParentFile(), "build");
if (buildFilePath.isDirectory()) {
delete(buildFilePath);
}
}
}
}
catch (Throwable ignored) {
// if something goes wrong, just ignore. Most likely it won't affect project import in any way.
}
}
delete(dotIdeaFolderPath);
}
}
@NotNull
protected IdeFrameFixture openProjectAndWaitUntilOpened(@NotNull VirtualFile projectDir,
@NotNull FileChooserDialogFixture fileChooserDialog) {
fileChooserDialog.select(projectDir).clickOk();
File projectPath = virtualToIoFile(projectDir);
IdeFrameFixture projectFrame = findIdeFrame(projectPath);
projectFrame.waitForGradleProjectSyncToFinish();
return projectFrame;
}
@NotNull
protected IdeFrameFixture findIdeFrame(@NotNull File projectPath) {
return IdeFrameFixture.find(myRobot, projectPath, null);
}
}