Clover coverage report - clover
Coverage timestamp: Sat Oct 8 2005 22:54:17 EDT
file stats: LOC: 405   Methods: 28
NCLOC: 224   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
ComponentTestFixture.java 80.6% 95% 96.4% 92.1%
coverage coverage
 1   
 package junit.extensions.abbot;
 2   
 
 3   
 import java.awt.*;
 4   
 import java.awt.event.*;
 5   
 import java.lang.reflect.*;
 6   
 import java.util.Iterator;
 7   
 
 8   
 import javax.swing.*;
 9   
 import javax.swing.border.EmptyBorder;
 10   
 
 11   
 import junit.framework.TestCase;
 12   
 import abbot.*;
 13   
 import abbot.finder.*;
 14   
 import abbot.finder.ComponentFinder;
 15   
 import abbot.finder.matchers.*;
 16   
 import abbot.script.*;
 17   
 import abbot.script.Resolver;
 18   
 import abbot.tester.*;
 19   
 import abbot.tester.Robot;
 20   
 import abbot.util.*;
 21   
 
 22   
 /** Simple wrapper for testing components under JUnit.  Ensures proper setup
 23   
  * and cleanup for a GUI environment.  Provides methods for automatically
 24   
  * placing a GUI component within a frame and properly handling Window
 25   
  * showing/hiding (including modal dialogs).  Catches exceptions thrown on the
 26   
  * event dispatch thread and rethrows them as test failures.<p> 
 27   
  * Use {@link #showFrame(Component)}
 28   
  * when testing individual components, or 
 29   
  * {@link #showWindow(Window)}
 30   
  * when testing a {@link Frame}, {@link Dialog}, or {@link Window}.<p>
 31   
  * Any member fields you define which are classes derived from any of the
 32   
  * classes in {@link #DISPOSE_CLASSES} will be automatically set to null after
 33   
  * the test is run.<p>
 34   
  * <bold>WARNING:</bold> Any tests which use significant or scarce resources
 35   
  * and reference them in member fields should explicitly null those fields in
 36   
  * the tearDown method.  Otherwise the resources will not be subject to GC
 37   
  * until the {@link TestCase} itself and any containing
 38   
  * {@link junit.framework.TestSuite} is 
 39   
  * disposed (which, in the case of the standard JUnit test runners, is
 40   
  * <i>never</i>). 
 41   
  */
 42   
 public class ComponentTestFixture extends ResolverFixture {
 43   
 
 44   
     /** Typical delay to wait for a robot event to be translated into a Java
 45   
         event. */
 46   
     public static final int EVENT_GENERATION_DELAY = 5000;
 47   
     public static final int WINDOW_DELAY = 20000; // for slow systems
 48   
     public static final int POPUP_DELAY = 10000;
 49   
 
 50   
     /** Any member data derived from these classes will be automatically set
 51   
         to <code>null</code> after the test has run.  This enables GC of said
 52   
         classes without GC of the test itself (the default JUnit runners never
 53   
         release their references to the tests) or requiring explicit
 54   
         <code>null</code>-setting in the {@link TestCase#tearDown()} method.
 55   
     */
 56   
     protected static final Class[] DISPOSE_CLASSES = {
 57   
         Component.class,
 58   
         ComponentTester.class
 59   
     };
 60   
 
 61   
     private static Robot robot;
 62   
     private static WindowTracker tracker;
 63   
 
 64   
     private AWTFixtureHelper savedState;
 65   
     private Throwable edtException;
 66   
     private long edtExceptionTime;
 67   
 
 68   
     /** Return an Abbot {@link abbot.tester.Robot} for basic event generation.
 69   
      */ 
 70  1994
     protected Robot getRobot() { return robot; }
 71   
     /** Return a WindowTracker instance. */
 72  1977
     protected WindowTracker getWindowTracker() { return tracker; }
 73   
 
 74   
     /** This method should be invoked to display the component under test.
 75   
      * The frame's size will be its preferred size.  This method will return
 76   
      * with the enclosing {@link Frame} is showing and ready for input.
 77   
      */
 78  258
     protected Frame showFrame(Component comp) {
 79  258
         return showFrame(comp, null);
 80   
     }
 81   
 
 82   
     /** This method should be invoked to display the component under test,
 83   
      * when a specific size of frame is desired.  The method will return when
 84   
      * the enclosing {@link Frame} is showing and ready for input.  
 85   
      * @param comp
 86   
      * @param size Desired size of the enclosing frame, or <code>null</code>
 87   
      * to make no explicit adjustments to its size.
 88   
      */
 89  313
     protected Frame showFrame(Component comp, Dimension size) {
 90  313
         JFrame frame = new JFrame(getName());
 91  313
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 92  313
         JPanel pane = (JPanel)frame.getContentPane();
 93  313
         pane.setBorder(new EmptyBorder(10, 10, 10, 10));
 94  313
         pane.add(comp);
 95  313
         showWindow(frame, size, true);
 96  313
         return frame;
 97   
     }
 98   
 
 99   
     /** Safely display a window with proper EDT synchronization.   This method
 100   
      * blocks until the {@link Window} is showing and ready for input.
 101   
      */
 102  91
     protected void showWindow(Window w) {
 103  91
         showWindow(w, null, true);
 104   
     }
 105   
 
 106   
     /** Safely display a window with proper EDT synchronization.   This method
 107   
      * blocks until the {@link Window} is showing and ready for input.
 108   
      */
 109  14
     protected void showWindow(final Window w, final Dimension size) {
 110  14
         showWindow(w, size, true);
 111   
     }
 112   
 
 113   
     /** Safely display a window with proper EDT synchronization.   This method
 114   
      * blocks until the window is showing.  This method will return even when
 115   
      * the window is a modal dialog, since the show method is called on the
 116   
      * event dispatch thread.  The window will be packed if the pack flag is
 117   
      * set, and set to the given size if it is non-<code>null</code>.<p>
 118   
      * Modal dialogs may be shown with this method without blocking.
 119   
      */
 120  420
     protected void showWindow(final Window w, final Dimension size,
 121   
                               final boolean pack) {
 122  420
         EventQueue.invokeLater(new Runnable() {
 123  420
             public void run() {
 124  420
                 if (pack) {
 125  420
                     w.pack();
 126   
                     // Make sure the window is positioned away from
 127   
                     // any toolbars around the display borders
 128  420
                     w.setLocation(100, 100);
 129   
                 }
 130  420
                 if (size != null)
 131  71
                     w.setSize(size.width, size.height);
 132  420
                 w.show();
 133   
             }
 134   
         });
 135   
         // Ensure the window is visible before returning
 136  420
         waitForWindow(w, true);
 137   
     }
 138   
 
 139   
     /** Return when the window is ready for input or times out waiting.
 140   
      * @param w
 141   
      */
 142  448
     private void waitForWindow(Window w, boolean visible) {
 143  448
         Timer timer = new Timer();
 144  448
         while (tracker.isWindowReady(w) != visible) {
 145  5373
             if (timer.elapsed() > WINDOW_DELAY)
 146  0
                 throw new RuntimeException("Timed out waiting for Window to "
 147  0
                                            + (visible ? "open" : "close")
 148   
                                            + " (" + timer.elapsed() + "ms)");
 149  5373
             robot.sleep();
 150   
         }
 151   
     }
 152   
     
 153   
     /** Synchronous, safe hide of a window.  The window is ensured to be
 154   
      * hidden ({@link java.awt.event.ComponentEvent#COMPONENT_HIDDEN} or
 155   
      * equivalent has been posted) when this method returns.  Note that this
 156   
      * will <em>not</em> trigger a 
 157   
      * {@link java.awt.event.WindowEvent#WINDOW_CLOSING} event; use
 158   
      * {@link abbot.tester.WindowTester#actionClose(Component)}
 159   
      * if a window manager window close operation is required. 
 160   
      */
 161  17
     protected void hideWindow(final Window w) {
 162  17
         EventQueue.invokeLater(new Runnable() {
 163  17
             public void run() {
 164  17
                 w.hide();
 165   
             }
 166   
         });
 167  17
         waitForWindow(w, false);
 168   
         // Not strictly required, but if a test is depending on a window
 169   
         // event listener's actions on window hide/close, better to wait.
 170  17
         robot.waitForIdle();
 171   
     }
 172   
 
 173   
     /** Synchronous, safe dispose of a window.  The window is ensured to be
 174   
      * disposed ({@link java.awt.event.WindowEvent#WINDOW_CLOSED} has been
 175   
      * posted) when this method returns. 
 176   
      */
 177  4
     protected void disposeWindow(Window w) {
 178  4
         w.dispose();
 179  4
         waitForWindow(w, false);
 180  4
         robot.waitForIdle();
 181   
     }
 182   
 
 183   
     /** Install the given popup on the given component.  Takes care of
 184   
      * installing the appropriate mouse handler to activate the popup.
 185   
      */
 186  5
     protected void installPopup(Component invoker, final JPopupMenu popup) {
 187  5
         invoker.addMouseListener(new MouseAdapter() {
 188  10
             public void mousePressed(MouseEvent e) {
 189  10
                 mouseReleased(e);
 190   
             }
 191  20
             public void mouseReleased(MouseEvent e) {
 192  20
                 if (e.isPopupTrigger()) {
 193  10
                     popup.show(e.getComponent(), e.getX(), e.getY());
 194   
                 }
 195   
             }
 196   
         });
 197   
     }
 198   
 
 199   
     /** Safely install and display a popup in the center of the given
 200   
      * component, returning when it is visible.  Does not install any mouse
 201   
      * handlers not generate any mouse events. 
 202   
      */
 203  4
     protected void showPopup(final JPopupMenu popup, final Component invoker) {
 204  4
         showPopup(popup, invoker, invoker.getWidth()/2, invoker.getHeight()/2);
 205   
     }
 206   
 
 207   
     /** Safely install and display a popup, returning when it is visible.
 208   
         Does not install any mouse handlers not generate any mouse events.
 209   
      */
 210  7
     protected void showPopup(final JPopupMenu popup, final Component invoker,
 211   
                              final int x, final int y) {
 212  7
         EventQueue.invokeLater(new Runnable() {
 213  7
             public void run() {
 214  7
                 popup.show(invoker, x, y);
 215   
             }
 216   
         });
 217  7
         Timer timer = new Timer();
 218  7
         while (!popup.isShowing()) {
 219  7
             if (timer.elapsed() > POPUP_DELAY)
 220  0
                 throw new RuntimeException("Timed out waiting for popup to show");
 221  7
             robot.sleep();
 222   
         }
 223  7
         waitForWindow(SwingUtilities.getWindowAncestor(popup), true);
 224   
     }
 225   
 
 226   
     /** Display a modal dialog and wait for it to show.  Useful for things
 227   
      * like {@link JFileChooser#showOpenDialog(Component)} or
 228   
      * {@link JOptionPane#showInputDialog(Component,Object)}, or any
 229   
      * other instance where the dialog contents are not predefined and
 230   
      * displaying the dialog involves anything more than 
 231   
      * {@link Window#show()} (if {@link Window#show()} is all that is
 232   
      * required, use the {@link #showWindow(Window)} method instead).<p>
 233   
      * The given {@link Runnable} should contain the code which will show the
 234   
      * modal {@link Dialog} (and thus block); it will be run on the event
 235   
      * dispatch thread.<p>
 236   
      * This method will return when a {@link Dialog} becomes visible which
 237   
      * contains the given component (which may be any component which will
 238   
      * appear on the {@link Dialog}), or the standard timeout (10s) is
 239   
      * reached, at which point a {@link RuntimeException} will be thrown.<p>
 240   
      * For example,<br>
 241   
      <pre><code>
 242   
      Frame parent = ...;
 243   
      showModalDialog(new Runnable) {
 244   
          public void run() {
 245   
              JOptionPane.showInputDialog(parent, "Hit me");
 246   
          }
 247   
      });
 248   
      </code></pre> 
 249   
      @see #showWindow(java.awt.Window)
 250   
      @see #showWindow(java.awt.Window,java.awt.Dimension)
 251   
      @see #showWindow(java.awt.Window,java.awt.Dimension,boolean)
 252   
      */
 253  9
     protected Dialog showModalDialog(Runnable showAction) throws Exception {
 254  9
         EventQueue.invokeLater(showAction);
 255   
         // Wait for a modal dialog to appear
 256  9
         Matcher matcher = new ClassMatcher(Dialog.class, true) {
 257  512
             public boolean matches(Component c) {
 258  512
                 return super.matches(c) 
 259   
                     && ((Dialog)c).isModal();
 260   
             }
 261   
         };
 262  9
         Timer timer = new Timer();
 263  9
         while (true) {
 264  16
             try {
 265  16
                 return (Dialog)getFinder().find(matcher);
 266   
             }
 267   
             catch(ComponentSearchException e) {
 268  7
                 if (timer.elapsed() > 10000)
 269  0
                     throw new RuntimeException("Timed out waiting for dialog to be ready");
 270  7
                 robot.sleep();
 271   
             }
 272   
         }
 273   
     }
 274   
 
 275   
     /** Similar to {@link #showModalDialog(Runnable)},
 276   
      * but provides for the case where some of the {@link Dialog}'s contents
 277   
      * are known beforehand.<p>
 278   
      * @deprecated Use {@link #showModalDialog(Runnable)} instead.
 279   
      */
 280  0
     protected Dialog showModalDialog(Runnable showAction, Component contents)
 281   
         throws Exception {
 282  0
         return showModalDialog(showAction);
 283   
     }
 284   
 
 285   
     /** Returns whether a Component is showing.  The ID may be the component
 286   
      * name or, in the case of a Frame or Dialog, the title.  Regular
 287   
      * expressions may be used, but must be delimited by slashes, e.g. /expr/.
 288   
      * Returns if one or more matches is found.
 289   
      */
 290  12
     protected boolean isShowing(String id) {
 291  12
         try {
 292  12
             getFinder().find(new WindowMatcher(id, true));
 293   
         }
 294   
         catch(ComponentNotFoundException e) {
 295  5
             return false;
 296   
         }
 297   
         catch(MultipleComponentsFoundException m) {
 298   
             // Might not be the one you want, but that's what the docs say
 299   
         }
 300  7
         return true;
 301   
     }
 302   
 
 303   
     /** Construct a test case with the given name.  */
 304  382
     public ComponentTestFixture(String name) {
 305  382
         super(name);
 306   
     }
 307   
 
 308   
     /** Default Constructor.  The name will be automatically set from the
 309   
         selected test method.
 310   
     */ 
 311  106
     public ComponentTestFixture() { }
 312   
 
 313   
     /** Ensure proper test harness setup and teardown that won't
 314   
      * be inadvertently overridden by a derived class. 
 315   
      */
 316  488
     protected void fixtureSetUp() throws Throwable {
 317  488
         super.fixtureSetUp();
 318   
 
 319  488
         savedState = new AWTFixtureHelper();
 320   
 
 321  488
         robot = new Robot();
 322  488
         tracker = WindowTracker.getTracker();
 323   
 
 324  488
         robot.reset();
 325  488
         if (Bugs.hasMultiClickFrameBug())
 326  488
             robot.delay(500);
 327   
     }
 328   
     
 329   
     /** Handles restoration of system state.  Automatically disposes of any
 330   
         Components used in the test.
 331   
     */
 332  488
     protected void fixtureTearDown() throws Throwable {
 333  488
         super.fixtureTearDown();
 334  488
         tracker = null;
 335  488
         if (robot != null) {
 336  484
             int buttons = Robot.getState().getButtons();
 337  484
             if (buttons != 0) {
 338  9
                 robot.mouseRelease(buttons);
 339   
             }
 340   
             // TODO: release any extant pressed keys
 341  484
             robot = null;
 342   
         }
 343  488
         edtExceptionTime = savedState.getEventDispatchErrorTime();
 344  488
         edtException = savedState.getEventDispatchError();
 345  488
         savedState.restore();
 346  488
         savedState = null;
 347  488
         clearTestFields();
 348   
     }
 349   
 
 350   
     /** Clears all non-static {@link TestCase} fields which are instances of
 351   
      * any class found in {@link #DISPOSE_CLASSES}.
 352   
      */
 353  488
     private void clearTestFields() {
 354  488
         try {
 355  488
             Field[] fields = getClass().getDeclaredFields();
 356  488
             for (int i=0;i < fields.length;i++) {
 357  3084
                 if ((fields[i].getModifiers() & Modifier.STATIC) == 0) {
 358  1223
                     fields[i].setAccessible(true);
 359  1223
                     for (int c=0;c < DISPOSE_CLASSES.length;c++) {
 360  2446
                         Class cls = DISPOSE_CLASSES[c];
 361  2446
                         if (cls.isAssignableFrom(fields[i].getType())) {
 362  479
                             fields[i].set(this, null);
 363   
                         }
 364   
                     }
 365   
                 }
 366   
             }
 367   
         }
 368   
         catch(Exception e) {
 369  0
             Log.warn(e);
 370   
         }
 371   
     }
 372   
 
 373   
     /** If any exceptions are thrown on the event dispatch thread, they count
 374   
         as errors.  They will not, however supersede any failures/errors
 375   
         thrown by the test itself.
 376   
     */
 377  488
     public void runBare() throws Throwable {
 378  488
         Throwable exception = null;
 379  488
         long exceptionTime = -1;
 380  488
         try {
 381  488
             super.runBare();
 382   
         }
 383   
         catch(Throwable e) {
 384  6
             exceptionTime = System.currentTimeMillis();
 385  6
             exception = e;
 386   
         }
 387   
         finally {
 388   
             // Cf. StepRunner.runStep()
 389   
             // Any EDT exception which occurred *prior* to when the
 390   
             // exception on the main thread was thrown should be used
 391   
             // instead.
 392  488
             if (edtException != null
 393   
                 && (exception == null
 394   
                     || edtExceptionTime < exceptionTime)) {
 395   
                 // TODO wrap this somehow with an additional message that
 396   
                 // says "this was thrown on the EDT"
 397  2
                 exception = edtException;
 398   
             }
 399   
         }
 400  488
         if (exception != null) {
 401  7
             throw exception;
 402   
         }
 403   
     }
 404   
 }
 405