// 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 #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 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); } }