Commit b6c231b5 authored by Rohit Rao's avatar Rohit Rao Committed by Commit Bot

[ios] Adds the ability to run unittests using XCTest.

This new functionality is hidden behind both a GN arg
(enable_run_ios_unittests_with_xctest) and a commandline switch
(--enable-run-unittests-with-xctest), in order to default it to off
until the bots are updated to properly run XCTest-based unittests.

The iOS test runner is updated to run in one of two modes.  When
--enable-run-unittests-with-xctest is false (the default), behavior is
unchanged; TestSuite::Run() calls UIApplicationMain(), then the
UIApplicationDelegate calls TestSuite::Run() again, which actually runs
the tests when invoked the second time.  When the switch is set to true,
the second invocation of TestSuite::Run() is made by our XCTestCase
subclass rather than by the application delegate.

Xcode provides the ability to run XCTests and XCUITests from the
commandline, but does not provide any other way to install and run an
app outside of this test-based workflow.  (We had an alternative that
used third party libraries, but they no longer work on iOS 13.)  This
makes it difficult to install and run GoogleTest-based tests on iOS
devices, since they run as a single self-contained application, but it
would not be practical to drop GoogleTest support on iOS.  Instead, we
are exploring invoking these tests via XCTest, which would allow us to
use Xcode's tooling but still run the same GoogleTest-based tests as on
other platforms.

BUG=635509

