diff options
-rw-r--r-- | base/base.gyp | 5 | ||||
-rw-r--r-- | base/test/main_hook.cc | 9 | ||||
-rw-r--r-- | base/test/main_hook.h | 17 | ||||
-rw-r--r-- | base/test/main_hook_ios.mm | 108 | ||||
-rw-r--r-- | base/test/run_all_perftests.cc | 4 | ||||
-rw-r--r-- | base/test/run_all_unittests.cc | 4 | ||||
-rw-r--r-- | base/test/test_listener_ios.h | 17 | ||||
-rw-r--r-- | base/test/test_listener_ios.mm | 45 | ||||
-rw-r--r-- | base/test/test_suite.cc | 9 |
9 files changed, 214 insertions, 4 deletions
diff --git a/base/base.gyp b/base/base.gyp index 22798dd..2e02941 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -686,6 +686,9 @@ ], 'sources': [ 'perftimer.cc', + 'test/main_hook.cc', + 'test/main_hook.h', + 'test/main_hook_ios.mm', 'test/mock_chrome_application_mac.h', 'test/mock_chrome_application_mac.mm', 'test/mock_devices_changed_observer.cc', @@ -708,6 +711,8 @@ 'test/test_file_util_mac.cc', 'test/test_file_util_posix.cc', 'test/test_file_util_win.cc', + 'test/test_listener_ios.h', + 'test/test_listener_ios.mm', 'test/test_reg_util_win.cc', 'test/test_reg_util_win.h', 'test/test_suite.cc', diff --git a/base/test/main_hook.cc b/base/test/main_hook.cc new file mode 100644 index 0000000..f1f1961 --- /dev/null +++ b/base/test/main_hook.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/main_hook.h" + +#if !defined(OS_IOS) +MainHook::MainHook(MainType main_func, int argc, char* argv[]) {} +#endif diff --git a/base/test/main_hook.h b/base/test/main_hook.h new file mode 100644 index 0000000..46b71ce --- /dev/null +++ b/base/test/main_hook.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" +#include "base/basictypes.h" + +// Provides a way of running code before gtest-based tests with access to +// argv and argc. +class MainHook { + public: + typedef int (*MainType)(int, char*[]); + MainHook(MainType main_func, int argc, char* argv[]); + + private: + DISALLOW_COPY_AND_ASSIGN(MainHook); +}; diff --git a/base/test/main_hook_ios.mm b/base/test/main_hook_ios.mm new file mode 100644 index 0000000..6d7bc44 --- /dev/null +++ b/base/test/main_hook_ios.mm @@ -0,0 +1,108 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/main_hook.h" + +#import <UIKit/UIKit.h> + +#include "base/debug/debugger.h" +#include "base/logging.h" +#include "base/mac/scoped_nsautorelease_pool.h" +#include "base/memory/scoped_nsobject.h" + +// Springboard will kill any iOS app that fails to check in after launch within +// a given time. These two classes prevent this from happening. + +// MainHook saves the chrome main() and calls UIApplicationMain(), +// providing an application delegate class: ChromeUnitTestDelegate. The delegate +// listens for UIApplicationDidFinishLaunchingNotification. When the +// notification is received, it fires main() again to have the real work done. + +// Example usage: +// int main(int argc, char** argv) { +// MainHook hook(main, argc, argv); +// // Testing code goes here. There should be no code above MainHook. If +// // there is, it will be run twice. +// } + +// Since the executable isn't likely to be a real iOS UI, the delegate puts up a +// window displaying the app name. If a bunch of apps using MainHook are being +// run in a row, this provides an indication of which one is currently running. + +static MainHook::MainType g_main_func = NULL; +static int g_argc; +static char** g_argv; + +@interface UIApplication (Testing) +- (void) _terminateWithStatus:(int)status; +@end + +@interface ChromeUnitTestDelegate : NSObject { +@private + scoped_nsobject<UIWindow> window_; +} +- (void)runTests; +@end + +@implementation ChromeUnitTestDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + CGRect bounds = [[UIScreen mainScreen] bounds]; + + // Yes, this is leaked, it's just to make what's running visible. + window_.reset([[UIWindow alloc] initWithFrame:bounds]); + [window_ makeKeyAndVisible]; + + // Add a label with the app name. + UILabel* label = [[[UILabel alloc] initWithFrame:bounds] autorelease]; + label.text = [[NSProcessInfo processInfo] processName]; + label.textAlignment = UITextAlignmentCenter; + [window_ addSubview:label]; + + // Queue up the test run. + [self performSelector:@selector(runTests) + withObject:nil + afterDelay:0.1]; + return YES; +} + +- (void)runTests { + int exitStatus = g_main_func(g_argc, g_argv); + + // If a test app is too fast, it will exit before Instruments has has a + // a chance to initialize and no test results will be seen. + // TODO(ios): crbug.com/137010 Figure out how much time is actually needed, + // and sleep only to make sure that much time has elapsed since launch. + [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; + window_.reset(); + + // Use the hidden selector to try and cleanly take down the app (otherwise + // things can think the app crashed even on a zero exit status). + UIApplication* application = [UIApplication sharedApplication]; + [application _terminateWithStatus:exitStatus]; + + exit(exitStatus); +} + +@end + +#pragma mark - + +MainHook::MainHook(MainType main_func, int argc, char* argv[]) { + static bool ran_hook = false; + if (!ran_hook) { + ran_hook = true; + + g_main_func = main_func; + g_argc = argc; + g_argv = argv; + + base::mac::ScopedNSAutoreleasePool pool; + int exit_status = UIApplicationMain(argc, argv, nil, + @"ChromeUnitTestDelegate"); + exit(exit_status); + } +} diff --git a/base/test/run_all_perftests.cc b/base/test/run_all_perftests.cc index 2b4c628..6d9817c 100644 --- a/base/test/run_all_perftests.cc +++ b/base/test/run_all_perftests.cc @@ -1,9 +1,11 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/test/main_hook.h" #include "base/test/perf_test_suite.h" int main(int argc, char** argv) { + MainHook hook(main, argc, argv); return base::PerfTestSuite(argc, argv).Run(); } diff --git a/base/test/run_all_unittests.cc b/base/test/run_all_unittests.cc index 8bfeb3b..969b091 100644 --- a/base/test/run_all_unittests.cc +++ b/base/test/run_all_unittests.cc @@ -1,9 +1,11 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/test/main_hook.h" #include "base/test/test_suite.h" int main(int argc, char** argv) { + MainHook hook(main, argc, argv); return base::TestSuite(argc, argv).Run(); } diff --git a/base/test/test_listener_ios.h b/base/test/test_listener_ios.h new file mode 100644 index 0000000..c312250 --- /dev/null +++ b/base/test/test_listener_ios.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_TEST_TEST_LISTENER_IOS_H_ +#define BASE_TEST_TEST_LISTENER_IOS_H_ + +namespace base { +namespace test_listener_ios { + +// Register an IOSRunLoopListener. +void RegisterTestEndListener(); + +} // namespace test_listener_ios +} // namespace base + +#endif // BASE_TEST_TEST_LISTENER_IOS_H_ diff --git a/base/test/test_listener_ios.mm b/base/test/test_listener_ios.mm new file mode 100644 index 0000000..12cf5bb --- /dev/null +++ b/base/test/test_listener_ios.mm @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_listener_ios.h" + +#import <Foundation/Foundation.h> + +#include "base/mac/scoped_nsautorelease_pool.h" +#include "testing/gtest/include/gtest/gtest.h" + +// The iOS watchdog timer will kill an app that doesn't spin the main event +// loop often enough. This uses a Gtest TestEventListener to spin the current +// loop after each test finishes. However, if any individual test takes too +// long, it is still possible that the app will get killed. + +namespace { + +class IOSRunLoopListener : public testing::EmptyTestEventListener { + public: + virtual void OnTestEnd(const testing::TestInfo& test_info); +}; + +void IOSRunLoopListener::OnTestEnd(const testing::TestInfo& test_info) { + base::mac::ScopedNSAutoreleasePool scoped_pool; + + // At the end of the test, spin the default loop for a moment. + NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001]; + [[NSRunLoop currentRunLoop] runUntilDate:stop_date]; +} + +} // namespace + + +namespace base { +namespace test_listener_ios { + +void RegisterTestEndListener() { + testing::TestEventListeners& listeners = + testing::UnitTest::GetInstance()->listeners(); + listeners.Append(new IOSRunLoopListener); +} + +} // namespace test_listener_ios +} // namespace base diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc index 0cca8fa..05a6069b 100644 --- a/base/test/test_suite.cc +++ b/base/test/test_suite.cc @@ -25,9 +25,11 @@ #if defined(OS_MACOSX) #include "base/mac/scoped_nsautorelease_pool.h" -#if !defined(OS_IOS) +#if defined(OS_IOS) +#include "base/test/test_listener_ios.h" +#else #include "base/test/mock_chrome_application_mac.h" -#endif // !OS_IOS +#endif // OS_IOS #endif // OS_MACOSX #if defined(OS_ANDROID) @@ -234,6 +236,9 @@ int TestSuite::Run() { // Check to see if we are being run as a client process. if (!client_func.empty()) return multi_process_function_list::InvokeChildProcessTest(client_func); +#if defined(OS_IOS) + base::test_listener_ios::RegisterTestEndListener(); +#endif int result = RUN_ALL_TESTS(); // If there are failed tests, see if we should ignore the failures. |