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 "api/test/metrics/chrome_perf_dashboard_metrics_exporter.h" 14#include "api/test/metrics/global_metrics_logger_and_exporter.h" 15#include "api/test/metrics/metrics_exporter.h" 16#include "api/test/metrics/metrics_set_proto_file_exporter.h" 17#include "api/test/metrics/print_result_proxy_metrics_exporter.h" 18#include "api/test/metrics/stdout_metrics_exporter.h" 19#include "test/ios/coverage_util_ios.h" 20#include "test/ios/google_test_runner_delegate.h" 21#include "test/ios/test_support.h" 22#include "test/testsupport/perf_test.h" 23 24#import "sdk/objc/helpers/NSString+StdString.h" 25 26// Springboard will kill any iOS app that fails to check in after launch within 27// a given time. Starting a UIApplication before invoking TestSuite::Run 28// prevents this from happening. 29 30// InitIOSRunHook saves the TestSuite and argc/argv, then invoking 31// RunTestsFromIOSApp calls UIApplicationMain(), providing an application 32// delegate class: WebRtcUnitTestDelegate. The delegate implements 33// application:didFinishLaunchingWithOptions: to invoke the TestSuite's Run 34// method. 35 36// Since the executable isn't likely to be a real iOS UI, the delegate puts up a 37// window displaying the app name. If a bunch of apps using MainHook are being 38// run in a row, this provides an indication of which one is currently running. 39 40// If enabled, runs unittests using the XCTest test runner. 41const char kEnableRunIOSUnittestsWithXCTest[] = "enable-run-ios-unittests-with-xctest"; 42 43static int (*g_test_suite)(void) = NULL; 44static int g_argc; 45static char **g_argv; 46static bool g_write_perf_output; 47static bool g_export_perf_results_new_api; 48static std::string g_webrtc_test_metrics_output_path; 49static absl::optional<bool> g_is_xctest; 50static absl::optional<std::vector<std::string>> g_metrics_to_plot; 51 52@interface UIApplication (Testing) 53- (void)_terminateWithStatus:(int)status; 54@end 55 56@interface WebRtcUnitTestDelegate : NSObject <GoogleTestRunnerDelegate> { 57 UIWindow *_window; 58} 59- (void)runTests; 60@end 61 62@implementation WebRtcUnitTestDelegate 63 64- (BOOL)application:(UIApplication *)application 65 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 66 CGRect bounds = [[UIScreen mainScreen] bounds]; 67 68 _window = [[UIWindow alloc] initWithFrame:bounds]; 69 [_window setBackgroundColor:[UIColor whiteColor]]; 70 [_window makeKeyAndVisible]; 71 72 // Add a label with the app name. 73 UILabel *label = [[UILabel alloc] initWithFrame:bounds]; 74 label.text = [[NSProcessInfo processInfo] processName]; 75 label.textAlignment = NSTextAlignmentCenter; 76 [_window addSubview:label]; 77 78 // An NSInternalInconsistencyException is thrown if the app doesn't have a 79 // root view controller. Set an empty one here. 80 [_window setRootViewController:[[UIViewController alloc] init]]; 81 82 if (!rtc::test::ShouldRunIOSUnittestsWithXCTest()) { 83 // When running in XCTest mode, XCTest will invoke `runGoogleTest` directly. 84 // Otherwise, schedule a call to `runTests`. 85 [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; 86 } 87 88 return YES; 89} 90 91- (BOOL)supportsRunningGoogleTests { 92 return rtc::test::ShouldRunIOSUnittestsWithXCTest(); 93} 94 95- (int)runGoogleTests { 96 rtc::test::ConfigureCoverageReportPath(); 97 98 int exitStatus = g_test_suite(); 99 100 std::vector<std::unique_ptr<webrtc::test::MetricsExporter>> exporters; 101 if (g_export_perf_results_new_api) { 102 exporters.push_back(std::make_unique<webrtc::test::StdoutMetricsExporter>()); 103 if (g_write_perf_output) { 104 // Stores data into a proto file under the app's document directory. 105 NSString *fileName = @"perftest-output.pb"; 106 NSArray<NSString *> *outputDirectories = 107 NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 108 if ([outputDirectories count] != 0) { 109 NSString *outputPath = [outputDirectories[0] stringByAppendingPathComponent:fileName]; 110 111 exporters.push_back(std::make_unique<webrtc::test::ChromePerfDashboardMetricsExporter>( 112 [NSString stdStringForString:outputPath])); 113 } 114 } 115 if (!g_webrtc_test_metrics_output_path.empty()) { 116 exporters.push_back(std::make_unique<webrtc::test::MetricsSetProtoFileExporter>( 117 webrtc::test::MetricsSetProtoFileExporter::Options(g_webrtc_test_metrics_output_path))); 118 } 119 } else { 120 exporters.push_back(std::make_unique<webrtc::test::PrintResultProxyMetricsExporter>()); 121 } 122 webrtc::test::ExportPerfMetric(*webrtc::test::GetGlobalMetricsLogger(), std::move(exporters)); 123 if (!g_export_perf_results_new_api) { 124 if (g_write_perf_output) { 125 // Stores data into a proto file under the app's document directory. 126 NSString *fileName = @"perftest-output.pb"; 127 NSArray<NSString *> *outputDirectories = 128 NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 129 if ([outputDirectories count] != 0) { 130 NSString *outputPath = [outputDirectories[0] stringByAppendingPathComponent:fileName]; 131 132 if (!webrtc::test::WritePerfResults([NSString stdStringForString:outputPath])) { 133 return 1; 134 } 135 } 136 } 137 if (g_metrics_to_plot) { 138 webrtc::test::PrintPlottableResults(*g_metrics_to_plot); 139 } 140 } 141 142 return exitStatus; 143} 144 145- (void)runTests { 146 RTC_DCHECK(!rtc::test::ShouldRunIOSUnittestsWithXCTest()); 147 rtc::test::ConfigureCoverageReportPath(); 148 149 int exitStatus = [self runGoogleTests]; 150 151 // If a test app is too fast, it will exit before Instruments has has a 152 // a chance to initialize and no test results will be seen. 153 // TODO(crbug.com/137010): Figure out how much time is actually needed, and 154 // sleep only to make sure that much time has elapsed since launch. 155 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; 156 157 // Use the hidden selector to try and cleanly take down the app (otherwise 158 // things can think the app crashed even on a zero exit status). 159 UIApplication *application = [UIApplication sharedApplication]; 160 [application _terminateWithStatus:exitStatus]; 161 162 exit(exitStatus); 163} 164 165@end 166namespace rtc { 167namespace test { 168 169// Note: This is not thread safe, and must be called from the same thread as 170// runTests above. 171void InitTestSuite(int (*test_suite)(void), 172 int argc, 173 char *argv[], 174 bool write_perf_output, 175 bool export_perf_results_new_api, 176 std::string webrtc_test_metrics_output_path, 177 absl::optional<std::vector<std::string>> metrics_to_plot) { 178 g_test_suite = test_suite; 179 g_argc = argc; 180 g_argv = argv; 181 g_write_perf_output = write_perf_output; 182 g_export_perf_results_new_api = export_perf_results_new_api; 183 g_webrtc_test_metrics_output_path = webrtc_test_metrics_output_path; 184 g_metrics_to_plot = std::move(metrics_to_plot); 185} 186 187void RunTestsFromIOSApp() { 188 @autoreleasepool { 189 exit(UIApplicationMain(g_argc, g_argv, nil, @"WebRtcUnitTestDelegate")); 190 } 191} 192 193bool ShouldRunIOSUnittestsWithXCTest() { 194 if (g_is_xctest.has_value()) { 195 return g_is_xctest.value(); 196 } 197 198 char **argv = g_argv; 199 while (*argv != nullptr) { 200 if (strstr(*argv, kEnableRunIOSUnittestsWithXCTest) != nullptr) { 201 g_is_xctest = absl::optional<bool>(true); 202 return true; 203 } 204 argv++; 205 } 206 g_is_xctest = absl::optional<bool>(false); 207 return false; 208} 209 210} // namespace test 211} // namespace rtc 212