Change-Id: I26c67d9c7e16a744f43a20f2d8c5839ca8b3c31a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1787593Reviewed-by: default avatarDirk Pranke <dpranke@chromium.org>
Reviewed-by: default avatarMark Mentovai <mark@chromium.org>
Reviewed-by: default avatarJustin Cohen <justincohen@chromium.org>
Commit-Queue: Rohit Rao <rohitrao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#694749}
parent 53e009b1
......@@ -228,6 +228,7 @@ static_library("test_support") {
set_sources_assignment_filter([])
sources += [ "test_file_util_mac.cc" ]
set_sources_assignment_filter(sources_assignment_filter)
deps += [ ":google_test_runner_shared_headers" ]
}
if (is_mac) {
......@@ -513,6 +514,29 @@ if (is_android) {
}
}
if (is_ios) {
source_set("google_test_runner_shared_headers") {
sources = [
"ios/google_test_runner_delegate.h",
]
}
source_set("google_test_runner") {
sources = [
"ios/google_test_runner.mm",
]
deps = [
":google_test_runner_shared_headers",
"//base",
]
libs = [ "UIKit.framework" ]
configs += [
"//build/config/compiler:enable_arc",
"//build/config/ios:xctest_config",
]
}
}
# Trivial executable which outputs space-delimited argv to stdout,
# used for testing.
executable("test_child_process") {
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#include "base/logging.h"
#import "base/test/ios/google_test_runner_delegate.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface GoogleTestRunner : XCTestCase
@end
@implementation GoogleTestRunner
- (void)testRunGoogleTests {
id appDelegate = UIApplication.sharedApplication.delegate;
DCHECK([appDelegate conformsToProtocol:@protocol(GoogleTestRunnerDelegate)]);
id<GoogleTestRunnerDelegate> runnerDelegate =
static_cast<id<GoogleTestRunnerDelegate>>(appDelegate);
DCHECK(runnerDelegate.supportsRunningGoogleTests);
XCTAssertTrue([runnerDelegate runGoogleTests] == 0);
}
@end
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TEST_IOS_GOOGLE_TEST_RUNNER_DELEGATE_H_
#define BASE_TEST_IOS_GOOGLE_TEST_RUNNER_DELEGATE_H_
@protocol GoogleTestRunnerDelegate
// Returns YES if this delegate supports running GoogleTests via a call to
// |runGoogleTests|.
@property(nonatomic, readonly, assign) BOOL supportsRunningGoogleTests;
// Runs GoogleTests and returns the final exit code.
- (int)runGoogleTests;
@end
#endif // BASE_TEST_IOS_GOOGLE_TEST_RUNNER_DELEGATE_H_
......@@ -19,6 +19,9 @@ void InitIOSRunHook(TestSuite* suite, int argc, char* argv[]);
// InitIOSRunHook.
void RunTestsFromIOSApp();
// Returns true if unittests should be run by the XCTest runnner.
bool ShouldRunIOSUnittestsWithXCTest();
} // namespace base
#endif // BASE_TEST_TEST_SUPPORT_IOS_H_
......@@ -2,14 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "base/test/test_support_ios.h"
#import <UIKit/UIKit.h>
#include "base/command_line.h"
#include "base/debug/debugger.h"
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "base/message_loop/message_pump.h"
#include "base/message_loop/message_pump_default.h"
#import "base/test/ios/google_test_runner_delegate.h"
#include "base/test/test_suite.h"
#include "base/test/test_switches.h"
#include "testing/coverage_util_ios.h"
// Springboard will kill any iOS app that fails to check in after launch within
......@@ -45,7 +50,7 @@ static char** g_argv;
@end
#endif // TARGET_IPHONE_SIMULATOR
@interface ChromeUnitTestDelegate : NSObject {
@interface ChromeUnitTestDelegate : NSObject <GoogleTestRunnerDelegate> {
base::scoped_nsobject<UIWindow> _window;
}
- (void)runTests;
......@@ -86,9 +91,12 @@ static char** g_argv;
[self redirectOutput];
// Queue up the test run.
[self performSelector:@selector(runTests)
withObject:nil
afterDelay:0.1];
if (!base::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;
}
......@@ -150,7 +158,11 @@ static char** g_argv;
}
}
- (void)runTests {
- (BOOL)supportsRunningGoogleTests {
return base::ShouldRunIOSUnittestsWithXCTest();
}
- (int)runGoogleTests {
coverage_util::ConfigureCoverageReportPath();
int exitStatus = g_test_suite->Run();
......@@ -158,6 +170,14 @@ static char** g_argv;
if ([self shouldRedirectOutputToFile])
[self writeOutputToNSLog];
return exitStatus;
}
- (void)runTests {
DCHECK(!base::ShouldRunIOSUnittestsWithXCTest());
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
......@@ -216,4 +236,9 @@ void RunTestsFromIOSApp() {
}
}
bool ShouldRunIOSUnittestsWithXCTest() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableRunIOSUnittestsWithXCTest);
}
} // namespace base
......@@ -84,3 +84,9 @@ const char switches::kTestLauncherTrace[] = "test-launcher-trace";
const char switches::kTestTinyTimeout[] = "test-tiny-timeout";
const char switches::kUiTestActionTimeout[] = "ui-test-action-timeout";
const char switches::kUiTestActionMaxTimeout[] = "ui-test-action-max-timeout";
#if defined(OS_IOS)
// If enabled, runs unittests using the XCTest test runner.
const char switches::kEnableRunIOSUnittestsWithXCTest[] =
"enable-run-ios-unittests-with-xctest";
#endif
......@@ -5,6 +5,8 @@
#ifndef BASE_TEST_TEST_SWITCHES_H_
#define BASE_TEST_TEST_SWITCHES_H_
#include "build/build_config.h"
namespace switches {
// All switches in alphabetical order. The switches should be documented
......@@ -32,6 +34,10 @@ extern const char kTestTinyTimeout[];
extern const char kUiTestActionTimeout[];
extern const char kUiTestActionMaxTimeout[];
#if defined(OS_IOS)
extern const char kEnableRunIOSUnittestsWithXCTest[];
#endif
} // namespace switches
#endif // BASE_TEST_TEST_SWITCHES_H_
......@@ -1808,13 +1808,20 @@ template("ios_xctest_test") {
_host_target = _target_name
_host_output = _output_name
_xctest_shell_source_target = _xctest_target + "shell_source"
source_set(_xctest_shell_source_target) {
sources = [
"//build/config/ios/xctest_shell.mm",
]
# Allow invokers to specify their own target for the xctest module, but
# fall back to a default (empty) module otherwise.
if (defined(invoker.xctest_module_target)) {
_xctest_module_target = invoker.xctest_module_target
} else {
_xctest_module_target_name = _xctest_target + "shell_source"
_xctest_module_target = ":$_xctest_module_target_name"
source_set(_xctest_module_target_name) {
sources = [
"//build/config/ios/xctest_shell.mm",
]
configs += [ "//build/config/ios:xctest_config" ]
configs += [ "//build/config/ios:xctest_config" ]
}
}
ios_xctest_bundle(_xctest_target) {
......@@ -1824,7 +1831,7 @@ template("ios_xctest_test") {
xcode_test_application_name = _host_output
deps = [
":$_xctest_shell_source_target",
_xctest_module_target,
]
}
......
......@@ -293,6 +293,14 @@ template("test") {
import("//build/config/ios/ios_sdk.gni")
import("//build/config/ios/rules.gni")
declare_args() {
# Keep the unittest-as-xctest functionality defaulted to off until the
# bots are updated to handle it properly.
# TODO(crbug.com/1001667): Remove this arg once the iOS test runner
# supports running unittests with xctest.
enable_run_ios_unittests_with_xctest = false
}
_test_target = target_name
_resources_bundle_data = target_name + "_resources_bundle_data"
......@@ -306,9 +314,19 @@ template("test") {
]
}
ios_app_bundle(_test_target) {
if (enable_run_ios_unittests_with_xctest) {
ios_test_target_type = "ios_xctest_test"
} else {
ios_test_target_type = "ios_app_bundle"
}
target(ios_test_target_type, _test_target) {
testonly = true
if (enable_run_ios_unittests_with_xctest) {
xctest_module_target = "//base/test:google_test_runner"
}
# See above call.
set_sources_assignment_filter([])
forward_variables_from(invoker, "*", [ "testonly" ])
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment