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