blob: 13270ea3bd7ecf27164e1845d93009889f6cdd2b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 The Eclipse Foundation 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:
* The Eclipse Foundation - initial API and implementation
*******************************************************************************/
package org.eclipse.epp.mpc.tests.ui.wizard;
import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType;
import static org.eclipse.swtbot.swt.finder.waits.Conditions.shellIsActive;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.epp.internal.mpc.ui.commands.MarketplaceWizardCommand;
import org.eclipse.epp.internal.mpc.ui.wizards.AbstractTagFilter;
import org.eclipse.epp.internal.mpc.ui.wizards.ComboTagFilter;
import org.eclipse.epp.internal.mpc.ui.wizards.FeatureSelectionWizardPage;
import org.eclipse.epp.internal.mpc.ui.wizards.MarketplaceWizard;
import org.eclipse.epp.internal.mpc.ui.wizards.MarketplaceWizardDialog;
import org.eclipse.epp.mpc.core.model.ICategory;
import org.eclipse.epp.mpc.core.model.IMarket;
import org.eclipse.epp.mpc.tests.ui.wizard.matcher.NodeMatcher;
import org.eclipse.equinox.internal.p2.discovery.model.Tag;
import org.eclipse.equinox.internal.p2.ui.discovery.wizards.CatalogFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.matchers.WidgetMatcherFactory;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor;
import org.eclipse.swtbot.swt.finder.SWTBot;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.junit.ScreenshotCaptureListener;
import org.eclipse.swtbot.swt.finder.results.ArrayResult;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferenceConstants;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.utils.SWTUtils;
import org.eclipse.swtbot.swt.finder.waits.Conditions;
import org.eclipse.swtbot.swt.finder.waits.ICondition;
import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotBrowser;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotCombo;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotLabel;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotLink;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTabItem;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotText;
import org.eclipse.swtbot.swt.finder.widgets.TimeoutException;
import org.eclipse.ui.IEditorReference;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runners.model.Statement;
public abstract class AbstractMarketplaceWizardBotTest {
private static long PROGRESS_TIMEOUT = Long.getLong("org.eclipse.epp.mpc.tests.progress.timeout", 30000);
private static final Logger logger = Logger.getLogger(AbstractMarketplaceWizardBotTest.class);
private static boolean dumpThreadsOnTearDownError = Boolean.valueOf(System.getProperty(
"org.eclipse.epp.mpc.tests.dump.threads", "true"));
protected SWTBot bot;
protected SWTBotShell wizardShell;
public AbstractMarketplaceWizardBotTest() {
super();
}
@Rule
public TestRule screenshotOnFailureRule = new TestRule() {
public Statement apply(final Statement base, final Description description) {
String targetDir = System.getProperty(SWTBotPreferenceConstants.KEY_SCREENSHOTS_DIR);
if (targetDir == null && new File("target").isDirectory()) {
SWTBotPreferences.SCREENSHOTS_DIR = "target/screenshots";
}
return new Statement() {
private final ScreenshotCaptureListener capturer = new ScreenshotCaptureListener();
@Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} catch (Throwable t) {
capturer.testFailure(new Failure(description, t));
throw t;
} finally {
tearDownBot();
}
}
};
}
};
@Before
public void setUp() {
launchMarketplaceWizard();
initWizardBot();
}
//tear-down is done in test rule above, since we need to do this after the rule has been applied
//@After
public void tearDownBot() {
if (bot != null) {
closeWizard();
}
}
protected void launchMarketplaceWizard() {
final MarketplaceWizardCommand marketplaceWizardCommand = new MarketplaceWizardCommand();
UIThreadRunnable.asyncExec(new VoidResult() {
public void run() {
try {
marketplaceWizardCommand.execute(new ExecutionEvent());
} catch (ExecutionException e) {
fail("ExecutionException: " + e.getMessage());
//otherwise ignore, we'll notice in the test thread when we don't get the wizard dialog in time
}
}
});
}
protected void initWizardBot() {
bot = new SWTBot();
bot.waitUntil(shellIsActive("Eclipse Marketplace"));
wizardShell = bot.shell("Eclipse Marketplace");
bot = wizardShell.bot();
assertNotNull(getWizardDialog());
waitForWizardProgress();
}
protected void waitForWizardProgress() {
waitForWizardProgress(PROGRESS_TIMEOUT);
}
protected void waitForWizardProgress(long timeout) {
SWTBotButton cancelButton = bot.button("Cancel");
bot.waitUntil(Conditions.widgetIsEnabled(cancelButton), timeout);
}
protected void closeWizard() {
String problem = null;
List<Exception> exceptions = new ArrayList<Exception>();
SWTBotShell mpcShell;
try {
//check if dialog is still open
mpcShell = bot.shell("Eclipse Marketplace");
} catch (TimeoutException e) {
//no MPC wizard found - maybe a bit strange, but so be it...
return;
}
//check if any message dialogs are open
boolean dumpedThreads = false;
try {
WaitForObjectCondition<Shell> subShellResult = Conditions.waitForShell(Matchers.any(Shell.class),
mpcShell.widget);
bot.waitUntil(subShellResult, 100, 60);
List<Shell> subShells = subShellResult.getAllMatches();
for (Shell shell : subShells) {
if (shell == mpcShell.widget) {
continue;
}
try {
SWTBotShell botShell = new SWTBotShell(shell);
//children are unexpected, so let's cry foul...
if (problem == null) {
problem = "MPC wizard has open child dialog:";
}
problem += "\n" + describeShell(botShell);
logger.info(problem);
problem += "\n" + captureShellScreenshot(botShell);
//also dump threads, since this is often caused by the wizard not being cancellable due to a still running operation:
//"Wizard can not be closed due to an active operation"
if (!dumpedThreads) {
dumpedThreads = true;
dumpThreads();
}
//kill message dialog
botShell.close();
} catch (Exception ex) {
exceptions.add(ex);
}
}
} catch (Exception ex) {
exceptions.add(ex);
}
//try killing it softly
try {
mpcShell.activate();
waitForWizardProgress(SWTBotPreferences.TIMEOUT);
mpcShell.close();//same as pressing "Cancel" actually
ICondition shellCloses = Conditions.shellCloses(mpcShell);
bot.waitUntil(shellCloses);
return;
} catch (Exception ex) {
exceptions.add(ex);
}
//now kill it hard - this is a last resort, because it can cause spurious errors in MPC jobs
//also dump threads, since this is often caused by the wizard not being cancellable due to a still running operation:
//"Wizard can not be closed due to an active operation"
problem += "\nFailed to close wizard regularly. Forcing close.";
if (!dumpedThreads) {
dumpedThreads = true;
dumpThreads();
}
try {
final Shell shell = mpcShell.widget;
if (!shell.isDisposed()) {
Display display = shell.getDisplay();
display.syncExec(new Runnable() {
@Override
public void run() {
if (!shell.isDisposed()) {
shell.dispose();
}
}
});
}
} catch (Exception ex) {
exceptions.add(ex);
}
if (problem != null || !exceptions.isEmpty()) {
//something happened
try {
fail(problem);
} catch (AssertionError e) {
for (Exception exception : exceptions) {
e.addSuppressed(exception);
}
throw e;
}
}
}
private void dumpThreads() {
if (!dumpThreadsOnTearDownError) {
return;
}
try {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
Method dumpMethod = ThreadMXBean.class.getMethod("dumpAllThreads", Boolean.TYPE, Boolean.TYPE);
ThreadInfo[] threadInfos = (ThreadInfo[]) dumpMethod.invoke(threadMXBean, true, true);
for (ThreadInfo threadInfo : threadInfos) {
logger.debug(threadInfo);
}
} catch (NoSuchMethodException e) {
dumpThreadsOnTearDownError = false;
logger.warn("Method ThreadMXBean.dumpAllThreads(boolean, boolean) does not exist. Try running on Java 6 or later.");
} catch (Throwable t) {
logger.warn("Error dumping threads: " + t, t);
}
}
private String captureShellScreenshot(SWTBotShell botShell) {
if (botShell.isVisible()) {
try {
//try to bring to front
botShell.activate();
} catch (Throwable ex) {
}
//make a screenshot
String fileName = "dialog_" + System.currentTimeMillis() + "."
+ SWTBotPreferences.SCREENSHOT_FORMAT.toLowerCase();
SWTUtils.captureScreenshot(SWTBotPreferences.SCREENSHOTS_DIR + "/" + fileName);
String message = "Captured screenshot of open shell '" + botShell.getText() + "' in " + fileName;
logger.info(message);
return message;
}
return "";
}
private String describeShell(SWTBotShell botShell) {
StringBuilder description = new StringBuilder(" Shell(\"").append(botShell.getText()).append("\")");
try {
SWTBot childBot = botShell.bot();
@SuppressWarnings("unchecked")
Matcher<Label> labelMatcher = allOf(widgetOfType(Label.class));
List<? extends Label> labels = childBot.widgets(labelMatcher);
for (Label label : labels) {
if (label != null) {//TODO why can this be null?
String labelText = new SWTBotLabel(label, labelMatcher).getText();
if (labelText != null && labelText.trim().length() > 0) {
description.append("\n > ").append(labelText.trim());
}
}
}
@SuppressWarnings("unchecked")
Matcher<Button> buttonMatcher = allOf(widgetOfType(Button.class));
List<? extends Button> buttons = childBot.widgets(buttonMatcher);
boolean firstButton = true;
for (Button button : buttons) {
if (button != null) {
SWTBotButton buttonBot = new SWTBotButton(button, buttonMatcher);
String buttonText = buttonBot.getText();
if (buttonText != null && buttonText.trim().length() > 0) {
if (firstButton) {
firstButton = false;
description.append("\n > ");
} else {
description.append(" ");
}
if (buttonBot.isEnabled()) {
description.append('[').append(buttonText.trim()).append(']');
} else {
description.append("[(").append(buttonText.trim()).append(")]");
}
}
}
}
} catch (Exception ex) {
description.append("\n > Error describing shell contents: ").append(ex);
}
return description.toString();
}
protected void checkNoItems() {
checkNoItems(NodeMatcher.any());
}
protected void checkNoItems(NodeMatcher<? extends Widget> matcher) {
List<? extends Widget> controls = bot.getFinder().findControls(matcher);
assertThat(controls, empty());
}
protected SWTBot itemBot(NodeMatcher<? extends Widget> matcher) {
List<? extends Widget> controls = bot.getFinder().findControls(matcher);
assertThat(controls.size(), greaterThanOrEqualTo(1));
Widget firstItem = controls.get(0);
return new SWTBot(firstItem);
}
protected SWTBot itemBot(String id) {
return itemBot(id == null ? NodeMatcher.any() : NodeMatcher.withId(id));
}
protected void checkSelectedTab(String tabLabel) {
SWTBotTabItem searchTab = bot.tabItem(tabLabel);
final TabItem tab = searchTab.widget;
TabItem[] selection = UIThreadRunnable.syncExec(new ArrayResult<TabItem>() {
public TabItem[] run() {
return tab.getParent().getSelection();
}
});
assertEquals(1, selection.length);
assertSame(tab, selection[0]);
}
protected void filterMarket(String term) {
SWTBotCombo comboBox = marketCombo();
select(comboBox, IMarket.class, term);
}
protected SWTBotCombo marketCombo() {
return bot.comboBox(0);
}
protected void filterCategory(String term) {
SWTBotCombo comboBox = categoryCombo();
select(comboBox, ICategory.class, term);
}
protected SWTBotCombo categoryCombo() {
return bot.comboBox(1);
}
protected void search(String term) {
SWTBotText searchField = searchField();
searchField.setFocus();
searchField.setText(term);
bot.button("Go").click();
waitForWizardProgress();
}
protected SWTBotText searchField() {
return bot.text(0);
}
protected SWTBotBrowser marketplaceBrowser() {
SWTWorkbenchBot wbBot = new SWTWorkbenchBot();
Matcher<IEditorReference> marketplaceBrowserMatch = allOf(WidgetMatcherFactory.<IEditorReference> withPartId("org.eclipse.ui.browser.editor"), WidgetMatcherFactory
.<IEditorReference> withTitle(containsString("Marketplace")));
SWTBotEditor browserEditor = wbBot.editor(marketplaceBrowserMatch);
SWTBotBrowser browser = browserEditor.bot().browser();
return browser;
}
protected List<StyleRange> findLinks(final SWTBotStyledText styledText) {
StyleRange[] ranges = findStyleRanges(styledText);
List<StyleRange> links = new ArrayList<StyleRange>();
for (StyleRange range : ranges) {
if (range.underline == true && range.underlineStyle == SWT.UNDERLINE_LINK) {
links.add(range);
}
}
assertFalse(links.isEmpty());
return links;
}
private StyleRange[] findStyleRanges(final SWTBotStyledText styledText) {
StyleRange[] ranges = UIThreadRunnable.syncExec(new ArrayResult<StyleRange>() {
public StyleRange[] run() {
return styledText.widget.getStyleRanges();
}
});
return ranges;
}
protected StyleRange findLink(final SWTBotStyledText styledText, String linkText) {
List<StyleRange> links = findLinks(styledText);
String text = styledText.getText();
for (StyleRange link : links) {
if (linkText.equals(getText(link, text))) {
return link;
}
}
fail("No link found with text '" + linkText + "'");
return null;
}
protected StyleRange findLink(final SWTBotStyledText styledText) {
List<StyleRange> links = findLinks(styledText);
return links.get(0);
}
protected String getText(StyleRange range, String text) {
return text.substring(range.start, range.start + range.length);
}
protected String getText(StyleRange range, SWTBotStyledText styledText) {
String text = styledText.getText();
return text.substring(range.start, range.start + range.length);
}
protected void select(SWTBotCombo comboBox, Class<?> classifier, String choice) {
AbstractTagFilter filter = findFilter(classifier);
String choiceText = choice != null ? choice : ((ComboTagFilter) filter).getNoSelectionLabel();
comboBox.setSelection(choiceText);
waitForWizardProgress();
assertEquals(choiceText, comboBox.getText());
checkSelected(filter, choice);
}
private void checkSelected(AbstractTagFilter filter, String selection) {
Set<Tag> selected = filter.getSelected();
if (selection == null) {
assertTrue(selected.isEmpty());
return;
}
for (Tag tag : selected) {
if (tag.getLabel().equals(selection)) {
return;
}
if (tag.getValue().equals(selection)) {
return;
}
}
fail(NLS.bind("Expected value {0} not selected in filter", selection));
}
private AbstractTagFilter findFilter(Class<?> classifier) {
List<CatalogFilter> filters = getWizard().getConfiguration().getFilters();
for (CatalogFilter filter : filters) {
if (filter instanceof AbstractTagFilter) {
AbstractTagFilter tagFilter = (AbstractTagFilter) filter;
List<Tag> choices = tagFilter.getChoices();
Object classification = choices.isEmpty() ? null : choices.get(0).getTagClassifier();
if (classification == classifier) {
return tagFilter;
}
}
}
fail("No filter found for " + classifier.getName());
return null;//unreachable
}
protected void deselectPending(int count) {
for (int i = 0; i < count; i++) {
bot.button("Install Pending").click();
}
}
protected SWTBotLink selectToInstall(int count) {
if (count == 0) {
return null;
}
bot.button("Install").click();
waitForWizardProgress(3 * PROGRESS_TIMEOUT);
assertSame(getWizard().getPage(FeatureSelectionWizardPage.class.getName()), getWizardDialog().getCurrentPage());
bot.button("< Install More").click();
waitForWizardProgress();
SWTBotLink selectedSolutionsLink = selectedSolutionsLink(1);
for (int i = 2; i <= count; i++) {
bot.button("Install").click();
selectedSolutionsLink = selectedSolutionsLink(i);
}
return selectedSolutionsLink;
}
protected SWTBotLink selectedSolutionsLink(int count) {
String linkText;
switch (count) {
case 0:
return null;
case 1:
linkText = "One solution selected";
break;
default:
linkText = String.format("%s solutions selected", count);
}
String linkContent = String.format("<a href=\"showSelection\">%s</a>", linkText);
return bot.link(linkContent);
}
protected void tryWaitForBrowser(SWTBotBrowser browser) {
for (int i = 0; i < 6; i++) {
try {
browser.waitForPageLoaded();
} catch (TimeoutException ex) {
//ignore
}
String url = browser.getUrl();
if (url != null && !"".equals(url) && !"about:blank".equals(url)) {
return;
} else {
bot.sleep(1000);
}
}
}
protected MarketplaceWizardDialog getWizardDialog() {
return (MarketplaceWizardDialog) UIThreadRunnable.syncExec(new Result<Object>() {
public Object run() {
return wizardShell.widget.getData();
}
});
}
protected MarketplaceWizard getWizard() {
return getWizardDialog().getWizard();
}
}