Home | History | Annotate | Download | only in test
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #import "ui/base/test/ui_cocoa_test_helper.h"
      6 
      7 #include "base/debug/debugger.h"
      8 #include "base/logging.h"
      9 #include "base/test/test_timeouts.h"
     10 
     11 @implementation CocoaTestHelperWindow
     12 
     13 - (id)initWithContentRect:(NSRect)contentRect {
     14   return [self initWithContentRect:contentRect
     15                          styleMask:NSBorderlessWindowMask
     16                            backing:NSBackingStoreBuffered
     17                              defer:NO];
     18 }
     19 
     20 - (id)init {
     21   return [self initWithContentRect:NSMakeRect(0, 0, 800, 600)];
     22 }
     23 
     24 - (void)dealloc {
     25   // Just a good place to put breakpoints when having problems with
     26   // unittests and CocoaTestHelperWindow.
     27   [super dealloc];
     28 }
     29 
     30 - (void)makePretendKeyWindowAndSetFirstResponder:(NSResponder*)responder {
     31   EXPECT_TRUE([self makeFirstResponder:responder]);
     32   [self setPretendIsKeyWindow:YES];
     33 }
     34 
     35 - (void)clearPretendKeyWindowAndFirstResponder {
     36   [self setPretendIsKeyWindow:NO];
     37   EXPECT_TRUE([self makeFirstResponder:NSApp]);
     38 }
     39 
     40 - (void)setPretendIsKeyWindow:(BOOL)flag {
     41   pretendIsKeyWindow_ = flag;
     42 }
     43 
     44 - (BOOL)isKeyWindow {
     45   return pretendIsKeyWindow_;
     46 }
     47 
     48 @end
     49 
     50 namespace ui {
     51 
     52 CocoaTest::CocoaTest() : called_tear_down_(false), test_window_(nil) {
     53   Init();
     54 }
     55 
     56 CocoaTest::~CocoaTest() {
     57   // Must call CocoaTest's teardown from your overrides.
     58   DCHECK(called_tear_down_);
     59 }
     60 
     61 void CocoaTest::Init() {
     62   // Set the duration of AppKit-evaluated animations (such as frame changes)
     63   // to zero for testing purposes. That way they take effect immediately.
     64   [[NSAnimationContext currentContext] setDuration:0.0];
     65 
     66   // The above does not affect window-resize time, such as for an
     67   // attached sheet dropping in.  Set that duration for the current
     68   // process (this is not persisted).  Empirically, the value of 0.0
     69   // is ignored.
     70   NSDictionary* dict =
     71       [NSDictionary dictionaryWithObject:@"0.01" forKey:@"NSWindowResizeTime"];
     72   [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
     73 
     74   // Collect the list of windows that were open when the test started so
     75   // that we don't wait for them to close in TearDown. Has to be done
     76   // after BootstrapCocoa is called.
     77   initial_windows_ = ApplicationWindows();
     78 }
     79 
     80 void CocoaTest::TearDown() {
     81   called_tear_down_ = true;
     82   // Call close on our test_window to clean it up if one was opened.
     83   [test_window_ clearPretendKeyWindowAndFirstResponder];
     84   [test_window_ close];
     85   test_window_ = nil;
     86 
     87   // Recycle the pool to clean up any stuff that was put on the
     88   // autorelease pool due to window or windowcontroller closures.
     89   pool_.Recycle();
     90 
     91   // Some controls (NSTextFields, NSComboboxes etc) use
     92   // performSelector:withDelay: to clean up drag handlers and other
     93   // things (Radar 5851458 "Closing a window with a NSTextView in it
     94   // should get rid of it immediately").  The event loop must be spun
     95   // to get everything cleaned up correctly.  It normally only takes
     96   // one to two spins through the event loop to see a change.
     97 
     98   // NOTE(shess): Under valgrind, -nextEventMatchingMask:* in one test
     99   // needed to run twice, once taking .2 seconds, the next time .6
    100   // seconds.  The loop exit condition attempts to be scalable.
    101 
    102   // Get the set of windows which weren't present when the test
    103   // started.
    104   std::set<NSWindow*> windows_left(WindowsLeft());
    105 
    106   while (!windows_left.empty()) {
    107     // Cover delayed actions by spinning the loop at least once after
    108     // this timeout.
    109     const NSTimeInterval kCloseTimeoutSeconds =
    110         TestTimeouts::action_timeout().InSecondsF();
    111 
    112     // Cover chains of delayed actions by spinning the loop at least
    113     // this many times.
    114     const int kCloseSpins = 3;
    115 
    116     // Track the set of remaining windows so that everything can be
    117     // reset if progress is made.
    118     std::set<NSWindow*> still_left = windows_left;
    119 
    120     NSDate* start_date = [NSDate date];
    121     bool one_more_time = true;
    122     int spins = 0;
    123     while (still_left.size() == windows_left.size() &&
    124            (spins < kCloseSpins || one_more_time)) {
    125       // Check the timeout before pumping events, so that we'll spin
    126       // the loop once after the timeout.
    127       one_more_time =
    128           ([start_date timeIntervalSinceNow] > -kCloseTimeoutSeconds);
    129 
    130       // Autorelease anything thrown up by the event loop.
    131       {
    132         base::mac::ScopedNSAutoreleasePool pool;
    133         ++spins;
    134         NSEvent *next_event = [NSApp nextEventMatchingMask:NSAnyEventMask
    135                                                  untilDate:nil
    136                                                     inMode:NSDefaultRunLoopMode
    137                                                    dequeue:YES];
    138         [NSApp sendEvent:next_event];
    139         [NSApp updateWindows];
    140       }
    141 
    142       // Refresh the outstanding windows.
    143       still_left = WindowsLeft();
    144     }
    145 
    146     // If no progress is being made, log a failure and continue.
    147     if (still_left.size() == windows_left.size()) {
    148       // NOTE(shess): Failing this expectation means that the test
    149       // opened windows which have not been fully released.  Either
    150       // there is a leak, or perhaps one of |kCloseTimeoutSeconds| or
    151       // |kCloseSpins| needs adjustment.
    152       EXPECT_EQ(0U, windows_left.size());
    153       for (std::set<NSWindow*>::iterator iter = windows_left.begin();
    154            iter != windows_left.end(); ++iter) {
    155         const char* desc = [[*iter description] UTF8String];
    156         LOG(WARNING) << "Didn't close window " << desc;
    157       }
    158       break;
    159     }
    160 
    161     windows_left = still_left;
    162   }
    163   PlatformTest::TearDown();
    164 }
    165 
    166 std::set<NSWindow*> CocoaTest::ApplicationWindows() {
    167   // This must NOT retain the windows it is returning.
    168   std::set<NSWindow*> windows;
    169 
    170   // Must create a pool here because [NSApp windows] has created an array
    171   // with retains on all the windows in it.
    172   base::mac::ScopedNSAutoreleasePool pool;
    173   NSArray *appWindows = [NSApp windows];
    174   for (NSWindow *window in appWindows) {
    175     windows.insert(window);
    176   }
    177   return windows;
    178 }
    179 
    180 std::set<NSWindow*> CocoaTest::WindowsLeft() {
    181   const std::set<NSWindow*> windows(ApplicationWindows());
    182   std::set<NSWindow*> windows_left;
    183   std::set_difference(windows.begin(), windows.end(),
    184                       initial_windows_.begin(), initial_windows_.end(),
    185                       std::inserter(windows_left, windows_left.begin()));
    186   return windows_left;
    187 }
    188 
    189 CocoaTestHelperWindow* CocoaTest::test_window() {
    190   if (!test_window_) {
    191     test_window_ = [[CocoaTestHelperWindow alloc] init];
    192     if (base::debug::BeingDebugged()) {
    193       [test_window_ orderFront:nil];
    194     } else {
    195       [test_window_ orderBack:nil];
    196     }
    197   }
    198   return test_window_;
    199 }
    200 
    201 }  // namespace ui
    202