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 { 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 */ createRobot()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 */ showWindowWait(Window win)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 */ pointOnComp(final Component comp, final Robot robot)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 */ clickOnComp(final Component comp, final Robot robot, int delay)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 */ clickOnComp(final Component comp, final Robot robot)131 public static void clickOnComp(final Component comp, final Robot robot) { 132 clickOnComp(comp, robot, 50); 133 } 134 getTitlePoint(Window decoratedWindow)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 */ clickOnTitle(final Window decoratedWindow, final Robot robot)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 */ testPixelColor(int x, int y, final Color color, int attempts, int delay, final Robot robot)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 */ testBoundsColor(final Rectangle bounds, final Color color, int attempts, int delay, final Robot robot)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 waitForIdle(Robot robot)208 public static void waitForIdle(Robot robot) { 209 if (robot == null) { 210 robot = createRobot(); 211 } 212 robot.waitForIdle(); 213 } 214 215 216 /* 217 * Waits for a notification and for a boolean condition to become true. 218 * The method returns when the above conditions are fullfilled or when the timeout 219 * occurs. 220 * 221 * @param condition the object to be notified and the booelan condition to wait for 222 * @param timeout the maximum time to wait in milliseconds 223 * @param catchExceptions if {@code true} the method catches InterruptedException 224 * @return the final boolean value of the {@code condition} 225 * @throws InterruptedException if the awaiting proccess has been interrupted 226 */ waitForConditionEx(final AtomicBoolean condition, long timeout)227 public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout) 228 throws InterruptedException 229 { 230 synchronized (condition) { 231 long startTime = System.currentTimeMillis(); 232 while (!condition.get()) { 233 condition.wait(timeout); 234 if (System.currentTimeMillis() - startTime >= timeout ) { 235 break; 236 } 237 } 238 } 239 return condition.get(); 240 } 241 242 /* 243 * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it 244 * doesn't throw InterruptedException. 245 */ waitForCondition(final AtomicBoolean condition, long timeout)246 public static boolean waitForCondition(final AtomicBoolean condition, long timeout) { 247 try { 248 return waitForConditionEx(condition, timeout); 249 } catch (InterruptedException e) { 250 throw new RuntimeException("Error: unexpected exception caught!", e); 251 } 252 } 253 254 /* 255 * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout. 256 */ waitForConditionEx(final AtomicBoolean condition)257 public static void waitForConditionEx(final AtomicBoolean condition) 258 throws InterruptedException 259 { 260 synchronized (condition) { 261 while (!condition.get()) { 262 condition.wait(); 263 } 264 } 265 } 266 267 /* 268 * The same as {@code waitForConditionEx(AtomicBoolean)} except that it 269 * doesn't throw InterruptedException. 270 */ waitForCondition(final AtomicBoolean condition)271 public static void waitForCondition(final AtomicBoolean condition) { 272 try { 273 waitForConditionEx(condition); 274 } catch (InterruptedException e) { 275 throw new RuntimeException("Error: unexpected exception caught!", e); 276 } 277 } 278 waitTillShownEx(final Component comp)279 public static void waitTillShownEx(final Component comp) throws InterruptedException { 280 while (true) { 281 try { 282 Thread.sleep(100); 283 comp.getLocationOnScreen(); 284 break; 285 } catch (IllegalComponentStateException e) {} 286 } 287 } waitTillShown(final Component comp)288 public static void waitTillShown(final Component comp) { 289 try { 290 waitTillShownEx(comp); 291 } catch (InterruptedException e) { 292 throw new RuntimeException("Error: unexpected exception caught!", e); 293 } 294 } 295 296 /** 297 * Drags from one point to another with the specified mouse button pressed. 298 * 299 * @param robot a robot to use for moving the mouse, etc. 300 * @param startPoint a start point of the drag 301 * @param endPoint an end point of the drag 302 * @param button one of {@code InputEvent.BUTTON1_MASK}, 303 * {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK} 304 * 305 * @throws IllegalArgumentException if {@code button} is not one of 306 * {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK}, 307 * {@code InputEvent.BUTTON3_MASK} 308 */ drag(Robot robot, Point startPoint, Point endPoint, int button)309 public static void drag(Robot robot, Point startPoint, Point endPoint, int button) { 310 if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK 311 || button == InputEvent.BUTTON3_MASK)) 312 { 313 throw new IllegalArgumentException("invalid mouse button"); 314 } 315 316 robot.mouseMove(startPoint.x, startPoint.y); 317 robot.mousePress(button); 318 try { 319 mouseMove(robot, startPoint, endPoint); 320 } finally { 321 robot.mouseRelease(button); 322 } 323 } 324 325 /** 326 * Moves the mouse pointer from one point to another. 327 * Uses Bresenham's algorithm. 328 * 329 * @param robot a robot to use for moving the mouse 330 * @param startPoint a start point of the drag 331 * @param endPoint an end point of the drag 332 */ mouseMove(Robot robot, Point startPoint, Point endPoint)333 public static void mouseMove(Robot robot, Point startPoint, Point endPoint) { 334 int dx = endPoint.x - startPoint.x; 335 int dy = endPoint.y - startPoint.y; 336 337 int ax = Math.abs(dx) * 2; 338 int ay = Math.abs(dy) * 2; 339 340 int sx = signWOZero(dx); 341 int sy = signWOZero(dy); 342 343 int x = startPoint.x; 344 int y = startPoint.y; 345 346 int d = 0; 347 348 if (ax > ay) { 349 d = ay - ax/2; 350 while (true){ 351 robot.mouseMove(x, y); 352 robot.delay(50); 353 354 if (x == endPoint.x){ 355 return; 356 } 357 if (d >= 0){ 358 y = y + sy; 359 d = d - ax; 360 } 361 x = x + sx; 362 d = d + ay; 363 } 364 } else { 365 d = ax - ay/2; 366 while (true){ 367 robot.mouseMove(x, y); 368 robot.delay(50); 369 370 if (y == endPoint.y){ 371 return; 372 } 373 if (d >= 0){ 374 x = x + sx; 375 d = d - ay; 376 } 377 y = y + sy; 378 d = d + ax; 379 } 380 } 381 } 382 signWOZero(int i)383 private static int signWOZero(int i){ 384 return (i > 0)? 1: -1; 385 } 386 sign(int n)387 private static int sign(int n) { 388 return n < 0 ? -1 : n == 0 ? 0 : 1; 389 } 390 391 /** Returns {@code WindowListener} instance that diposes {@code Window} on 392 * "window closing" event. 393 * 394 * @return the {@code WindowListener} instance that could be set 395 * on a {@code Window}. After that 396 * the {@code Window} is disposed when "window closed" 397 * event is sent to the {@code Window} 398 */ getClosingWindowAdapter()399 public static WindowListener getClosingWindowAdapter() { 400 return new WindowAdapter () { 401 public void windowClosing(WindowEvent e) { 402 e.getWindow().dispose(); 403 } 404 }; 405 } 406 407 /* 408 * The values directly map to the ones of 409 * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes. 410 */ 411 public final static int 412 UNDETERMINED_WM = 1, 413 NO_WM = 2, 414 OTHER_WM = 3, 415 OPENLOOK_WM = 4, 416 MOTIF_WM = 5, 417 CDE_WM = 6, 418 ENLIGHTEN_WM = 7, 419 KDE2_WM = 8, 420 SAWFISH_WM = 9, 421 ICE_WM = 10, 422 METACITY_WM = 11, 423 COMPIZ_WM = 12, 424 LG3D_WM = 13, 425 CWM_WM = 14, 426 MUTTER_WM = 15; 427 428 /* 429 * Returns -1 in case of not X Window or any problems. 430 */ 431 public static int getWMID() { 432 Class clazz = null; 433 try { 434 if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) { 435 clazz = Class.forName("sun.awt.X11.XWM"); 436 } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) { 437 clazz = Class.forName("sun.awt.motif.MToolkit"); 438 } 439 } catch (ClassNotFoundException cnfe) { 440 cnfe.printStackTrace(); 441 } 442 if (clazz == null) { 443 return -1; 444 } 445 446 try { 447 final Class _clazz = clazz; 448 Method m_addExports = Class.forName("java.awt.Helper").getDeclaredMethod("addExports", String.class, java.lang.Module.class); 449 // No MToolkit anymore: nothing to do about it. 450 // We may be called from non-X11 system, and this permission cannot be delegated to a test. 451 m_addExports.invoke(null, "sun.awt.X11", Util.class.getModule()); 452 Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() { 453 public Object run() { 454 try { 455 Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {}); 456 if (method != null) { 457 method.setAccessible(true); 458 } 459 return method; 460 } catch (NoSuchMethodException e) { 461 assert false; 462 } catch (SecurityException e) { 463 assert false; 464 } 465 return null; 466 } 467 }); 468 return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue(); 469 } catch (ClassNotFoundException cnfe) { 470 cnfe.printStackTrace(); 471 } catch (NoSuchMethodException nsme) { 472 nsme.printStackTrace(); 473 } catch (IllegalAccessException iae) { 474 iae.printStackTrace(); 475 } catch (InvocationTargetException ite) { 476 ite.printStackTrace(); 477 } 478 return -1; 479 } 480 481 //Cleans all the references 482 public static void cleanUp() { 483 apListener = null; 484 fgListener = null; 485 wgfListener = null; 486 } 487 488 489 //////////////////////////// 490 // Some stuff to test focus. 491 //////////////////////////// 492 493 private static WindowGainedFocusListener wgfListener = new WindowGainedFocusListener(); 494 private static FocusGainedListener fgListener = new FocusGainedListener(); 495 private static ActionPerformedListener apListener = new ActionPerformedListener(); 496 497 private abstract static class EventListener { 498 AtomicBoolean notifier = new AtomicBoolean(false); 499 Component comp; 500 boolean printEvent; 501 502 public void listen(Component comp, boolean printEvent) { 503 this.comp = comp; 504 this.printEvent = printEvent; 505 notifier.set(false); 506 setListener(comp); 507 } 508 509 public AtomicBoolean getNotifier() { 510 return notifier; 511 } 512 513 abstract void setListener(Component comp); 514 515 void printAndNotify(AWTEvent e) { 516 if (printEvent) { 517 System.err.println(e); 518 } 519 synchronized (notifier) { 520 notifier.set(true); 521 notifier.notifyAll(); 522 } 523 } 524 } 525 526 private static class WindowGainedFocusListener extends EventListener implements WindowFocusListener { 527 528 void setListener(Component comp) { 529 ((Window)comp).addWindowFocusListener(this); 530 } 531 532 public void windowGainedFocus(WindowEvent e) { 533 534 ((Window)comp).removeWindowFocusListener(this); 535 printAndNotify(e); 536 } 537 538 public void windowLostFocus(WindowEvent e) {} 539 } 540 541 private static class FocusGainedListener extends EventListener implements FocusListener { 542 543 void setListener(Component comp) { 544 comp.addFocusListener(this); 545 } 546 547 public void focusGained(FocusEvent e) { 548 comp.removeFocusListener(this); 549 printAndNotify(e); 550 } 551 552 public void focusLost(FocusEvent e) {} 553 } 554 555 private static class ActionPerformedListener extends EventListener implements ActionListener { 556 557 void setListener(Component comp) { 558 ((Button)comp).addActionListener(this); 559 } 560 561 public void actionPerformed(ActionEvent e) { 562 ((Button)comp).removeActionListener(this); 563 printAndNotify(e); 564 } 565 } 566 567 private static boolean trackEvent(int eventID, Component comp, Runnable action, int time, boolean printEvent) { 568 EventListener listener = null; 569 570 switch (eventID) { 571 case WindowEvent.WINDOW_GAINED_FOCUS: 572 listener = wgfListener; 573 break; 574 case FocusEvent.FOCUS_GAINED: 575 listener = fgListener; 576 break; 577 case ActionEvent.ACTION_PERFORMED: 578 listener = apListener; 579 break; 580 } 581 582 listener.listen(comp, printEvent); 583 action.run(); 584 return Util.waitForCondition(listener.getNotifier(), time); 585 } 586 587 /* 588 * Tracks WINDOW_GAINED_FOCUS event for a window caused by an action. 589 * @param window the window to track the event for 590 * @param action the action to perform 591 * @param time the max time to wait for the event 592 * @param printEvent should the event received be printed or doesn't 593 * @return true if the event has been received, otherwise false 594 */ 595 public static boolean trackWindowGainedFocus(Window window, Runnable action, int time, boolean printEvent) { 596 return trackEvent(WindowEvent.WINDOW_GAINED_FOCUS, window, action, time, printEvent); 597 } 598 599 /* 600 * Tracks FOCUS_GAINED event for a component caused by an action. 601 * @see #trackWindowGainedFocus 602 */ 603 public static boolean trackFocusGained(Component comp, Runnable action, int time, boolean printEvent) { 604 return trackEvent(FocusEvent.FOCUS_GAINED, comp, action, time, printEvent); 605 } 606 607 /* 608 * Tracks ACTION_PERFORMED event for a button caused by an action. 609 * @see #trackWindowGainedFocus 610 */ 611 public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) { 612 return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent); 613 } 614 615 /* 616 * Requests focus on the component provided and waits for the result. 617 * @return true if the component has been focused, false otherwise. 618 */ 619 public static boolean focusComponent(Component comp, int time) { 620 return focusComponent(comp, time, false); 621 } 622 public static boolean focusComponent(final Component comp, int time, boolean printEvent) { 623 return trackFocusGained(comp, 624 new Runnable() { 625 public void run() { 626 comp.requestFocus(); 627 } 628 }, 629 time, printEvent); 630 631 } 632 633 634 /** 635 * Invokes the <code>task</code> on the EDT thread. 636 * 637 * @return result of the <code>task</code> 638 */ 639 public static <T> T invokeOnEDT(final java.util.concurrent.Callable<T> task) throws Exception { 640 final java.util.List<T> result = new java.util.ArrayList<T>(1); 641 final Exception[] exception = new Exception[1]; 642 643 javax.swing.SwingUtilities.invokeAndWait(new Runnable() { 644 645 @Override 646 public void run() { 647 try { 648 result.add(task.call()); 649 } catch (Exception e) { 650 exception[0] = e; 651 } 652 } 653 }); 654 655 if (exception[0] != null) { 656 throw exception[0]; 657 } 658 659 return result.get(0); 660 } 661 662 } 663