mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
Adds support for running GTests on iOS.
iOS needs to run tests from within the context of a UIApplication, and it needs to periodically spin the runloop to ensure that the watchdog does not kill the app for being unresponsive. BUG=crashpad:31 Change-Id: Ia1d881e478d4f83c236b475a21529760c06100c7 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/1904226 Commit-Queue: Rohit Rao <rohitrao@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
parent
558b7ea43f
commit
54bbd7d0d5
BIN
build/ios/Default.png
Normal file
BIN
build/ios/Default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
10
build/ios/Unittest-Info.plist
Normal file
10
build/ios/Unittest-Info.plist
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${IOS_BUNDLE_ID_PREFIX}.gtest.${GTEST_BUNDLE_ID_SUFFIX:rfc1034identifier}</string>
|
||||||
|
<key>UIApplicationDelegate</key>
|
||||||
|
<string>CrashpadUnitTestDelegate</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -18,9 +18,35 @@ if (crashpad_is_in_chromium) {
|
|||||||
import("//testing/test.gni")
|
import("//testing/test.gni")
|
||||||
} else {
|
} else {
|
||||||
template("test") {
|
template("test") {
|
||||||
executable(target_name) {
|
if (crashpad_is_ios) {
|
||||||
testonly = true
|
import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni")
|
||||||
forward_variables_from(invoker, "*")
|
|
||||||
|
_launch_image_bundle_target = target_name + "_launch_image"
|
||||||
|
bundle_data(_launch_image_bundle_target) {
|
||||||
|
forward_variables_from(invoker, [ "testonly" ])
|
||||||
|
sources = [
|
||||||
|
"//build/ios/Default.png",
|
||||||
|
]
|
||||||
|
outputs = [
|
||||||
|
"{{bundle_contents_dir}}/{{source_file_part}}",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ios_app_bundle(target_name) {
|
||||||
|
testonly = true
|
||||||
|
info_plist = "//build/ios/Unittest-Info.plist"
|
||||||
|
extra_substitutions = [ "GTEST_BUNDLE_ID_SUFFIX=$target_name" ]
|
||||||
|
forward_variables_from(invoker, "*")
|
||||||
|
if (!defined(deps)) {
|
||||||
|
deps = []
|
||||||
|
}
|
||||||
|
deps += [ ":$_launch_image_bundle_target" ]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
executable(target_name) {
|
||||||
|
testonly = true
|
||||||
|
forward_variables_from(invoker, "*")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,6 +243,21 @@ if (!crashpad_is_ios) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (crashpad_is_ios) {
|
||||||
|
source_set("test_runner_ios") {
|
||||||
|
testonly = true
|
||||||
|
sources = [
|
||||||
|
"gtest_runner_ios.h",
|
||||||
|
"gtest_runner_ios.mm",
|
||||||
|
]
|
||||||
|
configs += [ "..:crashpad_config" ]
|
||||||
|
deps = [
|
||||||
|
"../third_party/gtest:gtest",
|
||||||
|
]
|
||||||
|
libs = [ "UIKit.framework" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static_library("gmock_main") {
|
static_library("gmock_main") {
|
||||||
testonly = true
|
testonly = true
|
||||||
sources = [
|
sources = [
|
||||||
@ -257,6 +272,9 @@ static_library("gmock_main") {
|
|||||||
"../third_party/mini_chromium:base",
|
"../third_party/mini_chromium:base",
|
||||||
"../third_party/mini_chromium:base_test_support",
|
"../third_party/mini_chromium:base_test_support",
|
||||||
]
|
]
|
||||||
|
if (crashpad_is_ios) {
|
||||||
|
deps += [ ":test_runner_ios" ]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static_library("gtest_main") {
|
static_library("gtest_main") {
|
||||||
@ -272,4 +290,7 @@ static_library("gtest_main") {
|
|||||||
"../third_party/mini_chromium:base",
|
"../third_party/mini_chromium:base",
|
||||||
"../third_party/mini_chromium:base_test_support",
|
"../third_party/mini_chromium:base_test_support",
|
||||||
]
|
]
|
||||||
|
if (crashpad_is_ios) {
|
||||||
|
deps += [ ":test_runner_ios" ]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#endif // CRASHPAD_TEST_LAUNCHER_GMOCK
|
#endif // CRASHPAD_TEST_LAUNCHER_GMOCK
|
||||||
|
|
||||||
|
#if defined(OS_IOS)
|
||||||
|
#include "test/gtest_runner_ios.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
#include "test/win/win_child_process.h"
|
#include "test/win/win_child_process.h"
|
||||||
#endif // OS_WIN
|
#endif // OS_WIN
|
||||||
@ -93,5 +97,12 @@ int main(int argc, char* argv[]) {
|
|||||||
#error #define CRASHPAD_TEST_LAUNCHER_GTEST or CRASHPAD_TEST_LAUNCHER_GMOCK
|
#error #define CRASHPAD_TEST_LAUNCHER_GTEST or CRASHPAD_TEST_LAUNCHER_GMOCK
|
||||||
#endif // CRASHPAD_TEST_LAUNCHER_GMOCK
|
#endif // CRASHPAD_TEST_LAUNCHER_GMOCK
|
||||||
|
|
||||||
|
#if defined(OS_IOS)
|
||||||
|
// iOS needs to run tests within the context of an app, so call a helper that
|
||||||
|
// invokes UIApplicationMain(). The application delegate will call
|
||||||
|
// RUN_ALL_TESTS() and exit before returning control to this function.
|
||||||
|
crashpad::test::IOSLaunchApplicationAndRunTests(argc, argv);
|
||||||
|
#else
|
||||||
return RUN_ALL_TESTS();
|
return RUN_ALL_TESTS();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
33
test/gtest_runner_ios.h
Normal file
33
test/gtest_runner_ios.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2019 The Crashpad Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef CRASHPAD_TEST_GTEST_RUNNER_IOS_
|
||||||
|
#define CRASHPAD_TEST_GTEST_RUNNER_IOS_
|
||||||
|
|
||||||
|
namespace crashpad {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
//! \brief Runs all registered tests in the context of a UIKit application.
|
||||||
|
//!
|
||||||
|
//! Invokes UIApplicationMain() to launch the iOS application and runs all
|
||||||
|
//! registered tests after the application finishes
|
||||||
|
//! launching. UIApplicationMain() brings up the main runloop and never returns,
|
||||||
|
//! so therefore this function never returns either. It invokes _exit() to
|
||||||
|
//! terminate the application after tests have completed.
|
||||||
|
void IOSLaunchApplicationAndRunTests(int argc, char* argv[]);
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace crashpad
|
||||||
|
|
||||||
|
#endif // CRASHPAD_TEST_GTEST_RUNNER_IOS_
|
114
test/gtest_runner_ios.mm
Normal file
114
test/gtest_runner_ios.mm
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2019 The Crashpad Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "test/gtest_runner_ios.h"
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
@interface UIApplication (Testing)
|
||||||
|
- (void)_terminateWithStatus:(int)status;
|
||||||
|
@end
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// The iOS watchdog timer will kill an app that doesn't spin the main event
|
||||||
|
// loop often enough. This uses a Gtest TestEventListener to spin the current
|
||||||
|
// loop after each test finishes. However, if any individual test takes too
|
||||||
|
// long, it is still possible that the app will get killed.
|
||||||
|
class IOSRunLoopListener : public testing::EmptyTestEventListener {
|
||||||
|
public:
|
||||||
|
virtual void OnTestEnd(const testing::TestInfo& test_info) {
|
||||||
|
@autoreleasepool {
|
||||||
|
// At the end of the test, spin the default loop for a moment.
|
||||||
|
NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001];
|
||||||
|
[[NSRunLoop currentRunLoop] runUntilDate:stop_date];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void RegisterTestEndListener() {
|
||||||
|
testing::TestEventListeners& listeners =
|
||||||
|
testing::UnitTest::GetInstance()->listeners();
|
||||||
|
listeners.Append(new IOSRunLoopListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
@interface CrashpadUnitTestDelegate : NSObject
|
||||||
|
@property(nonatomic, readwrite, strong) UIWindow* window;
|
||||||
|
- (void)runTests;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation CrashpadUnitTestDelegate
|
||||||
|
|
||||||
|
- (BOOL)application:(UIApplication*)application
|
||||||
|
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
|
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
|
||||||
|
self.window.backgroundColor = UIColor.whiteColor;
|
||||||
|
[self.window makeKeyAndVisible];
|
||||||
|
|
||||||
|
UIViewController* controller = [[UIViewController alloc] init];
|
||||||
|
[self.window setRootViewController:controller];
|
||||||
|
|
||||||
|
// Add a label with the app name.
|
||||||
|
UILabel* label = [[UILabel alloc] initWithFrame:controller.view.bounds];
|
||||||
|
label.text = [[NSProcessInfo processInfo] processName];
|
||||||
|
label.textAlignment = NSTextAlignmentCenter;
|
||||||
|
label.textColor = UIColor.blackColor;
|
||||||
|
[controller.view addSubview:label];
|
||||||
|
|
||||||
|
// Queue up the test run.
|
||||||
|
[self performSelector:@selector(runTests) withObject:nil afterDelay:0.1];
|
||||||
|
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)runTests {
|
||||||
|
RegisterTestEndListener();
|
||||||
|
|
||||||
|
int exitStatus = RUN_ALL_TESTS();
|
||||||
|
|
||||||
|
// 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]];
|
||||||
|
self.window = nil;
|
||||||
|
|
||||||
|
// 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 crashpad {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
void IOSLaunchApplicationAndRunTests(int argc, char* argv[]) {
|
||||||
|
@autoreleasepool {
|
||||||
|
int exit_status =
|
||||||
|
UIApplicationMain(argc, argv, nil, @"CrashpadUnitTestDelegate");
|
||||||
|
exit(exit_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crashpad
|
||||||
|
} // namespace test
|
Loading…
x
Reference in New Issue
Block a user