/******************************************************************************* * Copyright (c) 2010, 2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Mathias Kinzler (SAP AG) - initial implementation *******************************************************************************/ package org.eclipse.egit.ui.test; import static org.eclipse.swtbot.eclipse.finder.waits.Conditions.waitForView; import static org.eclipse.swtbot.swt.finder.waits.Conditions.widgetIsEnabled; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import org.eclipse.core.net.proxy.IProxyService; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.internal.commit.CommitHelper; import org.eclipse.egit.ui.internal.commit.CommitHelper.CommitInfo; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.action.Action; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.StringUtils; import org.eclipse.osgi.service.localization.BundleLocalization; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; import org.eclipse.swtbot.swt.finder.SWTBot; import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; import org.eclipse.swtbot.swt.finder.results.VoidResult; import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; import org.eclipse.swtbot.swt.finder.waits.ICondition; import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; import org.eclipse.swtbot.swt.finder.widgets.TimeoutException; import org.eclipse.team.ui.history.IHistoryView; import org.eclipse.ui.IViewReference; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.IProgressConstants; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; /** * Utilities to be used by SWTBot tests */ public class TestUtil { public final static String TESTAUTHOR = "Test Author <test.author@test.com>"; public final static String TESTCOMMITTER = "Test Committer <test.committer@test.com>"; public final static String TESTCOMMITTER_NAME = "Test Committer"; public final static String TESTCOMMITTER_EMAIL = "test.committer@test.com"; private final static char AMPERSAND = '&'; private Map<Bundle, ResourceBundle> bundle2ResourceBundle = new HashMap<>(); /** * Allows access to the localized values of the EGit UI Plug-in * <p> * This will effectively read the plugin.properties. Ampersands (often used * in menu items and field labels for keyboard shortcuts) will be filtered * out (see also {@link #getPluginLocalizedValue(String, boolean)} in order * to be able to reference these fields using SWTBot). * * @param key * the key, must not be null * @return the localized value in the current default {@link Locale}, or * null * @throws MissingResourceException * if no value is found for the given key */ public synchronized String getPluginLocalizedValue(String key) throws MissingResourceException { return getPluginLocalizedValue(key, false); } /** * Allows access to the localized values of the EGit UI Plug-in * <p> * * @param key * see {@link #getPluginLocalizedValue(String)} * @param keepAmpersands * if <code>true</code>, ampersands will be kept * @return see {@link #getPluginLocalizedValue(String)} * @throws MissingResourceException * see {@link #getPluginLocalizedValue(String)} */ public synchronized String getPluginLocalizedValue(String key, boolean keepAmpersands) throws MissingResourceException { return getPluginLocalizedValue(key, keepAmpersands, Activator.getDefault().getBundle()); } /** * Allows access to the localized values of the given Bundle * <p> * * @param key * see {@link #getPluginLocalizedValue(String)} * @param keepAmpersands * if <code>true</code>, ampersands will be kept * @param bundle * the Bundle that contains the localization * @return see {@link #getPluginLocalizedValue(String)} * @throws MissingResourceException * see {@link #getPluginLocalizedValue(String)} */ public synchronized String getPluginLocalizedValue(String key, boolean keepAmpersands, Bundle bundle) throws MissingResourceException { ResourceBundle myBundle = bundle2ResourceBundle.get(bundle); if (myBundle == null) { BundleContext context = bundle.getBundleContext(); ServiceTracker<BundleLocalization, BundleLocalization> localizationTracker = new ServiceTracker<BundleLocalization, BundleLocalization>( context, BundleLocalization.class, null); localizationTracker.open(); BundleLocalization location = localizationTracker.getService(); if (location != null) { myBundle = location.getLocalization(bundle, Locale.getDefault().toString()); bundle2ResourceBundle.put(bundle, myBundle); } } if (myBundle != null) { String raw = myBundle.getString(key); if (keepAmpersands || raw.indexOf(AMPERSAND) < 0) return raw; StringBuilder sb = new StringBuilder(raw.length()); for (int i = 0; i < raw.length(); i++) { char c = raw.charAt(i); if (c != AMPERSAND) sb.append(c); } return sb.toString(); } return null; } /** * Utility for waiting until the execution of jobs of a given * family has finished. * @param family * @throws InterruptedException */ public static void joinJobs(Object family) throws InterruptedException { // join() returns immediately if the job is not yet scheduled. // To avoid unstable tests, let us first wait some time TestUtil.waitForJobs(100, 1000); Job.getJobManager().join(family, null); TestUtil.processUIEvents(); } @SuppressWarnings("restriction") public static void waitForDecorations() throws InterruptedException { TestUtil.joinJobs( org.eclipse.ui.internal.decorators.DecoratorManager.FAMILY_DECORATE); } /** * Utility for waiting until the execution of jobs of any family has * finished or timeout is reached. If no jobs are running, the method waits * given minimum wait time. While this method is waiting for jobs, UI events * are processed. * * @param minTimeMs * minimum wait time in milliseconds * @param maxTimeMs * maximum wait time in milliseconds */ public static void waitForJobs(long minTimeMs, long maxTimeMs) { if (maxTimeMs < minTimeMs) { throw new IllegalArgumentException( "Max time is smaller as min time!"); } final long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < minTimeMs) { processUIEvents(); } while (!Job.getJobManager().isIdle() && System.currentTimeMillis() - start < maxTimeMs) { processUIEvents(); } } /** * Process all queued UI events. If called from background thread, blocks * until all pending events are processed in UI thread. */ public static void processUIEvents() { processUIEvents(0); } /** * Process all queued UI events. If called from background thread, blocks * until all pending events are processed in UI thread. * * @param timeInMillis * time to wait. During this time all UI events are processed but * the current thread is blocked */ public static void processUIEvents(final long timeInMillis) { if (Display.getCurrent() != null) { if (timeInMillis <= 0) { while (Display.getCurrent().readAndDispatch()) { // process queued ui events at least once } } else { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start <= timeInMillis) { while (Display.getCurrent().readAndDispatch()) { // process queued ui events try { Thread.sleep(10); } catch (InterruptedException e) { break; } } } } } else { // synchronously refresh UI PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { processUIEvents(); } }); } } /** * Appends content to given file. * * @param file * @param content * @param append * if true, then bytes will be written to the end of the file * rather than the beginning * @throws IOException */ public static void appendFileContent(File file, String content, boolean append) throws IOException { Writer fw = null; try { fw = new OutputStreamWriter(new FileOutputStream(file, append), "UTF-8"); fw.append(content); } finally { if (fw != null) fw.close(); } } /** * Waits until the given tree has a node whose label contains text * @param bot * @param tree * @param text * @param timeout * @throws TimeoutException */ public static void waitUntilTreeHasNodeContainsText(SWTBot bot, final SWTBotTree tree, final String text, long timeout) throws TimeoutException { bot.waitUntil(new ICondition() { @Override public boolean test() throws Exception { for (SWTBotTreeItem item : tree.getAllItems()) if (item.getText().contains(text)) return true; return false; } @Override public void init(SWTBot bot2) { // empty } @Override public String getFailureMessage() { return null; } }, timeout); } /** * Waits until the given tree item has a node whose label contains text * @param bot * @param treeItem * @param text * @param timeout * @throws TimeoutException */ public static void waitUntilTreeHasNodeContainsText(SWTBot bot, final SWTBotTreeItem treeItem, final String text, long timeout) throws TimeoutException { bot.waitUntil(new ICondition() { @Override public boolean test() throws Exception { for (SWTBotTreeItem item : treeItem.getItems()) if (item.getText().contains(text)) return true; return false; } @Override public void init(SWTBot bot2) { // empty } @Override public String getFailureMessage() { return null; } }, timeout); } /** * Waits until the given tree item has a selected node with the given text * * @param bot * @param tree * @param text * @param timeout * @throws TimeoutException */ public static void waitUntilTreeHasSelectedNodeWithText(SWTBot bot, final SWTBotTree tree, final String text, long timeout) throws TimeoutException { bot.waitUntil(new ICondition() { @Override public boolean test() throws Exception { return tree.selection().get(0, 0).equals(text); } @Override public void init(SWTBot bot2) { // empty } @Override public String getFailureMessage() { return null; } }, timeout); } /** * Waits until the given table has an item with the given text * @param bot * @param table * @param text * @param timeout * @throws TimeoutException */ public static void waitUntilTableHasRowWithText(SWTBot bot, final SWTBotTable table, final String text, long timeout) throws TimeoutException { bot.waitUntil(new ICondition() { @Override public boolean test() throws Exception { if (table.indexOf(text)<0) return false; return true; } @Override public void init(SWTBot bot2) { // empty } @Override public String getFailureMessage() { return null; } }, timeout); } public static void waitUntilEditorIsActive(SWTWorkbenchBot bot, final SWTBotEditor editor, long timeout) { bot.waitUntil(new ICondition() { @Override public boolean test() throws Exception { return editor.isActive(); } @Override public void init(SWTBot bot2) { // empty } @Override public String getFailureMessage() { return null; } }, timeout); } /** * Disables usage of proxy servers */ public static void disableProxy() { BundleContext context = Activator.getDefault().getBundle().getBundleContext(); ServiceReference<IProxyService> serviceReference = context.getServiceReference(IProxyService.class); IProxyService proxyService = context.getService(serviceReference); proxyService.setSystemProxiesEnabled(false); proxyService.setProxiesEnabled(false); } // TODO: this method is both needed by UI tests and Core tests // provide a common base for UI tests and core tests /** * verifies that repository contains exactly the given files. * @param repository * @param paths * @throws Exception */ public static void assertRepositoryContainsFiles(Repository repository, String[] paths) throws Exception { Set<String> expectedfiles = new HashSet<String>(); for (String path : paths) expectedfiles.add(path); try (TreeWalk treeWalk = new TreeWalk(repository)) { treeWalk.addTree(repository.resolve("HEAD^{tree}")); treeWalk.setRecursive(true); while (treeWalk.next()) { String path = treeWalk.getPathString(); if (!expectedfiles.contains(path)) fail("Repository contains unexpected expected file " + path); expectedfiles.remove(path); } } if (expectedfiles.size() > 0) { StringBuilder message = new StringBuilder( "Repository does not contain expected files: "); for (String path : expectedfiles) { message.append(path); message.append(" "); } fail(message.toString()); } } /** * verifies that repository contains exactly the given files with the given * content. Usage example:<br> * * <code> * assertRepositoryContainsFiles(repository, "foo/a.txt", "content of A", * "foo/b.txt", "content of B") * </code> * @param repository * @param args * @throws Exception */ public static void assertRepositoryContainsFilesWithContent(Repository repository, String... args) throws Exception { HashMap<String, String> expectedfiles = mkmap(args); try (TreeWalk treeWalk = new TreeWalk(repository)) { treeWalk.addTree(repository.resolve("HEAD^{tree}")); treeWalk.setRecursive(true); while (treeWalk.next()) { String path = treeWalk.getPathString(); assertTrue(expectedfiles.containsKey(path)); ObjectId objectId = treeWalk.getObjectId(0); byte[] expectedContent = expectedfiles.get(path) .getBytes("UTF-8"); byte[] repoContent = treeWalk.getObjectReader().open(objectId) .getBytes(); if (!Arrays.equals(repoContent, expectedContent)) fail("File " + path + " has repository content " + new String(repoContent, "UTF-8") + " instead of expected content " + new String(expectedContent, "UTF-8")); expectedfiles.remove(path); } } if (expectedfiles.size() > 0) { StringBuilder message = new StringBuilder( "Repository does not contain expected files: "); for (String path : expectedfiles.keySet()) { message.append(path); message.append(" "); } fail(message.toString()); } } private static HashMap<String, String> mkmap(String... args) { if ((args.length % 2) > 0) throw new IllegalArgumentException("needs to be pairs"); HashMap<String, String> map = new HashMap<String, String>(); for (int i = 0; i < args.length; i += 2) map.put(args[i], args[i+1]); return map; } /** * @param projectExplorerTree * @param projects * name of a project * @return the project item pertaining to the project */ public SWTBotTreeItem[] getProjectItems(SWTBotTree projectExplorerTree, String... projects) { List<SWTBotTreeItem> items = new ArrayList<SWTBotTreeItem>(); for (SWTBotTreeItem item : projectExplorerTree.getAllItems()) { String itemText = item.getText(); StringTokenizer tok = new StringTokenizer(itemText, " "); String name = tok.nextToken(); // may be a dirty marker if (name.equals(">")) name = tok.nextToken(); for (String project : projects) if (project.equals(name)) items.add(item); } return items.isEmpty() ? null : items.toArray(new SWTBotTreeItem[items.size()]); } /** * Expand a node and wait until it is expanded. Use only if the node is * expected to have children. * * @param treeItem * to expand * @return the {@code treeItem} */ public static SWTBotTreeItem expandAndWait(final SWTBotTreeItem treeItem) { final String text = treeItem.getText(); treeItem.expand(); new SWTBot().waitUntil(new DefaultCondition() { @Override public boolean test() { if (treeItem.widget.isDisposed()) { return true; // Stop waiting and report failure below. } SWTBotTreeItem[] children = treeItem.getItems(); return children != null && children.length > 0; } @Override public String getFailureMessage() { return "No children found for " + text; } }); if (treeItem.widget.isDisposed()) { fail("Widget disposed while waiting for expansion of node " + text); } return treeItem; } /** * Expand a node and wait until it is expanded and has a child node with the * given name. Use only if the node is expected to have children. * * @param treeItem * to expand * @param childName * to wait for * @return the child node */ public static SWTBotTreeItem expandAndWaitFor(final SWTBotTreeItem treeItem, final String childName) { final String text = treeItem.getText(); final SWTBotTreeItem[] result = { null }; treeItem.expand(); new SWTBot().waitUntil(new DefaultCondition() { @Override public boolean test() { if (treeItem.widget.isDisposed()) { return true; // Stop waiting and report failure below. } try { result[0] = treeItem.getNode(childName); return true; } catch (WidgetNotFoundException e) { return false; } } @Override public String getFailureMessage() { return "Child " + childName + " not found under " + text; } }); if (treeItem.widget.isDisposed()) { fail("Widget disposed while waiting for child node " + text + '.' + childName); } return result[0]; } /** * Navigates in the given tree to the node denoted by the labels in the * path, using {@link #getNode(SWTBotTreeItem[], String)} to find children. * * @param tree * to navigate * @param path * sequence of strings denoting the node to navigate to * @return the node found * @throws WidgetNotFoundException * if a node along the path cannot be found, or there are * multiple candidates */ public static SWTBotTreeItem navigateTo(SWTBotTree tree, String... path) { assertNotNull(path); new SWTBot().waitUntil(widgetIsEnabled(tree)); SWTBotTreeItem item = getNode(tree.getAllItems(), path[0]); for (int i = 1; item != null && i < path.length; i++) { item = expandAndWait(item); item = getChildNode(item, path[i]); } return item; } /** * @param node * @param childNodeText * @return child node containing childNodeText * @see #getNode(SWTBotTreeItem[], String) */ public static SWTBotTreeItem getChildNode(SWTBotTreeItem node, String childNodeText) { return getNode(node.getItems(), childNodeText); } /** * Finds the node that contains the given text. Throws a nice message in * case the item is not found or more than one matching node was found. * * @param nodes * @param searchText * @return node containing the text */ public static SWTBotTreeItem getNode(SWTBotTreeItem[] nodes, String searchText) { List<String> texts = new ArrayList<String>(); List<SWTBotTreeItem> matchingItems = new ArrayList<SWTBotTreeItem>(); for (SWTBotTreeItem item : nodes) { String text = item.getText(); if (text.contains(searchText)) matchingItems.add(item); texts.add(text); } if (matchingItems.isEmpty()) throw new WidgetNotFoundException( "Tree item element containg text \"" + searchText + "\" was not found. Existing tree items:\n" + StringUtils.join(texts, "\n")); else if (matchingItems.size() > 1) throw new WidgetNotFoundException( "Tree item element containg text \"" + searchText + "\" could not be uniquely identified. All tree items:\n" + StringUtils.join(texts, "\n")); return matchingItems.get(0); } public static RevCommit getHeadCommit(Repository repository) throws Exception { RevCommit headCommit = null; ObjectId parentId = repository.resolve(Constants.HEAD); if (parentId != null) { try (RevWalk rw = new RevWalk(repository)) { headCommit = rw.parseCommit(parentId); } } return headCommit; } public static void checkHeadCommit(Repository repository, String author, String committer, String message) throws Exception { CommitInfo commitInfo = CommitHelper.getHeadCommitInfo(repository); assertEquals(author, commitInfo.getAuthor()); assertEquals(committer, commitInfo.getCommitter()); assertEquals(message, commitInfo.getCommitMessage()); } public static void configureTestCommitterAsUser(Repository repository) { StoredConfig config = repository.getConfig(); config.setString(ConfigConstants.CONFIG_USER_SECTION, null, ConfigConstants.CONFIG_KEY_NAME, TestUtil.TESTCOMMITTER_NAME); config.setString(ConfigConstants.CONFIG_USER_SECTION, null, ConfigConstants.CONFIG_KEY_EMAIL, TestUtil.TESTCOMMITTER_EMAIL); } public static void waitUntilViewWithGivenIdShows(final String viewId) { waitForView(new BaseMatcher<IViewReference>() { @Override public boolean matches(Object item) { if (item instanceof IViewReference) return viewId.equals(((IViewReference) item).getId()); return false; } @Override public void describeTo(Description description) { description.appendText("Wait for view with ID=" + viewId); } }); } public static void waitUntilViewWithGivenTitleShows(final String viewTitle) { waitForView(new BaseMatcher<IViewReference>() { @Override public boolean matches(Object item) { if (item instanceof IViewReference) return viewTitle.equals(((IViewReference) item).getTitle()); return false; } @Override public void describeTo(Description description) { description.appendText("Wait for view with title " + viewTitle); } }); } public static SWTBotShell botForShellStartingWith(final String titlePrefix) { SWTWorkbenchBot bot = new SWTWorkbenchBot(); Matcher<Shell> matcher = new TypeSafeMatcher<Shell>() { @Override protected boolean matchesSafely(Shell item) { String title = item.getText(); return title != null && title.startsWith(titlePrefix); } @Override public void describeTo(Description description) { description.appendText("Shell with title starting with '" + titlePrefix + "'"); } }; Shell shell = bot.widget(matcher); return new SWTBotShell(shell); } public static SWTBotView showView(final String viewId) { Display.getDefault().syncExec(new Runnable() { @Override public void run() { IWorkbenchWindow workbenchWindow = PlatformUI.getWorkbench() .getActiveWorkbenchWindow(); IWorkbenchPage workbenchPage = workbenchWindow.getActivePage(); try { workbenchPage.showView(viewId); processUIEvents(); } catch (PartInitException e) { throw new RuntimeException("Showing view with ID " + viewId + " failed.", e); } } }); SWTWorkbenchBot bot = new SWTWorkbenchBot(); SWTBotView viewbot = bot.viewById(viewId); assertNotNull("View with ID " + viewId + " not found via SWTBot.", viewbot); return viewbot; } public static void hideView(final String viewId) { Display.getDefault().syncExec(new Runnable() { @Override public void run() { IWorkbenchWindow[] windows = PlatformUI.getWorkbench() .getWorkbenchWindows(); for (IWorkbenchWindow window : windows) { IWorkbenchPage workbenchPage = window.getActivePage(); IViewReference[] views = workbenchPage.getViewReferences(); for (int i = 0; i < views.length; i++) { IViewReference view = views[i]; if (viewId.equals(view.getId())) { workbenchPage.hideView(view); } } processUIEvents(); } } }); } public static SWTBotView showHistoryView() { return showView(IHistoryView.VIEW_ID); } public static SWTBotView showExplorerView() { return showView(JavaUI.ID_PACKAGES); } public static SWTBotTree getExplorerTree() { SWTBotView view = showExplorerView(); return view.bot().tree(); } public static void openJobResultDialog(Job job) { assertNotNull("Job should not be null", job); final Action action = (Action) job .getProperty(IProgressConstants.ACTION_PROPERTY); if (action != null) { UIThreadRunnable.asyncExec(new VoidResult() { @Override public void run() { action.run(); } }); } } }