diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn index a4ff2be0cf..83cb750dd2 100644 --- a/webrtc/base/BUILD.gn +++ b/webrtc/base/BUILD.gn @@ -43,8 +43,6 @@ config("rtc_base_all_dependent_config") { if (is_ios) { libs = [ "CFNetwork.framework", - - #"Foundation.framework", # Already in //build/config:default_libs. "Security.framework", "SystemConfiguration.framework", "UIKit.framework", @@ -731,6 +729,7 @@ if (rtc_include_tests) { "../test:field_trial", "../test:test_support", ] + public_deps = [ "//testing/gmock", "//testing/gtest", diff --git a/webrtc/base/unittest_main.cc b/webrtc/base/unittest_main.cc index 1600217916..90304d6aff 100644 --- a/webrtc/base/unittest_main.cc +++ b/webrtc/base/unittest_main.cc @@ -23,6 +23,10 @@ #include "webrtc/test/field_trial.h" #include "webrtc/test/testsupport/fileutils.h" +#if defined(WEBRTC_IOS) +#include "webrtc/test/ios/test_support.h" +#endif + DEFINE_bool(help, false, "prints this message"); DEFINE_string(log, "", "logging options to use"); DEFINE_string( @@ -106,7 +110,11 @@ int main(int argc, char** argv) { rtc::InitializeSSL(); rtc::SSLStreamAdapter::enable_time_callback_for_testing(); - int res = RUN_ALL_TESTS(); +#if defined(WEBRTC_IOS) + rtc::test::InitTestSuite(RUN_ALL_TESTS, argc, argv); + rtc::test::RunTestsFromIOSApp(); +#endif + const int res = RUN_ALL_TESTS(); rtc::CleanupSSL(); diff --git a/webrtc/test/BUILD.gn b/webrtc/test/BUILD.gn index 0d234cd6e3..7426a6839f 100644 --- a/webrtc/test/BUILD.gn +++ b/webrtc/test/BUILD.gn @@ -122,6 +122,13 @@ rtc_source_set("test_support") { "testsupport/unittest_utils.h", ] + if (is_ios) { + sources += [ + "ios/test_support.h", + "ios/test_support.mm", + ] + } + deps = [ "..:webrtc_common", "../base:gtest_prod", diff --git a/webrtc/test/ios/test_support.h b/webrtc/test/ios/test_support.h new file mode 100644 index 0000000000..a9620039c1 --- /dev/null +++ b/webrtc/test/ios/test_support.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef WEBRTC_TEST_IOS_TEST_SUPPORT_H_ +#define WEBRTC_TEST_IOS_TEST_SUPPORT_H_ + +namespace rtc { +namespace test { +// Launches an iOS app that serves as a host for a test suite. +// This is necessary as iOS doesn't like processes without a gui +// running for longer than a few seconds. +void RunTestsFromIOSApp(); +void InitTestSuite(int (*test_suite)(void), int argc, char* argv[]); + +} // namespace test +} // namespace rtc + +#endif // WEBRTC_TEST_IOS_TEST_SUPPORT_H_ diff --git a/webrtc/test/ios/test_support.mm b/webrtc/test/ios/test_support.mm new file mode 100644 index 0000000000..9a9035612a --- /dev/null +++ b/webrtc/test/ios/test_support.mm @@ -0,0 +1,101 @@ +/* + * 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 + +#include "webrtc/test/ios/test_support.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. + +static int (*g_test_suite)(void) = NULL; +static int g_argc; +static char **g_argv; + +@interface UIApplication (Testing) +- (void)_terminateWithStatus:(int)status; +@end + +@interface WebRtcUnitTestDelegate : NSObject { + 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]]; + + // Queue up the test run. + [self performSelector:@selector(runTests) withObject:nil afterDelay:0.1]; + return YES; +} + +- (void)runTests { + int exitStatus = g_test_suite(); + + // 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 rtc { +namespace test { + +void InitTestSuite(int (*test_suite)(void), int argc, char *argv[]) { + g_test_suite = test_suite; + g_argc = argc; + g_argv = argv; +} + +void RunTestsFromIOSApp() { + @autoreleasepool { + exit(UIApplicationMain(g_argc, g_argv, nil, @"WebRtcUnitTestDelegate")); + } +} +} // namespace test +} // namespace rtc diff --git a/webrtc/test/test_main.cc b/webrtc/test/test_main.cc index 1b1cdba789..ac8261e92a 100644 --- a/webrtc/test/test_main.cc +++ b/webrtc/test/test_main.cc @@ -17,6 +17,10 @@ #include "webrtc/test/testsupport/fileutils.h" #include "webrtc/test/testsupport/trace_to_stderr.h" +#if defined(WEBRTC_IOS) +#include "webrtc/test/ios/test_support.h" +#endif + DEFINE_bool(logs, false, "print logs to stderr"); DEFINE_string(force_fieldtrials, "", @@ -45,6 +49,10 @@ int main(int argc, char* argv[]) { std::unique_ptr trace_to_stderr; if (FLAGS_logs) trace_to_stderr.reset(new webrtc::test::TraceToStderr); +#if defined(WEBRTC_IOS) + rtc::test::InitTestSuite(RUN_ALL_TESTS, argc, argv); + rtc::test::RunTestsFromIOSApp(); +#endif return RUN_ALL_TESTS(); }