import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.util.Arrays;
import java.util.List;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.bindings.keys.IKeyLookup;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.SWTBot;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
+import org.eclipse.swtbot.swt.finder.keyboard.Keyboard;
+import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.results.VoidResult;
+import org.eclipse.swtbot.swt.finder.utils.SWTUtils;
import org.eclipse.swtbot.swt.finder.waits.Conditions;
+import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotCheckBox;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
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.tracecompass.internal.tmf.ui.project.operations.NewExperimentOperation;
import org.eclipse.tracecompass.tmf.ui.editors.TmfEventsEditor;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentElement;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentFolder;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfOpenTraceHelper;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectRegistry;
+import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceFolder;
import org.eclipse.tracecompass.tmf.ui.project.model.TmfTracesFolder;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ConditionHelpers.ProjectElementHasChild;
*
* @author Matthew Khouzam
*/
+@SuppressWarnings("restriction")
public final class SWTBotUtils {
+ private static final long MAX_JOBS_WAIT_TIME = 300000;
+ private static final String WINDOW_MENU = "Window";
+ private static final String PREFERENCES_MENU_ITEM = "Preferences";
+ private static boolean fPrintedEnvironment = false;
+
private SWTBotUtils() {
}
private static final String TRACING_PERSPECTIVE_ID = TracingPerspectiveFactory.ID;
/**
- * Waits for all Eclipse jobs to finish
+ * Waits for all Eclipse jobs to finish. Times out after
+ * SWTBotUtils#MAX_JOBS_WAIT_TIME by default.
+ *
+ * @throws TimeoutException
+ * once the waiting time passes the default maximum value
*/
public static void waitForJobs() {
+ waitForJobs(MAX_JOBS_WAIT_TIME);
+ }
+
+ /**
+ * Waits for all Eclipse jobs to finish
+ *
+ * @param maxWait
+ * the maximum time to wait, in milliseconds. Once the waiting
+ * time passes the maximum value, a TimeoutException is thrown
+ * @throws TimeoutException
+ * once the waiting time passes the maximum value
+ */
+ public static void waitForJobs(long maxWait) {
+ long waitStart = System.currentTimeMillis();
while (!Job.getJobManager().isIdle()) {
+ if (System.currentTimeMillis() - waitStart > maxWait) {
+ printJobs();
+ throw new TimeoutException("Timed out waiting for jobs to finish.");
+ }
+
delay(100);
}
}
+ private static void printJobs() {
+ Job[] jobs = Job.getJobManager().find(null);
+ for (Job job : jobs) {
+ System.err.println(job.toString() + " state: " + jobStateToString(job.getState()));
+ Thread thread = job.getThread();
+ if (thread != null) {
+ for (StackTraceElement stractTraceElement : thread.getStackTrace()) {
+ System.err.println(" " + stractTraceElement);
+ }
+ }
+ System.err.println();
+ }
+ }
+
+ private static String jobStateToString(int jobState) {
+ switch (jobState) {
+ case Job.RUNNING:
+ return "RUNNING";
+ case Job.WAITING:
+ return "WAITING";
+ case Job.SLEEPING:
+ return "SLEEPING";
+ case Job.NONE:
+ return "NONE";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
/**
* Sleeps current thread for a given time.
*
deleteProject(projectName, true, bot);
}
+ /**
+ * Creates an experiment
+ *
+ * @param bot
+ * a given workbench bot
+ * @param projectName
+ * the name of the project, creates the project if needed
+ * @param expName
+ * the experiment name
+ */
+ public static void createExperiment(SWTWorkbenchBot bot, String projectName, final @NonNull String expName) {
+ IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
+ TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, true);
+ TmfExperimentFolder expFolder = tmfProject.getExperimentsFolder();
+ assertNotNull(expFolder);
+ NewExperimentOperation operation = new NewExperimentOperation(expFolder, expName);
+ operation.run(new NullProgressMonitor());
+
+ bot.waitUntil(new DefaultCondition() {
+ @Override
+ public boolean test() throws Exception {
+ TmfExperimentElement experiment = expFolder.getExperiment(expName);
+ return experiment != null;
+ }
+
+ @Override
+ public String getFailureMessage() {
+ return "Experiment (" + expName + ") couldn't be created";
+ }
+ });
+ }
+
+
/**
* Focus on the main window
*
}
}
+ /**
+ * Close a view with an id
+ *
+ * @param viewId
+ * the view id, like "org.eclipse.linuxtools.tmf.ui.views.histogram"
+ * @param bot
+ * the workbench bot
+ */
+ public static void closeViewById(String viewId, SWTWorkbenchBot bot) {
+ final SWTBotView view = bot.viewById(viewId);
+ view.close();
+ bot.waitUntil(ConditionHelpers.ViewIsClosed(view));
+ }
+
/**
* Switch to the tracing perspective
*/
});
}
+ /**
+ * Initialize the environment for SWTBot
+ */
+ public static void initialize() {
+ failIfUIThread();
+
+ SWTWorkbenchBot bot = new SWTWorkbenchBot();
+ UIThreadRunnable.syncExec(() -> {
+ printEnvironment();
+
+ // There seems to be problems on some system where the main shell is
+ // not in focus initially. This was seen using Xvfb and Xephyr on some occasions.
+ focusMainWindow(bot.shells());
+
+ Shell shell = bot.activeShell().widget;
+
+ // Only adjust shell if it appears to be the top-most
+ if (shell.getParent() == null) {
+ makeShellFullyVisible(shell);
+ }
+ });
+ }
+
+ private static void printEnvironment() {
+ if (fPrintedEnvironment) {
+ return;
+ }
+
+ // Print some information about the environment that could affect test outcome
+ Rectangle bounds = Display.getDefault().getBounds();
+ System.out.println("Display size: " + bounds.width + "x" + bounds.height);
+
+ String osVersion = System.getProperty("os.version");
+ if (osVersion != null) {
+ System.out.println("OS version=" + osVersion);
+ }
+ String gtkVersion = System.getProperty("org.eclipse.swt.internal.gtk.version");
+ if (gtkVersion != null) {
+ System.out.println("GTK version=" + gtkVersion);
+ String overlayScrollbar = System.getenv("LIBOVERLAY_SCROLLBAR");
+ if (overlayScrollbar != null) {
+ System.out.println("LIBOVERLAY_SCROLLBAR=" + overlayScrollbar);
+ }
+ String ubuntuMenuProxy = System.getenv("UBUNTU_MENUPROXY");
+ if (ubuntuMenuProxy != null) {
+ System.out.println("UBUNTU_MENUPROXY=" + ubuntuMenuProxy);
+ }
+ }
+
+ System.out.println("Time zone: " + TimeZone.getDefault().getDisplayName());
+
+ fPrintedEnvironment = true;
+ }
+
/**
* If the test is running in the UI thread then fail
*/
- public static void failIfUIThread() {
+ private static void failIfUIThread() {
if (Display.getCurrent() != null && Display.getCurrent().getThread() == Thread.currentThread()) {
fail("SWTBot test needs to run in a non-UI thread. Make sure that \"Run in UI thread\" is unchecked in your launch configuration or"
+ " that useUIThread is set to false in the pom.xml");
}
+ }
+ /**
+ * Try to make the shell fully visible in the display. If the shell cannot
+ * fit the display, it will be positioned so that top-left corner is at
+ * <code>(0, 0)</code> in display-relative coordinates.
+ *
+ * @param shell
+ * the shell to make fully visible
+ */
+ private static void makeShellFullyVisible(Shell shell) {
+ Rectangle displayBounds = shell.getDisplay().getBounds();
+ Point absCoord = shell.toDisplay(0, 0);
+ Point shellSize = shell.getSize();
+
+ Point newLocation = new Point(absCoord.x, absCoord.y);
+ newLocation.x = Math.max(0, Math.min(absCoord.x, displayBounds.width - shellSize.x));
+ newLocation.y = Math.max(0, Math.min(absCoord.y, displayBounds.height - shellSize.y));
+ if (!newLocation.equals(absCoord)) {
+ shell.setLocation(newLocation);
+ }
}
/**
SWTBotTreeItem currentItem = tracesNode;
for (String segment : elementPath.segments()) {
currentItem = getTraceProjectItem(projectExplorerBot, currentItem, segment);
- currentItem.select();
currentItem.doubleClick();
}
return tracesFolderItem;
}
+ /**
+ * Clear the traces folder
+ *
+ * @param bot
+ * a given workbench bot
+ * @param projectName
+ * the name of the project (needs to exist)
+ */
+ public static void clearTracesFolder(SWTWorkbenchBot bot, String projectName) {
+ IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
+ TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, false);
+ TmfTraceFolder tracesFolder = tmfProject.getTracesFolder();
+ try {
+ for (TmfTraceElement traceElement : tracesFolder.getTraces()) {
+ traceElement.delete(null);
+ }
+
+ final IFolder resource = tracesFolder.getResource();
+ resource.accept(new IResourceVisitor() {
+ @Override
+ public boolean visit(IResource visitedResource) throws CoreException {
+ if (visitedResource != resource) {
+ visitedResource.delete(true, null);
+ }
+ return true;
+ }
+ }, IResource.DEPTH_ONE, 0);
+ } catch (CoreException e) {
+ fail(e.getMessage());
+ }
+
+ bot.waitUntil(new DefaultCondition() {
+ private int fTraceNb = 0;
+
+ @Override
+ public boolean test() throws Exception {
+ List<TmfTraceElement> traces = tracesFolder.getTraces();
+ fTraceNb = traces.size();
+ return fTraceNb == 0;
+ }
+
+ @Override
+ public String getFailureMessage() {
+ return "Traces Folder not empty (" + fTraceNb + ")";
+ }
+ });
+ }
+
+ /**
+ * Clear the trace folder (using the UI)
+ *
+ * @param bot
+ * a given workbench bot
+ * @param projectName
+ * the name of the project (needs to exist)
+ */
+ public static void clearTracesFolderUI(SWTWorkbenchBot bot, String projectName) {
+ SWTBotTreeItem tracesFolder = selectTracesFolder(bot, projectName);
+ tracesFolder.contextMenu().menu("Clear").click();
+ String CONFIRM_CLEAR_DIALOG_TITLE = "Confirm Clear";
+ bot.waitUntil(Conditions.shellIsActive(CONFIRM_CLEAR_DIALOG_TITLE));
+
+ SWTBotShell shell = bot.shell(CONFIRM_CLEAR_DIALOG_TITLE);
+ shell.bot().button("Yes").click();
+ bot.waitUntil(Conditions.shellCloses(shell));
+ bot.waitWhile(ConditionHelpers.treeItemHasChildren(tracesFolder));
+ }
+
+ /**
+ * Clear the experiment folder
+ *
+ * @param bot
+ * a given workbench bot
+ * @param projectName
+ * the name of the project (needs to exist)
+ */
+ public static void clearExperimentFolder(SWTWorkbenchBot bot, String projectName) {
+ IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
+ TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, false);
+ TmfExperimentFolder expFolder = tmfProject.getExperimentsFolder();
+ expFolder.getExperiments().forEach(experiment -> {
+ IResource resource = experiment.getResource();
+ try {
+ // Close the experiment if open
+ experiment.closeEditors();
+
+ IPath path = resource.getLocation();
+ if (path != null) {
+ // Delete supplementary files
+ experiment.deleteSupplementaryFolder();
+ }
+ // Finally, delete the experiment
+ resource.delete(true, null);
+ } catch (CoreException e) {
+ fail(e.getMessage());
+ }
+ });
+
+ bot.waitUntil(new DefaultCondition() {
+ private int fExperimentNb = 0;
+
+ @Override
+ public boolean test() throws Exception {
+ List<TmfExperimentElement> experiments = expFolder.getExperiments();
+ fExperimentNb = experiments.size();
+ return fExperimentNb == 0;
+ }
+
+ @Override
+ public String getFailureMessage() {
+ return "Experiment Folder not empty (" + fExperimentNb + ")";
+ }
+ });
+ }
+
/**
* Select the project in Project Explorer
*
public static SWTBotTreeItem selectProject(SWTWorkbenchBot bot, String projectName) {
SWTBotView projectExplorerBot = bot.viewByTitle("Project Explorer");
projectExplorerBot.show();
+ // FIXME: Bug 496519. Sometimes, the tree becomes disabled for a certain
+ // amount of time. This can happen during a long running operation
+ // (BusyIndicator.showWhile) which brings up the modal dialog "operation
+ // in progress" and this disables all shells
+ projectExplorerBot.bot().waitUntil(Conditions.widgetIsEnabled(projectExplorerBot.bot().tree()));
SWTBotTreeItem treeItem = projectExplorerBot.bot().tree().getTreeItem(projectName);
treeItem.select();
return treeItem;
* the {@link SWTBotTable} table
*/
public static void maximizeTable(SWTBotTable tableBot) {
+ final AtomicBoolean controlResized = new AtomicBoolean();
+ UIThreadRunnable.syncExec(new VoidResult() {
+ @Override
+ public void run() {
+ tableBot.widget.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ tableBot.widget.removeControlListener(this);
+ controlResized.set(true);
+ }
+ });
+ }
+ });
try {
tableBot.pressShortcut(KeyStroke.getInstance(IKeyLookup.CTRL_NAME + "+"), KeyStroke.getInstance("M"));
} catch (ParseException e) {
fail();
}
+ new SWTBot().waitUntil(new DefaultCondition() {
+ @Override
+ public boolean test() throws Exception {
+ return controlResized.get();
+ }
+
+ @Override
+ public String getFailureMessage() {
+ return "Control was not resized";
+ }
+ });
}
/**
bot.waitUntil(ConditionHelpers.IsTreeNodeAvailable(nodeNames[0], tree));
SWTBotTreeItem currentNode = tree.getTreeItem(nodeNames[0]);
- for (int i = 1; i < nodeNames.length; i++) {
+ return getTreeItem(bot, currentNode, Arrays.copyOfRange(nodeNames, 1, nodeNames.length));
+ }
+
+ /**
+ * Get the tree item from a parent tree item at the specified location
+ *
+ * @param bot
+ * the SWTBot
+ * @param treeItem
+ * the treeItem to find the tree item under
+ * @param nodeNames
+ * the path to the tree item, in the form of node names (from
+ * parent to child).
+ * @return the tree item
+ */
+ public static SWTBotTreeItem getTreeItem(SWTBot bot, SWTBotTreeItem treeItem, String... nodeNames) {
+ if (nodeNames.length == 0) {
+ return treeItem;
+ }
+
+ SWTBotTreeItem currentNode = treeItem;
+ for (int i = 0; i < nodeNames.length; i++) {
+ bot.waitUntil(ConditionHelpers.treeItemHasChildren(treeItem));
currentNode.expand();
String nodeName = nodeNames[i];
- bot.waitUntil(ConditionHelpers.IsTreeChildNodeAvailable(nodeName, currentNode));
+ try {
+ bot.waitUntil(ConditionHelpers.IsTreeChildNodeAvailable(nodeName, currentNode));
+ } catch (TimeoutException e) {
+ //FIXME: Sometimes in a JFace TreeViewer, it expands to nothing. Need to find out why.
+ currentNode.collapse();
+ currentNode.expand();
+ bot.waitUntil(ConditionHelpers.IsTreeChildNodeAvailable(nodeName, currentNode));
+ }
+
SWTBotTreeItem newNode = currentNode.getNode(nodeName);
currentNode = newNode;
}
return currentNode;
}
+
+ /**
+ * Press the keyboard shortcut that goes to the top of a tree widget. The
+ * key combination can differ on different platforms.
+ *
+ * @param keyboard
+ * the keyboard to use
+ */
+ public static void pressShortcutGoToTreeTop(Keyboard keyboard) {
+ if (SWTUtils.isMac()) {
+ keyboard.pressShortcut(Keystrokes.ALT, Keystrokes.UP);
+ } else {
+ keyboard.pressShortcut(Keystrokes.HOME);
+ }
+ }
+
+ /**
+ * Get the active events editor. Note that this will wait until such editor
+ * is available.
+ *
+ * @param workbenchBot
+ * a given workbench bot
+ * @return the active events editor
+ */
+ public static SWTBotEditor activeEventsEditor(final SWTWorkbenchBot workbenchBot) {
+ ConditionHelpers.ActiveEventsEditor condition = new ConditionHelpers.ActiveEventsEditor(workbenchBot, null);
+ workbenchBot.waitUntil(condition);
+ return condition.getActiveEditor();
+ }
+
+ /**
+ * Get the active events editor. Note that this will wait until such editor
+ * is available.
+ *
+ * @param workbenchBot
+ * a given workbench bot
+ * @param editorTitle
+ * the desired editor title. If null, any active events editor
+ * will be considered valid.
+ * @return the active events editor
+ */
+ public static SWTBotEditor activeEventsEditor(final SWTWorkbenchBot workbenchBot, String editorTitle) {
+ ConditionHelpers.ActiveEventsEditor condition = new ConditionHelpers.ActiveEventsEditor(workbenchBot, editorTitle);
+ workbenchBot.waitUntil(condition);
+ return condition.getActiveEditor();
+ }
+
+ /**
+ * Open the preferences dialog and return the corresponding shell.
+ *
+ * @param bot
+ * a given workbench bot
+ * @return the preferences shell
+ */
+ public static SWTBotShell openPreferences(SWTBot bot) {
+ if (SWTUtils.isMac()) {
+ // On Mac, the Preferences menu item is under the application name.
+ // For some reason, we can't access the application menu anymore so
+ // we use the keyboard shortcut.
+ try {
+ bot.activeShell().pressShortcut(KeyStroke.getInstance(IKeyLookup.COMMAND_NAME + "+"), KeyStroke.getInstance(","));
+ } catch (ParseException e) {
+ fail();
+ }
+ } else {
+ bot.menu(WINDOW_MENU).menu(PREFERENCES_MENU_ITEM).click();
+ }
+
+ bot.waitUntil(Conditions.shellIsActive(PREFERENCES_MENU_ITEM));
+ return bot.activeShell();
+ }
}