1 /* 2 * Copyright (c) 2006, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package test.java.awt.regtesthelpers; 25 /** 26 * <p>This class contains utilities useful for regression testing. 27 * <p>When using jtreg you would include this class into the build 28 * list via something like: 29 * <pre> 30 @library ../../../regtesthelpers 31 @build Util 32 @run main YourTest 33 </pre> 34 * Note that if you are about to create a test based on 35 * Applet-template, then put those lines into html-file, not in java-file. 36 * <p> And put an 37 * import test.java.awt.regtesthelpers.Util; 38 * into the java source of test. 39 */ 40 41 import java.awt.Component; 42 import java.awt.Frame; 43 import java.awt.Dialog; 44 import java.awt.Window; 45 import java.awt.Button; 46 import java.awt.Point; 47 import java.awt.Dimension; 48 import java.awt.Rectangle; 49 import java.awt.Robot; 50 import java.awt.Toolkit; 51 import java.awt.IllegalComponentStateException; 52 import java.awt.AWTException; 53 import java.awt.AWTEvent; 54 import java.awt.Color; 55 56 import java.awt.event.InputEvent; 57 import java.awt.event.WindowAdapter; 58 import java.awt.event.WindowEvent; 59 import java.awt.event.ActionEvent; 60 import java.awt.event.FocusEvent; 61 import java.awt.event.WindowListener; 62 import java.awt.event.WindowFocusListener; 63 import java.awt.event.FocusListener; 64 import java.awt.event.ActionListener; 65 66 import java.lang.reflect.Constructor; 67 import java.lang.reflect.Field; 68 import java.lang.reflect.InvocationTargetException; 69 import java.lang.reflect.Method; 70 71 import java.security.PrivilegedAction; 72 import java.security.AccessController; 73 74 import java.util.concurrent.atomic.AtomicBoolean; 75 76 public final class Util { 77 private Util() {} // this is a helper class with static methods :) 78 79 private volatile static Robot robot; 80 81 /* 82 * @throws RuntimeException when creation failed 83 */ 84 public static Robot createRobot() { 85 try { 86 if (robot == null) { 87 robot = new Robot(); 88 } 89 return robot; 90 } catch (AWTException e) { 91 throw new RuntimeException("Error: unable to create robot", e); 92 } 93 } 94 95 96 /** 97 * Makes the window visible and waits until it's shown. 98 */ 99 public static void showWindowWait(Window win) { 100 win.setVisible(true); 101 waitTillShown(win); 102 } 103 104 /** 105 * Moves mouse pointer in the center of given {@code comp} component 106 * using {@code robot} parameter. 107 */ 108 public static void pointOnComp(final Component comp, final Robot robot) { 109 Rectangle bounds = new Rectangle(comp.getLocationOnScreen(), comp.getSize()); 110 robot.mouseMove(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); 111 } 112 113 /** 114 * Moves mouse pointer in the center of a given {@code comp} component 115 * and performs a left mouse button click using the {@code robot} parameter 116 * with the {@code delay} delay between press and release. 117 */ 118 public static void clickOnComp(final Component comp, final Robot robot, int delay) { 119 pointOnComp(comp, robot); 120 robot.delay(delay); 121 robot.mousePress(InputEvent.BUTTON1_MASK); 122 robot.delay(delay); 123 robot.mouseRelease(InputEvent.BUTTON1_MASK); 124 } 125 126 /** 127 * Moves mouse pointer in the center of a given {@code comp} component 128 * and performs a left mouse button click using the {@code robot} parameter 129 * with the default delay between press and release. 130 */ 131 public static void clickOnComp(final Component comp, final Robot robot) { 132 clickOnComp(comp, robot, 50); 133 } 134 135 public static Point getTitlePoint(Window decoratedWindow) { 136 Point p = decoratedWindow.getLocationOnScreen(); 137 Dimension d = decoratedWindow.getSize(); 138 return new Point(p.x + (int)(d.getWidth()/2), 139 p.y + (int)(decoratedWindow.getInsets().top/2)); 140 } 141 142 /* 143 * Clicks on a title of Frame/Dialog. 144 * WARNING: it may fail on some platforms when the window is not wide enough. 145 */ 146 public static void clickOnTitle(final Window decoratedWindow, final Robot robot) { 147 if (decoratedWindow instanceof Frame || decoratedWindow instanceof Dialog) { 148 Point p = getTitlePoint(decoratedWindow); 149 robot.mouseMove(p.x, p.y); 150 robot.delay(50); 151 robot.mousePress(InputEvent.BUTTON1_MASK); 152 robot.delay(50); 153 robot.mouseRelease(InputEvent.BUTTON1_MASK); 154 } 155 } 156 157 /** 158 * Tests whether screen pixel has the expected color performing several 159 * attempts. This method is useful for asynchronous window manager where 160 * it's impossible to determine when drawing actually takes place. 161 * 162 * @param x X position of pixel 163 * @param y Y position of pixel 164 * @param color expected color 165 * @param attempts number of attempts to undertake 166 * @param delay delay before each attempt 167 * @param robot a robot to use for retrieving pixel color 168 * @return true if pixel color matches the color expected, otherwise false 169 */ 170 public static boolean testPixelColor(int x, int y, final Color color, int attempts, int delay, final Robot robot) { 171 while (attempts-- > 0) { 172 robot.delay(delay); 173 Color screen = robot.getPixelColor(x, y); 174 if (screen.equals(color)) { 175 return true; 176 } 177 } 178 return false; 179 } 180 181 /** 182 * Tests whether the area within boundaries has the expected color 183 * performing several attempts. This method is useful for asynchronous 184 * window manager where it's impossible to determine when drawing actually 185 * takes place. 186 * 187 * @param bounds position of area 188 * @param color expected color 189 * @param attempts number of attempts to undertake 190 * @param delay delay before each attempt 191 * @param robot a robot to use for retrieving pixel color 192 * @return true if area color matches the color expected, otherwise false 193 */ 194 public static boolean testBoundsColor(final Rectangle bounds, final Color color, int attempts, int delay, final Robot robot) { 195 int right = bounds.x + bounds.width - 1; 196 int bottom = bounds.y + bounds.height - 1; 197 while (attempts-- > 0) { 198 if (testPixelColor(bounds.x, bounds.y, color, 1, delay, robot) 199 && testPixelColor(right, bounds.y, color, 1, 0, robot) 200 && testPixelColor(right, bottom, color, 1, 0, robot) 201 && testPixelColor(bounds.x, bottom, color, 1, 0, robot)) { 202 return true; 203 } 204 } 205 return false; 206 } 207 208 public static void waitForIdle(Robot robot) { 209 if (robot == null) { 210 robot = createRobot(); 211 } 212 robot.waitForIdle(); 213 } 214 215 public static Field getField(final Class klass, final String fieldName) { 216 return AccessController.doPrivileged(new PrivilegedAction<Field>() { 217 public Field run() { 218 try { 219 Field field = klass.getDeclaredField(fieldName); 220 assert (field != null); 221 field.setAccessible(true); 222 return field; 223 } catch (SecurityException se) { 224 throw new RuntimeException("Error: unexpected exception caught!", se); 225 } catch (NoSuchFieldException nsfe) { 226 throw new RuntimeException("Error: unexpected exception caught!", nsfe); 227 } 228 } 229 }); 230 } 231 232 /* 233 * Waits for a notification and for a boolean condition to become true. 234 * The method returns when the above conditions are fullfilled or when the timeout 235 * occurs. 236 * 237 * @param condition the object to be notified and the booelan condition to wait for 238 * @param timeout the maximum time to wait in milliseconds 239 * @param catchExceptions if {@code true} the method catches InterruptedException 240 * @return the final boolean value of the {@code condition} 241 * @throws InterruptedException if the awaiting proccess has been interrupted 242 */ 243 public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout) 244 throws InterruptedException 245 { 246 synchronized (condition) { 247 long startTime = System.currentTimeMillis(); 248 while (!condition.get()) { 249 condition.wait(timeout); 250 if (System.currentTimeMillis() - startTime >= timeout ) { 251 break; 252 } 253 } 254 } 255 return condition.get(); 256 } 257 258 /* 259 * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it 260 * doesn't throw InterruptedException. 261 */ 262 public static boolean waitForCondition(final AtomicBoolean condition, long timeout) { 263 try { 264 return waitForConditionEx(condition, timeout); 265 } catch (InterruptedException e) { 266 throw new RuntimeException("Error: unexpected exception caught!", e); 267 } 268 } 269 270 /* 271 * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout. 272 */ 273 public static void waitForConditionEx(final AtomicBoolean condition) 274 throws InterruptedException 275 { 276 synchronized (condition) { 277 while (!condition.get()) { 278 condition.wait(); 279 } 280 } 281 } 282 283 /* 284 * The same as {@code waitForConditionEx(AtomicBoolean)} except that it 285 * doesn't throw InterruptedException. 286 */ 287 public static void waitForCondition(final AtomicBoolean condition) { 288 try { 289 waitForConditionEx(condition); 290 } catch (InterruptedException e) { 291 throw new RuntimeException("Error: unexpected exception caught!", e); 292 } 293 } 294 295 public static void waitTillShownEx(final Component comp) throws InterruptedException { 296 while (true) { 297 try { 298 Thread.sleep(100); 299 comp.getLocationOnScreen(); 300 break; 301 } catch (IllegalComponentStateException e) {} 302 } 303 } 304 public static void waitTillShown(final Component comp) { 305 try { 306 waitTillShownEx(comp); 307 } catch (InterruptedException e) { 308 throw new RuntimeException("Error: unexpected exception caught!", e); 309 } 310 } 311 312 /** 313 * Drags from one point to another with the specified mouse button pressed. 314 * 315 * @param robot a robot to use for moving the mouse, etc. 316 * @param startPoint a start point of the drag 317 * @param endPoint an end point of the drag 318 * @param button one of {@code InputEvent.BUTTON1_MASK}, 319 * {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK} 320 * 321 * @throws IllegalArgumentException if {@code button} is not one of 322 * {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK}, 323 * {@code InputEvent.BUTTON3_MASK} 324 */ 325 public static void drag(Robot robot, Point startPoint, Point endPoint, int button) { 326 if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK 327 || button == InputEvent.BUTTON3_MASK)) 328 { 329 throw new IllegalArgumentException("invalid mouse button"); 330 } 331 332 robot.mouseMove(startPoint.x, startPoint.y); 333 robot.mousePress(button); 334 try { 335 mouseMove(robot, startPoint, endPoint); 336 } finally { 337 robot.mouseRelease(button); 338 } 339 } 340 341 /** 342 * Moves the mouse pointer from one point to another. 343 * Uses Bresenham's algorithm. 344 * 345 * @param robot a robot to use for moving the mouse 346 * @param startPoint a start point of the drag 347 * @param endPoint an end point of the drag 348 */ 349 public static void mouseMove(Robot robot, Point startPoint, Point endPoint) { 350 int dx = endPoint.x - startPoint.x; 351 int dy = endPoint.y - startPoint.y; 352 353 int ax = Math.abs(dx) * 2; 354 int ay = Math.abs(dy) * 2; 355 356 int sx = signWOZero(dx); 357 int sy = signWOZero(dy); 358 359 int x = startPoint.x; 360 int y = startPoint.y; 361 362 int d = 0; 363 364 if (ax > ay) { 365 d = ay - ax/2; 366 while (true){ 367 robot.mouseMove(x, y); 368 robot.delay(50); 369 370 if (x == endPoint.x){ 371 return; 372 } 373 if (d >= 0){ 374 y = y + sy; 375 d = d - ax; 376 } 377 x = x + sx; 378 d = d + ay; 379 } 380 } else { 381 d = ax - ay/2; 382 while (true){ 383 robot.mouseMove(x, y); 384 robot.delay(50); 385 386 if (y == endPoint.y){ 387 return; 388 } 389 if (d >= 0){ 390 x = x + sx; 391 d = d - ay; 392 } 393 y = y + sy; 394 d = d + ax; 395 } 396 } 397 } 398 399 private static int signWOZero(int i){ 400 return (i > 0)? 1: -1; 401 } 402 403 private static int sign(int n) { 404 return n < 0 ? -1 : n == 0 ? 0 : 1; 405 } 406 407 /** Returns {@code WindowListener} instance that diposes {@code Window} on 408 * "window closing" event. 409 * 410 * @return the {@code WindowListener} instance that could be set 411 * on a {@code Window}. After that 412 * the {@code Window} is disposed when "window closed" 413 * event is sent to the {@code Window} 414 */ 415 public static WindowListener getClosingWindowAdapter() { 416 return new WindowAdapter () { 417 public void windowClosing(WindowEvent e) { 418 e.getWindow().dispose(); 419 } 420 }; 421 } 422 423 /* 424 * The values directly map to the ones of 425 * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes. 426 */ 427 public final static int 428 UNDETERMINED_WM = 1, 429 NO_WM = 2, 430 OTHER_WM = 3, 431 OPENLOOK_WM = 4, 432 MOTIF_WM = 5, 433 CDE_WM = 6, 434 ENLIGHTEN_WM = 7, 435 KDE2_WM = 8, 436 SAWFISH_WM = 9, 437 ICE_WM = 10, 438 METACITY_WM = 11, 439 COMPIZ_WM = 12, 440 LG3D_WM = 13, 441 CWM_WM = 14, 442 MUTTER_WM = 15; 443 444 /* 445 * Returns -1 in case of not X Window or any problems. 446 */ 447 public static int getWMID() { 448 Class clazz = null; 449 try { 450 if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) { 451 clazz = Class.forName("sun.awt.X11.XWM"); 452 } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) { 453 clazz = Class.forName("sun.awt.motif.MToolkit"); 454 } 455 } catch (ClassNotFoundException cnfe) { 456 cnfe.printStackTrace(); 457 } 458 if (clazz == null) { 459 return -1; 460 } 461 462 try { 463 final Class _clazz = clazz; 464 Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() { 465 public Object run() { 466 try { 467 Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {}); 468 if (method != null) { 469 method.setAccessible(true); 470 } 471 return method; 472 } catch (NoSuchMethodException e) { 473 assert false; 474 } catch (SecurityException e) { 475 assert false; 476 } 477 return null; 478 } 479 }); 480 return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue(); 481 } catch (IllegalAccessException iae) { 482 iae.printStackTrace(); 483 } catch (InvocationTargetException ite) { 484 ite.printStackTrace(); 485 } 486 return -1; 487 } 488 489 //Cleans all the references 490 public static void cleanUp() { 491 apListener = null; 492 fgListener = null; 493 wgfListener = null; 494 } 495 496 497 //////////////////////////// 498 // Some stuff to test focus. 499 //////////////////////////// 500 501 private static WindowGainedFocusListener wgfListener = new WindowGainedFocusListener(); 502 private static FocusGainedListener fgListener = new FocusGainedListener(); 503 private static ActionPerformedListener apListener = new ActionPerformedListener(); 504 505 private abstract static class EventListener { 506 AtomicBoolean notifier = new AtomicBoolean(false); 507 Component comp; 508 boolean printEvent; 509 510 public void listen(Component comp, boolean printEvent) { 511 this.comp = comp; 512 this.printEvent = printEvent; 513 notifier.set(false); 514 setListener(comp); 515 } 516 517 public AtomicBoolean getNotifier() { 518 return notifier; 519 } 520 521 abstract void setListener(Component comp); 522 523 void printAndNotify(AWTEvent e) { 524 if (printEvent) { 525 System.err.println(e); 526 } 527 synchronized (notifier) { 528 notifier.set(true); 529 notifier.notifyAll(); 530 } 531 } 532 } 533 534 private static class WindowGainedFocusListener extends EventListener implements WindowFocusListener { 535 536 void setListener(Component comp) { 537 ((Window)comp).addWindowFocusListener(this); 538 } 539 540 public void windowGainedFocus(WindowEvent e) { 541 542 ((Window)comp).removeWindowFocusListener(this); 543 printAndNotify(e); 544 } 545 546 public void windowLostFocus(WindowEvent e) {} 547 } 548 549 private static class FocusGainedListener extends EventListener implements FocusListener { 550 551 void setListener(Component comp) { 552 comp.addFocusListener(this); 553 } 554 555 public void focusGained(FocusEvent e) { 556 comp.removeFocusListener(this); 557 printAndNotify(e); 558 } 559 560 public void focusLost(FocusEvent e) {} 561 } 562 563 private static class ActionPerformedListener extends EventListener implements ActionListener { 564 565 void setListener(Component comp) { 566 ((Button)comp).addActionListener(this); 567 } 568 569 public void actionPerformed(ActionEvent e) { 570 ((Button)comp).removeActionListener(this); 571 printAndNotify(e); 572 } 573 } 574 575 private static boolean trackEvent(int eventID, Component comp, Runnable action, int time, boolean printEvent) { 576 EventListener listener = null; 577 578 switch (eventID) { 579 case WindowEvent.WINDOW_GAINED_FOCUS: 580 listener = wgfListener; 581 break; 582 case FocusEvent.FOCUS_GAINED: 583 listener = fgListener; 584 break; 585 case ActionEvent.ACTION_PERFORMED: 586 listener = apListener; 587 break; 588 } 589 590 listener.listen(comp, printEvent); 591 action.run(); 592 return Util.waitForCondition(listener.getNotifier(), time); 593 } 594 595 /* 596 * Tracks WINDOW_GAINED_FOCUS event for a window caused by an action. 597 * @param window the window to track the event for 598 * @param action the action to perform 599 * @param time the max time to wait for the event 600 * @param printEvent should the event received be printed or doesn't 601 * @return true if the event has been received, otherwise false 602 */ 603 public static boolean trackWindowGainedFocus(Window window, Runnable action, int time, boolean printEvent) { 604 return trackEvent(WindowEvent.WINDOW_GAINED_FOCUS, window, action, time, printEvent); 605 } 606 607 /* 608 * Tracks FOCUS_GAINED event for a component caused by an action. 609 * @see #trackWindowGainedFocus 610 */ 611 public static boolean trackFocusGained(Component comp, Runnable action, int time, boolean printEvent) { 612 return trackEvent(FocusEvent.FOCUS_GAINED, comp, action, time, printEvent); 613 } 614 615 /* 616 * Tracks ACTION_PERFORMED event for a button caused by an action. 617 * @see #trackWindowGainedFocus 618 */ 619 public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) { 620 return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent); 621 } 622 623 /* 624 * Requests focus on the component provided and waits for the result. 625 * @return true if the component has been focused, false otherwise. 626 */ 627 public static boolean focusComponent(Component comp, int time) { 628 return focusComponent(comp, time, false); 629 } 630 public static boolean focusComponent(final Component comp, int time, boolean printEvent) { 631 return trackFocusGained(comp, 632 new Runnable() { 633 public void run() { 634 comp.requestFocus(); 635 } 636 }, 637 time, printEvent); 638 639 } 640 641 642 /** 643 * Invokes the <code>task</code> on the EDT thread. 644 * 645 * @return result of the <code>task</code> 646 */ 647 public static <T> T invokeOnEDT(final java.util.concurrent.Callable<T> task) throws Exception { 648 final java.util.List<T> result = new java.util.ArrayList<T>(1); 649 final Exception[] exception = new Exception[1]; 650 651 javax.swing.SwingUtilities.invokeAndWait(new Runnable() { 652 653 @Override 654 public void run() { 655 try { 656 result.add(task.call()); 657 } catch (Exception e) { 658 exception[0] = e; 659 } 660 } 661 }); 662 663 if (exception[0] != null) { 664 throw exception[0]; 665 } 666 667 return result.get(0); 668 } 669 670 }