|  | /* | 
|  | *  Copyright 2017 The WebRTC Project Authors. All rights reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #import <UIKit/UIKit.h> | 
|  |  | 
|  | #include "api/test/metrics/chrome_perf_dashboard_metrics_exporter.h" | 
|  | #include "api/test/metrics/global_metrics_logger_and_exporter.h" | 
|  | #include "api/test/metrics/metrics_exporter.h" | 
|  | #include "api/test/metrics/metrics_set_proto_file_exporter.h" | 
|  | #include "api/test/metrics/print_result_proxy_metrics_exporter.h" | 
|  | #include "api/test/metrics/stdout_metrics_exporter.h" | 
|  | #include "test/ios/coverage_util_ios.h" | 
|  | #include "test/ios/google_test_runner_delegate.h" | 
|  | #include "test/ios/test_support.h" | 
|  | #include "test/testsupport/perf_test.h" | 
|  |  | 
|  | #import "sdk/objc/helpers/NSString+StdString.h" | 
|  |  | 
|  | // Springboard will kill any iOS app that fails to check in after launch within | 
|  | // a given time. Starting a UIApplication before invoking TestSuite::Run | 
|  | // prevents this from happening. | 
|  |  | 
|  | // InitIOSRunHook saves the TestSuite and argc/argv, then invoking | 
|  | // RunTestsFromIOSApp calls UIApplicationMain(), providing an application | 
|  | // delegate class: WebRtcUnitTestDelegate. The delegate implements | 
|  | // application:didFinishLaunchingWithOptions: to invoke the TestSuite's Run | 
|  | // method. | 
|  |  | 
|  | // 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. | 
|  |  | 
|  | // If enabled, runs unittests using the XCTest test runner. | 
|  | const char kEnableRunIOSUnittestsWithXCTest[] = | 
|  | "enable-run-ios-unittests-with-xctest"; | 
|  |  | 
|  | static int (*g_test_suite)(void) = NULL; | 
|  | static int g_argc; | 
|  | static char **g_argv; | 
|  | static bool g_write_perf_output; | 
|  | static std::string g_webrtc_test_metrics_output_path; | 
|  | static std::optional<bool> g_is_xctest; | 
|  | static std::optional<std::vector<std::string>> g_metrics_to_plot; | 
|  |  | 
|  | @interface UIApplication (Testing) | 
|  | - (void)_terminateWithStatus:(int)status; | 
|  | @end | 
|  |  | 
|  | @interface WebRtcUnitTestDelegate : NSObject <GoogleTestRunnerDelegate> { | 
|  | UIWindow *_window; | 
|  | } | 
|  | - (void)runTests; | 
|  | @end | 
|  |  | 
|  | @implementation WebRtcUnitTestDelegate | 
|  |  | 
|  | - (BOOL)application:(UIApplication *)application | 
|  | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { | 
|  | CGRect bounds = [[UIScreen mainScreen] bounds]; | 
|  |  | 
|  | _window = [[UIWindow alloc] initWithFrame:bounds]; | 
|  | [_window setBackgroundColor:[UIColor whiteColor]]; | 
|  | [_window makeKeyAndVisible]; | 
|  |  | 
|  | // Add a label with the app name. | 
|  | UILabel *label = [[UILabel alloc] initWithFrame:bounds]; | 
|  | label.text = [[NSProcessInfo processInfo] processName]; | 
|  | label.textAlignment = NSTextAlignmentCenter; | 
|  | [_window addSubview:label]; | 
|  |  | 
|  | // An NSInternalInconsistencyException is thrown if the app doesn't have a | 
|  | // root view controller. Set an empty one here. | 
|  | [_window setRootViewController:[[UIViewController alloc] init]]; | 
|  |  | 
|  | if (!webrtc::test::ShouldRunIOSUnittestsWithXCTest()) { | 
|  | // When running in XCTest mode, XCTest will invoke `runGoogleTest` directly. | 
|  | // Otherwise, schedule a call to `runTests`. | 
|  | [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; | 
|  | } | 
|  |  | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | - (BOOL)supportsRunningGoogleTests { | 
|  | return webrtc::test::ShouldRunIOSUnittestsWithXCTest(); | 
|  | } | 
|  |  | 
|  | - (int)runGoogleTests { | 
|  | webrtc::test::ConfigureCoverageReportPath(); | 
|  |  | 
|  | int exitStatus = g_test_suite(); | 
|  |  | 
|  | NSArray<NSString *> *outputDirectories = NSSearchPathForDirectoriesInDomains( | 
|  | NSDocumentDirectory, NSUserDomainMask, YES); | 
|  | std::vector<std::unique_ptr<webrtc::test::MetricsExporter>> exporters; | 
|  | exporters.push_back(std::make_unique<webrtc::test::StdoutMetricsExporter>()); | 
|  | if (g_write_perf_output) { | 
|  | // Stores data into a proto file under the app's document directory. | 
|  | NSString *fileName = @"perftest-output.pb"; | 
|  | if ([outputDirectories count] != 0) { | 
|  | NSString *outputPath = | 
|  | [outputDirectories[0] stringByAppendingPathComponent:fileName]; | 
|  |  | 
|  | exporters.push_back( | 
|  | std::make_unique<webrtc::test::ChromePerfDashboardMetricsExporter>( | 
|  | [NSString stdStringForString:outputPath])); | 
|  | } | 
|  | } | 
|  | if (!g_webrtc_test_metrics_output_path.empty()) { | 
|  | RTC_CHECK_EQ(g_webrtc_test_metrics_output_path.find('/'), std::string::npos) | 
|  | << "On iOS, --webrtc_test_metrics_output_path must only be a file " | 
|  | "name."; | 
|  | if ([outputDirectories count] != 0) { | 
|  | NSString *fileName = | 
|  | [NSString stringWithCString:g_webrtc_test_metrics_output_path.c_str() | 
|  | encoding:[NSString defaultCStringEncoding]]; | 
|  | NSString *outputPath = | 
|  | [outputDirectories[0] stringByAppendingPathComponent:fileName]; | 
|  | exporters.push_back( | 
|  | std::make_unique<webrtc::test::MetricsSetProtoFileExporter>( | 
|  | webrtc::test::MetricsSetProtoFileExporter::Options( | 
|  | [NSString stdStringForString:outputPath]))); | 
|  | } | 
|  | } | 
|  | webrtc::test::ExportPerfMetric(*webrtc::test::GetGlobalMetricsLogger(), | 
|  | std::move(exporters)); | 
|  | return exitStatus; | 
|  | } | 
|  |  | 
|  | - (void)runTests { | 
|  | RTC_DCHECK(!webrtc::test::ShouldRunIOSUnittestsWithXCTest()); | 
|  | webrtc::test::ConfigureCoverageReportPath(); | 
|  |  | 
|  | int exitStatus = [self runGoogleTests]; | 
|  |  | 
|  | // 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(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]]; | 
|  |  | 
|  | // 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 | 
|  | namespace webrtc { | 
|  | namespace test { | 
|  |  | 
|  | // Note: This is not thread safe, and must be called from the same thread as | 
|  | // runTests above. | 
|  | void InitTestSuite(int (*test_suite)(void), | 
|  | int argc, | 
|  | char *argv[], | 
|  | bool write_perf_output, | 
|  | std::string webrtc_test_metrics_output_path, | 
|  | std::optional<std::vector<std::string>> metrics_to_plot) { | 
|  | g_test_suite = test_suite; | 
|  | g_argc = argc; | 
|  | g_argv = argv; | 
|  | g_write_perf_output = write_perf_output; | 
|  | g_webrtc_test_metrics_output_path = webrtc_test_metrics_output_path; | 
|  | g_metrics_to_plot = std::move(metrics_to_plot); | 
|  | } | 
|  |  | 
|  | void RunTestsFromIOSApp() { | 
|  | @autoreleasepool { | 
|  | exit(UIApplicationMain(g_argc, g_argv, nil, @"WebRtcUnitTestDelegate")); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool ShouldRunIOSUnittestsWithXCTest() { | 
|  | if (g_is_xctest.has_value()) { | 
|  | return g_is_xctest.value(); | 
|  | } | 
|  |  | 
|  | char **argv = g_argv; | 
|  | while (*argv != nullptr) { | 
|  | if (strstr(*argv, kEnableRunIOSUnittestsWithXCTest) != nullptr) { | 
|  | g_is_xctest = std::optional<bool>(true); | 
|  | return true; | 
|  | } | 
|  | argv++; | 
|  | } | 
|  | g_is_xctest = std::optional<bool>(false); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | }  // namespace test | 
|  | }  // namespace webrtc |