xref: /aosp_15_r20/external/webrtc/test/ios/test_support.mm (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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