| 1 | /******************************************************************************* |
| 2 | * Copyright (c) 2014, 2015 Ericsson |
| 3 | * |
| 4 | * All rights reserved. This program and the accompanying materials are |
| 5 | * made available under the terms of the Eclipse Public License v1.0 which |
| 6 | * accompanies this distribution, and is available at |
| 7 | * http://www.eclipse.org/legal/epl-v10.html |
| 8 | * |
| 9 | * Contributors: |
| 10 | * Matthew Khouzam - Initial API and implementation |
| 11 | *******************************************************************************/ |
| 12 | |
| 13 | package org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared; |
| 14 | |
| 15 | import static org.junit.Assert.assertNotNull; |
| 16 | import static org.junit.Assert.assertTrue; |
| 17 | import static org.junit.Assert.fail; |
| 18 | |
| 19 | import java.util.Arrays; |
| 20 | import java.util.List; |
| 21 | import java.util.TimeZone; |
| 22 | import java.util.concurrent.atomic.AtomicBoolean; |
| 23 | |
| 24 | import org.apache.log4j.Logger; |
| 25 | import org.eclipse.core.resources.IFolder; |
| 26 | import org.eclipse.core.resources.IProject; |
| 27 | import org.eclipse.core.resources.IResource; |
| 28 | import org.eclipse.core.resources.IResourceVisitor; |
| 29 | import org.eclipse.core.resources.ResourcesPlugin; |
| 30 | import org.eclipse.core.runtime.CoreException; |
| 31 | import org.eclipse.core.runtime.IPath; |
| 32 | import org.eclipse.core.runtime.NullProgressMonitor; |
| 33 | import org.eclipse.jdt.annotation.NonNull; |
| 34 | import org.eclipse.jface.bindings.keys.IKeyLookup; |
| 35 | import org.eclipse.jface.bindings.keys.KeyStroke; |
| 36 | import org.eclipse.jface.bindings.keys.ParseException; |
| 37 | import org.eclipse.swt.events.ControlAdapter; |
| 38 | import org.eclipse.swt.events.ControlEvent; |
| 39 | import org.eclipse.swt.graphics.Point; |
| 40 | import org.eclipse.swt.graphics.Rectangle; |
| 41 | import org.eclipse.swt.widgets.Display; |
| 42 | import org.eclipse.swt.widgets.Shell; |
| 43 | import org.eclipse.swt.widgets.Table; |
| 44 | import org.eclipse.swt.widgets.TableItem; |
| 45 | import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; |
| 46 | import org.eclipse.swtbot.eclipse.finder.matchers.WidgetMatcherFactory; |
| 47 | import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; |
| 48 | import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; |
| 49 | import org.eclipse.swtbot.swt.finder.SWTBot; |
| 50 | import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; |
| 51 | import org.eclipse.swtbot.swt.finder.keyboard.Keyboard; |
| 52 | import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; |
| 53 | import org.eclipse.swtbot.swt.finder.results.Result; |
| 54 | import org.eclipse.swtbot.swt.finder.results.VoidResult; |
| 55 | import org.eclipse.swtbot.swt.finder.utils.MessageFormat; |
| 56 | import org.eclipse.swtbot.swt.finder.utils.SWTUtils; |
| 57 | import org.eclipse.swtbot.swt.finder.waits.Conditions; |
| 58 | import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; |
| 59 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotButton; |
| 60 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotCheckBox; |
| 61 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; |
| 62 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; |
| 63 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; |
| 64 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; |
| 65 | import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; |
| 66 | import org.eclipse.swtbot.swt.finder.widgets.TimeoutException; |
| 67 | import org.eclipse.tracecompass.internal.tmf.ui.project.operations.NewExperimentOperation; |
| 68 | import org.eclipse.tracecompass.tmf.ui.editors.TmfEventsEditor; |
| 69 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentElement; |
| 70 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentFolder; |
| 71 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfOpenTraceHelper; |
| 72 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectElement; |
| 73 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectRegistry; |
| 74 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement; |
| 75 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceFolder; |
| 76 | import org.eclipse.tracecompass.tmf.ui.project.model.TmfTracesFolder; |
| 77 | import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ConditionHelpers.ProjectElementHasChild; |
| 78 | import org.eclipse.tracecompass.tmf.ui.tests.shared.WaitUtils; |
| 79 | import org.eclipse.tracecompass.tmf.ui.views.TracingPerspectiveFactory; |
| 80 | import org.eclipse.ui.IEditorPart; |
| 81 | import org.eclipse.ui.IEditorReference; |
| 82 | import org.eclipse.ui.IPageLayout; |
| 83 | import org.eclipse.ui.PartInitException; |
| 84 | import org.eclipse.ui.PlatformUI; |
| 85 | import org.eclipse.ui.WorkbenchException; |
| 86 | import org.hamcrest.Matcher; |
| 87 | |
| 88 | /** |
| 89 | * SWTBot Helper functions |
| 90 | * |
| 91 | * @author Matthew Khouzam |
| 92 | */ |
| 93 | @SuppressWarnings("restriction") |
| 94 | public final class SWTBotUtils { |
| 95 | |
| 96 | private static final String WINDOW_MENU = "Window"; |
| 97 | private static final String PREFERENCES_MENU_ITEM = "Preferences"; |
| 98 | private static boolean fPrintedEnvironment = false; |
| 99 | private static Logger log = Logger.getLogger(SWTBotUtils.class); |
| 100 | |
| 101 | private SWTBotUtils() { |
| 102 | |
| 103 | } |
| 104 | |
| 105 | private static final String TRACING_PERSPECTIVE_ID = TracingPerspectiveFactory.ID; |
| 106 | |
| 107 | /** |
| 108 | * Waits for all Eclipse jobs to finish. Times out after |
| 109 | * WaitUtils#MAX_JOBS_WAIT_TIME by default. |
| 110 | * |
| 111 | * @throws RuntimeException |
| 112 | * once the waiting time passes the default maximum value |
| 113 | * |
| 114 | * @deprecated Use {@link WaitUtils#waitForJobs()} instead |
| 115 | */ |
| 116 | @Deprecated |
| 117 | public static void waitForJobs() { |
| 118 | WaitUtils.waitForJobs(); |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Sleeps current thread for a given time. |
| 123 | * |
| 124 | * @param waitTimeMillis |
| 125 | * time in milliseconds to wait |
| 126 | */ |
| 127 | public static void delay(final long waitTimeMillis) { |
| 128 | try { |
| 129 | Thread.sleep(waitTimeMillis); |
| 130 | } catch (final InterruptedException e) { |
| 131 | // Ignored |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | /** |
| 136 | * Create a tracing project |
| 137 | * |
| 138 | * @param projectName |
| 139 | * the name of the tracing project |
| 140 | */ |
| 141 | public static void createProject(final String projectName) { |
| 142 | /* |
| 143 | * Make a new test |
| 144 | */ |
| 145 | UIThreadRunnable.syncExec(new VoidResult() { |
| 146 | @Override |
| 147 | public void run() { |
| 148 | IProject project = TmfProjectRegistry.createProject(projectName, null, new NullProgressMonitor()); |
| 149 | assertNotNull(project); |
| 150 | } |
| 151 | }); |
| 152 | |
| 153 | WaitUtils.waitForJobs(); |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Deletes a project |
| 158 | * |
| 159 | * @param projectName |
| 160 | * the name of the tracing project |
| 161 | * @param deleteResources |
| 162 | * whether or not to deleted resources under the project |
| 163 | * @param bot |
| 164 | * the workbench bot |
| 165 | */ |
| 166 | public static void deleteProject(final String projectName, boolean deleteResources, SWTWorkbenchBot bot) { |
| 167 | // Wait for any analysis to complete because it might create |
| 168 | // supplementary files |
| 169 | WaitUtils.waitForJobs(); |
| 170 | try { |
| 171 | ResourcesPlugin.getWorkspace().getRoot().getProject(projectName).refreshLocal(IResource.DEPTH_INFINITE, null); |
| 172 | } catch (CoreException e) { |
| 173 | } |
| 174 | |
| 175 | WaitUtils.waitForJobs(); |
| 176 | |
| 177 | closeSecondaryShells(bot); |
| 178 | WaitUtils.waitForJobs(); |
| 179 | |
| 180 | final SWTBotView projectViewBot = bot.viewById(IPageLayout.ID_PROJECT_EXPLORER); |
| 181 | projectViewBot.setFocus(); |
| 182 | |
| 183 | SWTBotTree treeBot = projectViewBot.bot().tree(); |
| 184 | SWTBotTreeItem treeItem = treeBot.getTreeItem(projectName); |
| 185 | SWTBotMenu contextMenu = treeItem.contextMenu("Delete"); |
| 186 | contextMenu.click(); |
| 187 | |
| 188 | if (deleteResources) { |
| 189 | bot.shell("Delete Resources").setFocus(); |
| 190 | final SWTBotCheckBox checkBox = bot.checkBox(); |
| 191 | bot.waitUntil(Conditions.widgetIsEnabled(checkBox)); |
| 192 | checkBox.click(); |
| 193 | } |
| 194 | |
| 195 | final SWTBotButton okButton = bot.button("OK"); |
| 196 | bot.waitUntil(Conditions.widgetIsEnabled(okButton)); |
| 197 | okButton.click(); |
| 198 | |
| 199 | WaitUtils.waitForJobs(); |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Deletes a project and its resources |
| 204 | * |
| 205 | * @param projectName |
| 206 | * the name of the tracing project |
| 207 | * @param bot |
| 208 | * the workbench bot |
| 209 | */ |
| 210 | public static void deleteProject(String projectName, SWTWorkbenchBot bot) { |
| 211 | deleteProject(projectName, true, bot); |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Creates an experiment |
| 216 | * |
| 217 | * @param bot |
| 218 | * a given workbench bot |
| 219 | * @param projectName |
| 220 | * the name of the project, creates the project if needed |
| 221 | * @param expName |
| 222 | * the experiment name |
| 223 | */ |
| 224 | public static void createExperiment(SWTWorkbenchBot bot, String projectName, final @NonNull String expName) { |
| 225 | IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); |
| 226 | TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, true); |
| 227 | TmfExperimentFolder expFolder = tmfProject.getExperimentsFolder(); |
| 228 | assertNotNull(expFolder); |
| 229 | NewExperimentOperation operation = new NewExperimentOperation(expFolder, expName); |
| 230 | operation.run(new NullProgressMonitor()); |
| 231 | |
| 232 | bot.waitUntil(new DefaultCondition() { |
| 233 | @Override |
| 234 | public boolean test() throws Exception { |
| 235 | TmfExperimentElement experiment = expFolder.getExperiment(expName); |
| 236 | return experiment != null; |
| 237 | } |
| 238 | |
| 239 | @Override |
| 240 | public String getFailureMessage() { |
| 241 | return "Experiment (" + expName + ") couldn't be created"; |
| 242 | } |
| 243 | }); |
| 244 | } |
| 245 | |
| 246 | |
| 247 | /** |
| 248 | * Focus on the main window |
| 249 | * |
| 250 | * @param shellBots |
| 251 | * swtbotshells for all the shells |
| 252 | */ |
| 253 | public static void focusMainWindow(SWTBotShell[] shellBots) { |
| 254 | SWTBotShell mainShell = getMainShell(shellBots); |
| 255 | if (mainShell != null) { |
| 256 | mainShell.activate(); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | private static SWTBotShell getMainShell(SWTBotShell[] shellBots) { |
| 261 | SWTBotShell mainShell = null; |
| 262 | for (SWTBotShell shellBot : shellBots) { |
| 263 | if (shellBot.getText().toLowerCase().contains("eclipse")) { |
| 264 | mainShell = shellBot; |
| 265 | } |
| 266 | } |
| 267 | return mainShell; |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Close all non-main shells that are visible. |
| 272 | * |
| 273 | * @param bot |
| 274 | * the workbench bot |
| 275 | */ |
| 276 | public static void closeSecondaryShells(SWTWorkbenchBot bot) { |
| 277 | SWTBotShell[] shells = bot.shells(); |
| 278 | SWTBotShell mainShell = getMainShell(shells); |
| 279 | if (mainShell == null) { |
| 280 | return; |
| 281 | } |
| 282 | |
| 283 | // Close all non-main shell but make sure we don't close an invisible |
| 284 | // shell such the special "limbo shell" that Eclipse needs to work |
| 285 | Arrays.stream(shells) |
| 286 | .filter(shell -> shell != mainShell) |
| 287 | .filter(SWTBotShell::isVisible) |
| 288 | .peek(shell -> log.debug(MessageFormat.format("Closing lingering shell with title {0}", shell.getText()))) |
| 289 | .forEach(SWTBotShell::close); |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * Close a view with a title |
| 294 | * |
| 295 | * @param title |
| 296 | * the title, like "welcome" |
| 297 | * @param bot |
| 298 | * the workbench bot |
| 299 | */ |
| 300 | public static void closeView(String title, SWTWorkbenchBot bot) { |
| 301 | final List<SWTBotView> openViews = bot.views(); |
| 302 | for (SWTBotView view : openViews) { |
| 303 | if (view.getTitle().equalsIgnoreCase(title)) { |
| 304 | view.close(); |
| 305 | bot.waitUntil(ConditionHelpers.ViewIsClosed(view)); |
| 306 | } |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | /** |
| 311 | * Close a view with an id |
| 312 | * |
| 313 | * @param viewId |
| 314 | * the view id, like "org.eclipse.linuxtools.tmf.ui.views.histogram" |
| 315 | * @param bot |
| 316 | * the workbench bot |
| 317 | */ |
| 318 | public static void closeViewById(String viewId, SWTWorkbenchBot bot) { |
| 319 | final SWTBotView view = bot.viewById(viewId); |
| 320 | view.close(); |
| 321 | bot.waitUntil(ConditionHelpers.ViewIsClosed(view)); |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Switch to the tracing perspective |
| 326 | */ |
| 327 | public static void switchToTracingPerspective() { |
| 328 | switchToPerspective(TRACING_PERSPECTIVE_ID); |
| 329 | } |
| 330 | |
| 331 | /** |
| 332 | * Switch to a given perspective |
| 333 | * |
| 334 | * @param id |
| 335 | * the perspective id (like |
| 336 | * "org.eclipse.linuxtools.tmf.ui.perspective" |
| 337 | */ |
| 338 | public static void switchToPerspective(final String id) { |
| 339 | UIThreadRunnable.syncExec(new VoidResult() { |
| 340 | @Override |
| 341 | public void run() { |
| 342 | try { |
| 343 | PlatformUI.getWorkbench().showPerspective(id, PlatformUI.getWorkbench().getActiveWorkbenchWindow()); |
| 344 | } catch (WorkbenchException e) { |
| 345 | fail(e.getMessage()); |
| 346 | } |
| 347 | } |
| 348 | }); |
| 349 | } |
| 350 | |
| 351 | /** |
| 352 | * Initialize the environment for SWTBot |
| 353 | */ |
| 354 | public static void initialize() { |
| 355 | failIfUIThread(); |
| 356 | |
| 357 | SWTWorkbenchBot bot = new SWTWorkbenchBot(); |
| 358 | UIThreadRunnable.syncExec(() -> { |
| 359 | printEnvironment(); |
| 360 | |
| 361 | // There seems to be problems on some system where the main shell is |
| 362 | // not in focus initially. This was seen using Xvfb and Xephyr on some occasions. |
| 363 | focusMainWindow(bot.shells()); |
| 364 | |
| 365 | Shell shell = bot.activeShell().widget; |
| 366 | |
| 367 | // Only adjust shell if it appears to be the top-most |
| 368 | if (shell.getParent() == null) { |
| 369 | makeShellFullyVisible(shell); |
| 370 | } |
| 371 | }); |
| 372 | } |
| 373 | |
| 374 | private static void printEnvironment() { |
| 375 | if (fPrintedEnvironment) { |
| 376 | return; |
| 377 | } |
| 378 | |
| 379 | // Print some information about the environment that could affect test outcome |
| 380 | Rectangle bounds = Display.getDefault().getBounds(); |
| 381 | System.out.println("Display size: " + bounds.width + "x" + bounds.height); |
| 382 | |
| 383 | String osVersion = System.getProperty("os.version"); |
| 384 | if (osVersion != null) { |
| 385 | System.out.println("OS version=" + osVersion); |
| 386 | } |
| 387 | String gtkVersion = System.getProperty("org.eclipse.swt.internal.gtk.version"); |
| 388 | if (gtkVersion != null) { |
| 389 | System.out.println("GTK version=" + gtkVersion); |
| 390 | // Try to print the GTK theme information as behavior can change depending on the theme |
| 391 | String gtkTheme = System.getProperty("org.eclipse.swt.internal.gtk.theme"); |
| 392 | System.out.println("GTK theme=" + (gtkTheme == null ? "unknown" : gtkTheme)); |
| 393 | |
| 394 | String overlayScrollbar = System.getenv("LIBOVERLAY_SCROLLBAR"); |
| 395 | if (overlayScrollbar != null) { |
| 396 | System.out.println("LIBOVERLAY_SCROLLBAR=" + overlayScrollbar); |
| 397 | } |
| 398 | String ubuntuMenuProxy = System.getenv("UBUNTU_MENUPROXY"); |
| 399 | if (ubuntuMenuProxy != null) { |
| 400 | System.out.println("UBUNTU_MENUPROXY=" + ubuntuMenuProxy); |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | System.out.println("Time zone: " + TimeZone.getDefault().getDisplayName()); |
| 405 | |
| 406 | fPrintedEnvironment = true; |
| 407 | } |
| 408 | |
| 409 | /** |
| 410 | * If the test is running in the UI thread then fail |
| 411 | */ |
| 412 | private static void failIfUIThread() { |
| 413 | if (Display.getCurrent() != null && Display.getCurrent().getThread() == Thread.currentThread()) { |
| 414 | 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" |
| 415 | + " that useUIThread is set to false in the pom.xml"); |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | /** |
| 420 | * Try to make the shell fully visible in the display. If the shell cannot |
| 421 | * fit the display, it will be positioned so that top-left corner is at |
| 422 | * <code>(0, 0)</code> in display-relative coordinates. |
| 423 | * |
| 424 | * @param shell |
| 425 | * the shell to make fully visible |
| 426 | */ |
| 427 | private static void makeShellFullyVisible(Shell shell) { |
| 428 | Rectangle displayBounds = shell.getDisplay().getBounds(); |
| 429 | Point absCoord = shell.toDisplay(0, 0); |
| 430 | Point shellSize = shell.getSize(); |
| 431 | |
| 432 | Point newLocation = new Point(absCoord.x, absCoord.y); |
| 433 | newLocation.x = Math.max(0, Math.min(absCoord.x, displayBounds.width - shellSize.x)); |
| 434 | newLocation.y = Math.max(0, Math.min(absCoord.y, displayBounds.height - shellSize.y)); |
| 435 | if (!newLocation.equals(absCoord)) { |
| 436 | shell.setLocation(newLocation); |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | /** |
| 441 | * Open a trace, this does not perform any validation though |
| 442 | * |
| 443 | * @param projectName |
| 444 | * The project name |
| 445 | * @param tracePath |
| 446 | * the path of the trace file (absolute or relative) |
| 447 | * @param traceType |
| 448 | * the trace type id (eg: org.eclipse.linuxtools.btf.trace) |
| 449 | */ |
| 450 | public static void openTrace(final String projectName, final String tracePath, final String traceType) { |
| 451 | openTrace(projectName, tracePath, traceType, true); |
| 452 | } |
| 453 | |
| 454 | /** |
| 455 | * Open a trace, this does not perform any validation though |
| 456 | * |
| 457 | * @param projectName |
| 458 | * The project name |
| 459 | * @param tracePath |
| 460 | * the path of the trace file (absolute or relative) |
| 461 | * @param traceType |
| 462 | * the trace type id (eg: org.eclipse.linuxtools.btf.trace) |
| 463 | * @param delay |
| 464 | * delay and wait for jobs |
| 465 | */ |
| 466 | public static void openTrace(final String projectName, final String tracePath, final String traceType, boolean delay) { |
| 467 | final Exception exception[] = new Exception[1]; |
| 468 | exception[0] = null; |
| 469 | UIThreadRunnable.syncExec(new VoidResult() { |
| 470 | @Override |
| 471 | public void run() { |
| 472 | try { |
| 473 | IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); |
| 474 | TmfTraceFolder destinationFolder = TmfProjectRegistry.getProject(project, true).getTracesFolder(); |
| 475 | TmfOpenTraceHelper.openTraceFromPath(destinationFolder, tracePath, PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), traceType); |
| 476 | } catch (CoreException e) { |
| 477 | exception[0] = e; |
| 478 | } |
| 479 | } |
| 480 | }); |
| 481 | if (exception[0] != null) { |
| 482 | fail(exception[0].getMessage()); |
| 483 | } |
| 484 | |
| 485 | if (delay) { |
| 486 | delay(1000); |
| 487 | WaitUtils.waitForJobs(); |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | /** |
| 492 | * Finds an editor and sets focus to the editor |
| 493 | * |
| 494 | * @param bot |
| 495 | * the workbench bot |
| 496 | * @param editorName |
| 497 | * the editor name |
| 498 | * @return the corresponding SWTBotEditor |
| 499 | */ |
| 500 | public static SWTBotEditor activateEditor(SWTWorkbenchBot bot, String editorName) { |
| 501 | Matcher<IEditorReference> matcher = WidgetMatcherFactory.withPartName(editorName); |
| 502 | final SWTBotEditor editorBot = bot.editor(matcher); |
| 503 | IEditorPart iep = editorBot.getReference().getEditor(true); |
| 504 | final TmfEventsEditor tmfEd = (TmfEventsEditor) iep; |
| 505 | editorBot.show(); |
| 506 | UIThreadRunnable.syncExec(new VoidResult() { |
| 507 | @Override |
| 508 | public void run() { |
| 509 | tmfEd.setFocus(); |
| 510 | } |
| 511 | }); |
| 512 | |
| 513 | WaitUtils.waitForJobs(); |
| 514 | SWTBotUtils.delay(1000); |
| 515 | assertNotNull(tmfEd); |
| 516 | return editorBot; |
| 517 | } |
| 518 | |
| 519 | /** |
| 520 | * Opens a trace in an editor and get the TmfEventsEditor |
| 521 | * |
| 522 | * @param bot |
| 523 | * the workbench bot |
| 524 | * @param projectName |
| 525 | * the name of the project that contains the trace |
| 526 | * @param elementPath |
| 527 | * the trace element path (relative to Traces folder) |
| 528 | * @return TmfEventsEditor the opened editor |
| 529 | */ |
| 530 | public static TmfEventsEditor openEditor(SWTWorkbenchBot bot, String projectName, IPath elementPath) { |
| 531 | final SWTBotView projectExplorerView = bot.viewById(IPageLayout.ID_PROJECT_EXPLORER); |
| 532 | projectExplorerView.setFocus(); |
| 533 | SWTBot projectExplorerBot = projectExplorerView.bot(); |
| 534 | |
| 535 | final SWTBotTree tree = projectExplorerBot.tree(); |
| 536 | projectExplorerBot.waitUntil(ConditionHelpers.IsTreeNodeAvailable(projectName, tree)); |
| 537 | final SWTBotTreeItem treeItem = tree.getTreeItem(projectName); |
| 538 | treeItem.expand(); |
| 539 | |
| 540 | SWTBotTreeItem tracesNode = getTraceProjectItem(projectExplorerBot, treeItem, TmfTracesFolder.TRACES_FOLDER_NAME); |
| 541 | tracesNode.expand(); |
| 542 | |
| 543 | SWTBotTreeItem currentItem = tracesNode; |
| 544 | for (String segment : elementPath.segments()) { |
| 545 | currentItem = getTraceProjectItem(projectExplorerBot, currentItem, segment); |
| 546 | currentItem.doubleClick(); |
| 547 | } |
| 548 | |
| 549 | SWTBotEditor editor = bot.editorByTitle(elementPath.toString()); |
| 550 | IEditorPart editorPart = editor.getReference().getEditor(false); |
| 551 | assertTrue(editorPart instanceof TmfEventsEditor); |
| 552 | return (TmfEventsEditor) editorPart; |
| 553 | } |
| 554 | |
| 555 | /** |
| 556 | * Returns the child tree item of the specified item at the given sub-path. |
| 557 | * The project element labels may have a count suffix in the format ' [n]'. |
| 558 | * |
| 559 | * @param bot |
| 560 | * a given workbench bot |
| 561 | * @param parentItem |
| 562 | * the parent tree item |
| 563 | * @param path |
| 564 | * the desired child element sub-path (without suffix) |
| 565 | * @return the a {@link SWTBotTreeItem} with the specified name |
| 566 | */ |
| 567 | public static SWTBotTreeItem getTraceProjectItem(SWTBot bot, final SWTBotTreeItem parentItem, final String... path) { |
| 568 | SWTBotTreeItem item = parentItem; |
| 569 | for (String name : path) { |
| 570 | item = getTraceProjectItem(bot, item, name); |
| 571 | } |
| 572 | return item; |
| 573 | } |
| 574 | |
| 575 | /** |
| 576 | * Returns the child tree item of the specified item with the given name. |
| 577 | * The project element label may have a count suffix in the format ' [n]'. |
| 578 | * |
| 579 | * @param bot |
| 580 | * a given workbench bot |
| 581 | * @param parentItem |
| 582 | * the parent tree item |
| 583 | * @param name |
| 584 | * the desired child element name (without suffix) |
| 585 | * @return the a {@link SWTBotTreeItem} with the specified name |
| 586 | */ |
| 587 | public static SWTBotTreeItem getTraceProjectItem(SWTBot bot, final SWTBotTreeItem parentItem, final String name) { |
| 588 | ProjectElementHasChild condition = new ProjectElementHasChild(parentItem, name); |
| 589 | bot.waitUntil(condition); |
| 590 | return condition.getItem(); |
| 591 | } |
| 592 | |
| 593 | /** |
| 594 | * Select the traces folder |
| 595 | * |
| 596 | * @param bot |
| 597 | * a given workbench bot |
| 598 | * @param projectName |
| 599 | * the name of the project (it needs to exist or else it would |
| 600 | * time out) |
| 601 | * @return a {@link SWTBotTreeItem} of the "Traces" folder |
| 602 | */ |
| 603 | public static SWTBotTreeItem selectTracesFolder(SWTWorkbenchBot bot, String projectName) { |
| 604 | SWTBotTreeItem projectTreeItem = selectProject(bot, projectName); |
| 605 | projectTreeItem.select(); |
| 606 | SWTBotTreeItem tracesFolderItem = getTraceProjectItem(bot, projectTreeItem, TmfTracesFolder.TRACES_FOLDER_NAME); |
| 607 | tracesFolderItem.select(); |
| 608 | return tracesFolderItem; |
| 609 | } |
| 610 | |
| 611 | /** |
| 612 | * Clear the traces folder |
| 613 | * |
| 614 | * @param bot |
| 615 | * a given workbench bot |
| 616 | * @param projectName |
| 617 | * the name of the project (needs to exist) |
| 618 | */ |
| 619 | public static void clearTracesFolder(SWTWorkbenchBot bot, String projectName) { |
| 620 | IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); |
| 621 | TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, false); |
| 622 | TmfTraceFolder tracesFolder = tmfProject.getTracesFolder(); |
| 623 | try { |
| 624 | for (TmfTraceElement traceElement : tracesFolder.getTraces()) { |
| 625 | traceElement.delete(null); |
| 626 | } |
| 627 | |
| 628 | final IFolder resource = tracesFolder.getResource(); |
| 629 | resource.accept(new IResourceVisitor() { |
| 630 | @Override |
| 631 | public boolean visit(IResource visitedResource) throws CoreException { |
| 632 | if (visitedResource != resource) { |
| 633 | visitedResource.delete(true, null); |
| 634 | } |
| 635 | return true; |
| 636 | } |
| 637 | }, IResource.DEPTH_ONE, 0); |
| 638 | } catch (CoreException e) { |
| 639 | fail(e.getMessage()); |
| 640 | } |
| 641 | |
| 642 | bot.waitUntil(new DefaultCondition() { |
| 643 | private int fTraceNb = 0; |
| 644 | |
| 645 | @Override |
| 646 | public boolean test() throws Exception { |
| 647 | List<TmfTraceElement> traces = tracesFolder.getTraces(); |
| 648 | fTraceNb = traces.size(); |
| 649 | return fTraceNb == 0; |
| 650 | } |
| 651 | |
| 652 | @Override |
| 653 | public String getFailureMessage() { |
| 654 | return "Traces Folder not empty (" + fTraceNb + ")"; |
| 655 | } |
| 656 | }); |
| 657 | } |
| 658 | |
| 659 | /** |
| 660 | * Clear the trace folder (using the UI) |
| 661 | * |
| 662 | * @param bot |
| 663 | * a given workbench bot |
| 664 | * @param projectName |
| 665 | * the name of the project (needs to exist) |
| 666 | */ |
| 667 | public static void clearTracesFolderUI(SWTWorkbenchBot bot, String projectName) { |
| 668 | SWTBotTreeItem tracesFolder = selectTracesFolder(bot, projectName); |
| 669 | tracesFolder.contextMenu().menu("Clear").click(); |
| 670 | String CONFIRM_CLEAR_DIALOG_TITLE = "Confirm Clear"; |
| 671 | bot.waitUntil(Conditions.shellIsActive(CONFIRM_CLEAR_DIALOG_TITLE)); |
| 672 | |
| 673 | SWTBotShell shell = bot.shell(CONFIRM_CLEAR_DIALOG_TITLE); |
| 674 | shell.bot().button("Yes").click(); |
| 675 | bot.waitUntil(Conditions.shellCloses(shell)); |
| 676 | bot.waitWhile(ConditionHelpers.treeItemHasChildren(tracesFolder)); |
| 677 | } |
| 678 | |
| 679 | /** |
| 680 | * Clear the experiment folder |
| 681 | * |
| 682 | * @param bot |
| 683 | * a given workbench bot |
| 684 | * @param projectName |
| 685 | * the name of the project (needs to exist) |
| 686 | */ |
| 687 | public static void clearExperimentFolder(SWTWorkbenchBot bot, String projectName) { |
| 688 | IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName); |
| 689 | TmfProjectElement tmfProject = TmfProjectRegistry.getProject(project, false); |
| 690 | TmfExperimentFolder expFolder = tmfProject.getExperimentsFolder(); |
| 691 | expFolder.getExperiments().forEach(experiment -> { |
| 692 | IResource resource = experiment.getResource(); |
| 693 | try { |
| 694 | // Close the experiment if open |
| 695 | experiment.closeEditors(); |
| 696 | |
| 697 | IPath path = resource.getLocation(); |
| 698 | if (path != null) { |
| 699 | // Delete supplementary files |
| 700 | experiment.deleteSupplementaryFolder(); |
| 701 | } |
| 702 | // Finally, delete the experiment |
| 703 | resource.delete(true, null); |
| 704 | } catch (CoreException e) { |
| 705 | fail(e.getMessage()); |
| 706 | } |
| 707 | }); |
| 708 | |
| 709 | bot.waitUntil(new DefaultCondition() { |
| 710 | private int fExperimentNb = 0; |
| 711 | |
| 712 | @Override |
| 713 | public boolean test() throws Exception { |
| 714 | List<TmfExperimentElement> experiments = expFolder.getExperiments(); |
| 715 | fExperimentNb = experiments.size(); |
| 716 | return fExperimentNb == 0; |
| 717 | } |
| 718 | |
| 719 | @Override |
| 720 | public String getFailureMessage() { |
| 721 | return "Experiment Folder not empty (" + fExperimentNb + ")"; |
| 722 | } |
| 723 | }); |
| 724 | } |
| 725 | |
| 726 | /** |
| 727 | * Select the project in Project Explorer |
| 728 | * |
| 729 | * @param bot |
| 730 | * a given workbench bot |
| 731 | * @param projectName |
| 732 | * the name of the project (it needs to exist or else it would time out) |
| 733 | * @return a {@link SWTBotTreeItem} of the project |
| 734 | */ |
| 735 | public static SWTBotTreeItem selectProject(SWTWorkbenchBot bot, String projectName) { |
| 736 | SWTBotView projectExplorerBot = bot.viewByTitle("Project Explorer"); |
| 737 | projectExplorerBot.show(); |
| 738 | // FIXME: Bug 496519. Sometimes, the tree becomes disabled for a certain |
| 739 | // amount of time. This can happen during a long running operation |
| 740 | // (BusyIndicator.showWhile) which brings up the modal dialog "operation |
| 741 | // in progress" and this disables all shells |
| 742 | projectExplorerBot.bot().waitUntil(Conditions.widgetIsEnabled(projectExplorerBot.bot().tree())); |
| 743 | SWTBotTreeItem treeItem = projectExplorerBot.bot().tree().getTreeItem(projectName); |
| 744 | treeItem.select(); |
| 745 | return treeItem; |
| 746 | } |
| 747 | |
| 748 | /** |
| 749 | * Open a view by id. |
| 750 | * |
| 751 | * @param id |
| 752 | * view id. |
| 753 | */ |
| 754 | public static void openView(final String id) { |
| 755 | final PartInitException res[] = new PartInitException[1]; |
| 756 | UIThreadRunnable.syncExec(new VoidResult() { |
| 757 | @Override |
| 758 | public void run() { |
| 759 | try { |
| 760 | PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(id); |
| 761 | } catch (PartInitException e) { |
| 762 | res[0] = e; |
| 763 | } |
| 764 | } |
| 765 | }); |
| 766 | if (res[0] != null) { |
| 767 | fail(res[0].getMessage()); |
| 768 | } |
| 769 | WaitUtils.waitForJobs(); |
| 770 | } |
| 771 | |
| 772 | /** |
| 773 | * Maximize a table |
| 774 | * |
| 775 | * @param tableBot |
| 776 | * the {@link SWTBotTable} table |
| 777 | */ |
| 778 | public static void maximizeTable(SWTBotTable tableBot) { |
| 779 | final AtomicBoolean controlResized = new AtomicBoolean(); |
| 780 | UIThreadRunnable.syncExec(new VoidResult() { |
| 781 | @Override |
| 782 | public void run() { |
| 783 | tableBot.widget.addControlListener(new ControlAdapter() { |
| 784 | @Override |
| 785 | public void controlResized(ControlEvent e) { |
| 786 | tableBot.widget.removeControlListener(this); |
| 787 | controlResized.set(true); |
| 788 | } |
| 789 | }); |
| 790 | } |
| 791 | }); |
| 792 | try { |
| 793 | tableBot.pressShortcut(KeyStroke.getInstance(IKeyLookup.CTRL_NAME + "+"), KeyStroke.getInstance("M")); |
| 794 | } catch (ParseException e) { |
| 795 | fail(); |
| 796 | } |
| 797 | new SWTBot().waitUntil(new DefaultCondition() { |
| 798 | @Override |
| 799 | public boolean test() throws Exception { |
| 800 | return controlResized.get(); |
| 801 | } |
| 802 | |
| 803 | @Override |
| 804 | public String getFailureMessage() { |
| 805 | return "Control was not resized"; |
| 806 | } |
| 807 | }); |
| 808 | } |
| 809 | |
| 810 | /** |
| 811 | * Get the bounds of a cell (SWT.Rectangle) for the specified row and column |
| 812 | * index in a table |
| 813 | * |
| 814 | * @param table |
| 815 | * the table |
| 816 | * @param row |
| 817 | * the row of the table to look up |
| 818 | * @param col |
| 819 | * the column of the table to look up |
| 820 | * @return the bounds in display relative coordinates |
| 821 | */ |
| 822 | public static Rectangle getCellBounds(final Table table, final int row, final int col) { |
| 823 | return UIThreadRunnable.syncExec(new Result<Rectangle>() { |
| 824 | @Override |
| 825 | public Rectangle run() { |
| 826 | TableItem item = table.getItem(row); |
| 827 | Rectangle bounds = item.getBounds(col); |
| 828 | Point p = table.toDisplay(bounds.x, bounds.y); |
| 829 | Rectangle rect = new Rectangle(p.x, p.y, bounds.width, bounds.height); |
| 830 | return rect; |
| 831 | } |
| 832 | }); |
| 833 | } |
| 834 | |
| 835 | /** |
| 836 | * Get the tree item from a tree at the specified location |
| 837 | * |
| 838 | * @param bot |
| 839 | * the SWTBot |
| 840 | * @param tree |
| 841 | * the tree to find the tree item in |
| 842 | * @param nodeNames |
| 843 | * the path to the tree item, in the form of node names (from |
| 844 | * parent to child). |
| 845 | * @return the tree item |
| 846 | */ |
| 847 | public static SWTBotTreeItem getTreeItem(SWTBot bot, SWTBotTree tree, String... nodeNames) { |
| 848 | if (nodeNames.length == 0) { |
| 849 | return null; |
| 850 | } |
| 851 | |
| 852 | bot.waitUntil(ConditionHelpers.IsTreeNodeAvailable(nodeNames[0], tree)); |
| 853 | SWTBotTreeItem currentNode = tree.getTreeItem(nodeNames[0]); |
| 854 | return getTreeItem(bot, currentNode, Arrays.copyOfRange(nodeNames, 1, nodeNames.length)); |
| 855 | } |
| 856 | |
| 857 | /** |
| 858 | * Get the tree item from a parent tree item at the specified location |
| 859 | * |
| 860 | * @param bot |
| 861 | * the SWTBot |
| 862 | * @param treeItem |
| 863 | * the treeItem to find the tree item under |
| 864 | * @param nodeNames |
| 865 | * the path to the tree item, in the form of node names (from |
| 866 | * parent to child). |
| 867 | * @return the tree item |
| 868 | */ |
| 869 | public static SWTBotTreeItem getTreeItem(SWTBot bot, SWTBotTreeItem treeItem, String... nodeNames) { |
| 870 | if (nodeNames.length == 0) { |
| 871 | return treeItem; |
| 872 | } |
| 873 | |
| 874 | SWTBotTreeItem currentNode = treeItem; |
| 875 | for (int i = 0; i < nodeNames.length; i++) { |
| 876 | bot.waitUntil(ConditionHelpers.treeItemHasChildren(treeItem)); |
| 877 | currentNode.expand(); |
| 878 | |
| 879 | String nodeName = nodeNames[i]; |
| 880 | try { |
| 881 | bot.waitUntil(ConditionHelpers.IsTreeChildNodeAvailable(nodeName, currentNode)); |
| 882 | } catch (TimeoutException e) { |
| 883 | //FIXME: Sometimes in a JFace TreeViewer, it expands to nothing. Need to find out why. |
| 884 | currentNode.collapse(); |
| 885 | currentNode.expand(); |
| 886 | bot.waitUntil(ConditionHelpers.IsTreeChildNodeAvailable(nodeName, currentNode)); |
| 887 | } |
| 888 | |
| 889 | SWTBotTreeItem newNode = currentNode.getNode(nodeName); |
| 890 | currentNode = newNode; |
| 891 | } |
| 892 | |
| 893 | return currentNode; |
| 894 | } |
| 895 | |
| 896 | /** |
| 897 | * Press the keyboard shortcut that goes to the top of a tree widget. The |
| 898 | * key combination can differ on different platforms. |
| 899 | * |
| 900 | * @param keyboard |
| 901 | * the keyboard to use |
| 902 | */ |
| 903 | public static void pressShortcutGoToTreeTop(Keyboard keyboard) { |
| 904 | if (SWTUtils.isMac()) { |
| 905 | keyboard.pressShortcut(Keystrokes.ALT, Keystrokes.UP); |
| 906 | } else { |
| 907 | keyboard.pressShortcut(Keystrokes.HOME); |
| 908 | } |
| 909 | } |
| 910 | |
| 911 | /** |
| 912 | * Get the active events editor. Note that this will wait until such editor |
| 913 | * is available. |
| 914 | * |
| 915 | * @param workbenchBot |
| 916 | * a given workbench bot |
| 917 | * @return the active events editor |
| 918 | */ |
| 919 | public static SWTBotEditor activeEventsEditor(final SWTWorkbenchBot workbenchBot) { |
| 920 | ConditionHelpers.ActiveEventsEditor condition = new ConditionHelpers.ActiveEventsEditor(workbenchBot, null); |
| 921 | workbenchBot.waitUntil(condition); |
| 922 | return condition.getActiveEditor(); |
| 923 | } |
| 924 | |
| 925 | /** |
| 926 | * Get the active events editor. Note that this will wait until such editor |
| 927 | * is available. |
| 928 | * |
| 929 | * @param workbenchBot |
| 930 | * a given workbench bot |
| 931 | * @param editorTitle |
| 932 | * the desired editor title. If null, any active events editor |
| 933 | * will be considered valid. |
| 934 | * @return the active events editor |
| 935 | */ |
| 936 | public static SWTBotEditor activeEventsEditor(final SWTWorkbenchBot workbenchBot, String editorTitle) { |
| 937 | ConditionHelpers.ActiveEventsEditor condition = new ConditionHelpers.ActiveEventsEditor(workbenchBot, editorTitle); |
| 938 | workbenchBot.waitUntil(condition); |
| 939 | return condition.getActiveEditor(); |
| 940 | } |
| 941 | |
| 942 | /** |
| 943 | * Open the preferences dialog and return the corresponding shell. |
| 944 | * |
| 945 | * @param bot |
| 946 | * a given workbench bot |
| 947 | * @return the preferences shell |
| 948 | */ |
| 949 | public static SWTBotShell openPreferences(SWTBot bot) { |
| 950 | if (SWTUtils.isMac()) { |
| 951 | // On Mac, the Preferences menu item is under the application name. |
| 952 | // For some reason, we can't access the application menu anymore so |
| 953 | // we use the keyboard shortcut. |
| 954 | try { |
| 955 | bot.activeShell().pressShortcut(KeyStroke.getInstance(IKeyLookup.COMMAND_NAME + "+"), KeyStroke.getInstance(",")); |
| 956 | } catch (ParseException e) { |
| 957 | fail(); |
| 958 | } |
| 959 | } else { |
| 960 | bot.menu(WINDOW_MENU).menu(PREFERENCES_MENU_ITEM).click(); |
| 961 | } |
| 962 | |
| 963 | bot.waitUntil(Conditions.shellIsActive(PREFERENCES_MENU_ITEM)); |
| 964 | return bot.activeShell(); |
| 965 | } |
| 966 | } |