From c743b0591ddaa6b2d550be938153c273853e167e Mon Sep 17 00:00:00 2001 From: "dmaclach@chromium.org" Date: Fri, 23 Oct 2009 19:41:04 +0000 Subject: Basic test class that is going to replace CocoaTestHelper and CocoaNoWindowTestHelper. Is responsible for bootstrapping cocoa and verifying that all windows are closed down correctly between tests. Also includes a macro for doing some very repetitive view testing that we had multiple copies of. I included a single changed over test so that you can see the difference. BUG=none TEST=none Review URL: http://codereview.chromium.org/327010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29928 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/browser/cocoa/cocoa_test_helper.h | 100 ++++++++++++++++++-- chrome/browser/cocoa/cocoa_test_helper.mm | 128 ++++++++++++++++++++++++++ chrome/browser/cocoa/sad_tab_view_unittest.mm | 31 ++----- 3 files changed, 229 insertions(+), 30 deletions(-) (limited to 'chrome/browser') diff --git a/chrome/browser/cocoa/cocoa_test_helper.h b/chrome/browser/cocoa/cocoa_test_helper.h index 1627391..f980790 100644 --- a/chrome/browser/cocoa/cocoa_test_helper.h +++ b/chrome/browser/cocoa/cocoa_test_helper.h @@ -6,18 +6,21 @@ #define CHROME_BROWSER_COCOA_COCOA_TEST_HELPER_H_ #import +#include #include "base/debug_util.h" #include "base/file_path.h" #include "base/mac_util.h" #include "base/path_service.h" +#import "base/scoped_nsautorelease_pool.h" #import "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" #include "chrome/common/chrome_constants.h" +#include "testing/platform_test.h" // Background windows normally will not display things such as focus // rings. This class allows -isKeyWindow to be manipulated to test // such things. - @interface CocoaTestHelperWindow : NSWindow { @private BOOL pretendIsKeyWindow_; @@ -29,6 +32,15 @@ // Init with a default frame. - (id)init; +// Sets the responder passed in as first responder, and sets the window +// so that it will return "YES" if asked if it key window. It does not actually +// make the window key. +- (void)makePretendKeyWindowAndSetFirstResponder:(NSResponder*)responder; + +// Clears the first responder duty for the window and returns the window +// to being non-key. +- (void)clearPretendKeyWindowAndFirstResponder; + // Set value to return for -isKeyWindow. - (void)setPretendIsKeyWindow:(BOOL)isKeyWindow; @@ -36,6 +48,77 @@ @end +// A test class that all tests that depend on AppKit should inherit from. +// Sets up NSApplication and paths correctly, and makes sure that any windows +// created in the test are closed down properly by the test. If you need to +// inherit from a different test class, but need to set up the AppKit runtime +// environment, you can call BootstrapCocoa directly from your test class. You +// will have to deal with windows on your own though. +class CocoaTest : public PlatformTest { + public: + // Sets up AppKit and paths correctly for unit tests. If you can't inherit + // from CocoaTest but are going to be using any AppKit features directly, + // or indirectly, you should be calling this from the c'tor or SetUp methods + // of your test class. + static void BootstrapCocoa(); + + CocoaTest(); + virtual ~CocoaTest(); + + // Must be called by subclasses that override TearDown. We verify that it + // is called in our destructor. Takes care of making sure that all windows + // are closed off correctly. If your tests open windows, they must be sure + // to close them before CocoaTest::TearDown is called. A standard way of doing + // this would be to create them in SetUp (after calling CocoaTest::Setup) and + // then close them in TearDown before calling CocoaTest::TearDown. + virtual void TearDown(); + + // Retuns a test window that can be used by views and other UI objects + // as part of their tests. Is created lazily, and will be closed correctly + // in CocoaTest::TearDown. Note that it is a CocoaTestHelperWindow which + // has special handling for being Key. + CocoaTestHelperWindow* test_window(); + + private: + // Return a vector of currently open windows. Note that it is a vector + // instead of an NSArray because we don't want any retains placed on the + // windows in it and that the windows in this list may no longer be valid + // NSWindows any time after this returns. You can only use the pointer values + // in the vector for comparison purposes. + static std::vector ApplicationWindows(); + + bool called_tear_down_; + base::ScopedNSAutoreleasePool pool_; + std::vector initial_windows_; + // Strong. Lazily created. This isn't wrapped in a scoped_nsobject because + // we want to call [close] to destroy it rather than calling [release]. We + // want to verify that [close] is actually removing our window and that it's + // not hanging around because releaseWhenClosed was set to "no" on the window. + // It isn't wrapped in a different wrapper class to close it because we + // need to close it at a very specific time; just before we enter our clean + // up loop in TearDown. + CocoaTestHelperWindow* test_window_; +}; + +// A macro defining a standard set of tests to run on a view. Since we can't +// inherit tests, this macro saves us a lot of duplicate code. Handles simply +// displaying the view to make sure it won't crash, as well as removing it +// from a window. All tests that work with NSView subclasses and/or +// NSViewController subclasses should use it. +#define TEST_VIEW(test_fixture, view_member_name) \ + TEST_F(test_fixture, AddRemove##test_fixture) { \ + scoped_nsobject view([view_member_name retain]); \ + EXPECT_EQ([test_window() contentView], [view_member_name superview]); \ + [view_member_name removeFromSuperview]; \ + EXPECT_FALSE([view_member_name superview]); \ + } \ + TEST_F(test_fixture, Display##test_fixture) { \ + [view_member_name display]; \ + } + +// The classes below are deprecated and will be removed shortly. Do not write +// any tests based on them. + // A class that initializes Cocoa and sets up resources for many of our // Cocoa controller unit tests. It does several key things: // - Creates and displays an empty Cocoa window for views to live in @@ -48,10 +131,10 @@ // testing::Test. // Provides the Cocoa goodness without the extraneous window. -// TODO(shess): It might make more sense to have CocoaTest as a -// PlatformTest subclass which adds the Cocoa magic, then -// CocoaViewTest as a further subclass which provides a convenience -// window. + +// DEPRECATED +// TODO(dmaclach): remove as soon as I can get my other CLs in that get rid +// of any dependencies on this. 10/30/09 at the latest. class CocoaNoWindowTestHelper { public: CocoaNoWindowTestHelper() { @@ -70,6 +153,9 @@ class CocoaNoWindowTestHelper { } }; +// DEPRECATED +// TODO(dmaclach): remove as soon as I can get my other CLs in that get rid +// of any dependencies on this. 10/30/09 at the latest. class CocoaTestHelper : public CocoaNoWindowTestHelper { public: CocoaTestHelper() { @@ -88,14 +174,14 @@ class CocoaTestHelper : public CocoaNoWindowTestHelper { // Set |window_| to pretend to be key and make |aView| its // firstResponder. void makeFirstResponder(NSView* aView) { - [window_ setPretendIsKeyWindow:YES]; [window_ makeFirstResponder:aView]; + [window_ setPretendIsKeyWindow:YES]; } // Clear |window_| firstResponder and stop pretending to be key. void clearFirstResponder() { - [window_ makeFirstResponder:nil]; [window_ setPretendIsKeyWindow:NO]; + [window_ makeFirstResponder:nil]; } private: diff --git a/chrome/browser/cocoa/cocoa_test_helper.mm b/chrome/browser/cocoa/cocoa_test_helper.mm index efe5c5c..462b10a 100644 --- a/chrome/browser/cocoa/cocoa_test_helper.mm +++ b/chrome/browser/cocoa/cocoa_test_helper.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "chrome/browser/cocoa/cocoa_test_helper.h" +#import "base/logging.h" @implementation CocoaTestHelperWindow @@ -17,6 +18,22 @@ return [self initWithContentRect:NSMakeRect(0, 0, 800, 600)]; } +- (void)dealloc { + // Just a good place to put breakpoints when having problems with + // unittests and CocoaTestHelperWindow. + [super dealloc]; +} + +- (void)makePretendKeyWindowAndSetFirstResponder:(NSResponder*)responder { + EXPECT_TRUE([self makeFirstResponder:responder]); + [self setPretendIsKeyWindow:YES]; +} + +- (void)clearPretendKeyWindowAndFirstResponder { + [self setPretendIsKeyWindow:NO]; + EXPECT_TRUE([self makeFirstResponder:NSApp]); +} + - (void)setPretendIsKeyWindow:(BOOL)flag { pretendIsKeyWindow_ = flag; } @@ -26,3 +43,114 @@ } @end + +CocoaTest::CocoaTest() : called_tear_down_(false), test_window_(nil) { + BootstrapCocoa(); + // Set the duration of AppKit-evaluated animations (such as frame changes) + // to zero for testing purposes. That way they take effect immediately. + [[NSAnimationContext currentContext] setDuration:0.0]; + // Collect the list of windows that were open when the test started so + // that we don't wait for them to close in TearDown. Has to be done + // after BootstrapCocoa is called. + initial_windows_ = ApplicationWindows(); +} + +CocoaTest::~CocoaTest() { + // Must call CocoaTest's teardown from your overrides. + DCHECK(called_tear_down_); +} + +void CocoaTest::BootstrapCocoa() { + // Look in the framework bundle for resources. + FilePath path; + PathService::Get(base::DIR_EXE, &path); + path = path.Append(chrome::kFrameworkName); + mac_util::SetOverrideAppBundlePath(path); + + // Bootstrap Cocoa. It's very unhappy without this. + [NSApplication sharedApplication]; +} + +void CocoaTest::TearDown() { + called_tear_down_ = true; + // Call close on our test_window to clean it up if one was opened. + [test_window_ close]; + test_window_ = nil; + + // Recycle the pool to clean up any stuff that was put on the + // autorelease pool due to window or windowcontroller closures. + // Note that many controls (NSTextFields, NSComboboxes etc) may call + // performSelector:withDelay: to clean up drag handlers and other things. + // We must spin the event loop a bit to make sure that everything gets cleaned + // up correctly. We will wait up to one second for windows to clean themselves + // up (normally only takes one to two loops through the event loop). + // Radar 5851458 "Closing a window with a NSTextView in it should get rid of + // it immediately" + pool_.Recycle(); + NSDate *start_date = [NSDate date]; + const std::vector windows_waiting(ApplicationWindows()); + + bool loop = windows_waiting.size() > 0; + while (loop) { + { + // Need an autorelease pool to wrap our event loop. + base::ScopedNSAutoreleasePool pool; + NSEvent *next_event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue:YES]; + [NSApp sendEvent:next_event]; + [NSApp updateWindows]; + } + // Check the windows after we have released the event loop pool so that + // all retains are cleaned up. + const std::vector current_windows(ApplicationWindows()); + std::vector windows_left; + std::set_difference(current_windows.begin(), + current_windows.end(), + initial_windows_.begin(), + initial_windows_.end(), + inserter(windows_left, windows_left.begin())); + + if (windows_left.size() == 0) { + // All our windows are closed. + break; + } + if ([start_date timeIntervalSinceNow] < -1.0) { + // Took us over a second to shut down, and windows still exist. + // Log a failure and continue. + EXPECT_EQ(windows_left.size(), 0U); + for (size_t i = 0; i < windows_left.size(); ++i) { + const char* desc = [[windows_left[i] description] UTF8String]; + LOG(WARNING) << "Didn't close window " << desc; + } + break; + } + } + PlatformTest::TearDown(); +} + +std::vector CocoaTest::ApplicationWindows() { + // This must NOT retain the windows it is returning. + std::vector windows; + // Must create a pool here because [NSApp windows] has created an array + // with retains on all the windows in it. + base::ScopedNSAutoreleasePool pool; + NSArray *appWindows = [NSApp windows]; + for (NSWindow *window in appWindows) { + windows.push_back(window); + } + return windows; +} + +CocoaTestHelperWindow* CocoaTest::test_window() { + if (!test_window_) { + test_window_ = [[CocoaTestHelperWindow alloc] init]; + if (DebugUtil::BeingDebugged()) { + [test_window_ orderFront:nil]; + } else { + [test_window_ orderBack:nil]; + } + } + return test_window_; +} diff --git a/chrome/browser/cocoa/sad_tab_view_unittest.mm b/chrome/browser/cocoa/sad_tab_view_unittest.mm index 49c24e3..382fdb6 100644 --- a/chrome/browser/cocoa/sad_tab_view_unittest.mm +++ b/chrome/browser/cocoa/sad_tab_view_unittest.mm @@ -2,39 +2,24 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import - -#include "base/scoped_nsobject.h" #import "chrome/browser/cocoa/sad_tab_view.h" #import "chrome/browser/cocoa/cocoa_test_helper.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/platform_test.h" namespace { -class SadTabViewTest : public PlatformTest { +class SadTabViewTest : public CocoaTest { public: SadTabViewTest() { - NSRect content_frame = [cocoa_helper_.contentView() frame]; - view_.reset([[SadTabView alloc] initWithFrame:content_frame]); - [cocoa_helper_.contentView() addSubview:view_.get()]; + NSRect content_frame = [[test_window() contentView] frame]; + scoped_nsobject view([[SadTabView alloc] + initWithFrame:content_frame]); + view_ = view.get(); + [[test_window() contentView] addSubview:view_]; } - CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc... - scoped_nsobject view_; + SadTabView* view_; // Weak. Owned by the view hierarchy. }; -// Test adding/removing from the view hierarchy, mostly to ensure nothing -// leaks or crashes. -TEST_F(SadTabViewTest, AddRemove) { - EXPECT_EQ(cocoa_helper_.contentView(), [view_ superview]); - [view_.get() removeFromSuperview]; - EXPECT_FALSE([view_ superview]); -} - -// Test drawing, mostly to ensure nothing leaks or crashes. -TEST_F(SadTabViewTest, Display) { - [view_ display]; -} +TEST_VIEW(SadTabViewTest, view_) } // namespace -- cgit v1.1