1/* 2 * Copyright 2017 The WebRTC Project Authors. All rights reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11#import <UIKit/UIKit.h> 12 13#include "test/ios/coverage_util_ios.h" 14#include "test/ios/test_support.h" 15#include "test/testsupport/perf_test.h" 16 17#import "sdk/objc/helpers/NSString+StdString.h" 18 19// Springboard will kill any iOS app that fails to check in after launch within 20// a given time. Starting a UIApplication before invoking TestSuite::Run 21// prevents this from happening. 22 23// InitIOSRunHook saves the TestSuite and argc/argv, then invoking 24// RunTestsFromIOSApp calls UIApplicationMain(), providing an application 25// delegate class: WebRtcUnitTestDelegate. The delegate implements 26// application:didFinishLaunchingWithOptions: to invoke the TestSuite's Run 27// method. 28 29// Since the executable isn't likely to be a real iOS UI, the delegate puts up a 30// window displaying the app name. If a bunch of apps using MainHook are being 31// run in a row, this provides an indication of which one is currently running. 32 33static int (*g_test_suite)(void) = NULL; 34static int g_argc; 35static char **g_argv; 36static bool g_write_perf_output; 37static absl::optional<std::vector<std::string>> g_metrics_to_plot; 38 39@interface UIApplication (Testing) 40- (void)_terminateWithStatus:(int)status; 41@end 42 43@interface WebRtcUnitTestDelegate : NSObject { 44 UIWindow *_window; 45} 46- (void)runTests; 47@end 48 49@implementation WebRtcUnitTestDelegate 50 51- (BOOL)application:(UIApplication *)application 52 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 53 CGRect bounds = [[UIScreen mainScreen] bounds]; 54 55 _window = [[UIWindow alloc] initWithFrame:bounds]; 56 [_window setBackgroundColor:[UIColor whiteColor]]; 57 [_window makeKeyAndVisible]; 58 59 // Add a label with the app name. 60 UILabel *label = [[UILabel alloc] initWithFrame:bounds]; 61 label.text = [[NSProcessInfo processInfo] processName]; 62 label.textAlignment = NSTextAlignmentCenter; 63 [_window addSubview:label]; 64 65 // An NSInternalInconsistencyException is thrown if the app doesn't have a 66 // root view controller. Set an empty one here. 67 [_window setRootViewController:[[UIViewController alloc] init]]; 68 69 // Queue up the test run. 70 [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; 71 return YES; 72} 73 74- (void)runTests { 75 rtc::test::ConfigureCoverageReportPath(); 76 77 int exitStatus = g_test_suite(); 78 79 if (g_write_perf_output) { 80 // Stores data into a proto file under the app's document directory. 81 NSString *fileName = @"perftest-output.pb"; 82 NSArray<NSString*>* outputDirectories = NSSearchPathForDirectoriesInDomains( 83 NSDocumentDirectory, NSUserDomainMask, YES); 84 if ([outputDirectories count] != 0) { 85 NSString* outputPath = 86 [outputDirectories[0] stringByAppendingPathComponent:fileName]; 87 88 if (!webrtc::test::WritePerfResults([NSString stdStringForString:outputPath])) { 89 exit(1); 90 } 91 } 92 } 93 if (g_metrics_to_plot) { 94 webrtc::test::PrintPlottableResults(*g_metrics_to_plot); 95 } 96 97 // If a test app is too fast, it will exit before Instruments has has a 98 // a chance to initialize and no test results will be seen. 99 // TODO(crbug.com/137010): Figure out how much time is actually needed, and 100 // sleep only to make sure that much time has elapsed since launch. 101 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; 102 103 // Use the hidden selector to try and cleanly take down the app (otherwise 104 // things can think the app crashed even on a zero exit status). 105 UIApplication *application = [UIApplication sharedApplication]; 106 [application _terminateWithStatus:exitStatus]; 107 108 exit(exitStatus); 109} 110 111@end 112namespace rtc { 113namespace test { 114 115// Note: This is not thread safe, and must be called from the same thread as 116// runTests above. 117void InitTestSuite(int (*test_suite)(void), 118 int argc, 119 char *argv[], 120 bool write_perf_output, 121 absl::optional<std::vector<std::string>> metrics_to_plot) { 122 g_test_suite = test_suite; 123 g_argc = argc; 124 g_argv = argv; 125 g_write_perf_output = write_perf_output; 126 g_metrics_to_plot = std::move(metrics_to_plot); 127} 128 129void RunTestsFromIOSApp() { 130 @autoreleasepool { 131 exit(UIApplicationMain(g_argc, g_argv, nil, @"WebRtcUnitTestDelegate")); 132 } 133} 134} // namespace test 135} // namespace rtc 136