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")
|
||||
} else {
|
||||
template("test") {
|
||||
executable(target_name) {
|
||||
testonly = true
|
||||
forward_variables_from(invoker, "*")
|
||||
if (crashpad_is_ios) {
|
||||
import("//third_party/mini_chromium/mini_chromium/build/ios/rules.gni")
|
||||
|
||||
_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") {
|
||||
testonly = true
|
||||
sources = [
|
||||
@ -257,6 +272,9 @@ static_library("gmock_main") {
|
||||
"../third_party/mini_chromium:base",
|
||||
"../third_party/mini_chromium:base_test_support",
|
||||
]
|
||||
if (crashpad_is_ios) {
|
||||
deps += [ ":test_runner_ios" ]
|
||||
}
|
||||
}
|
||||
|
||||
static_library("gtest_main") {
|
||||
@ -272,4 +290,7 @@ static_library("gtest_main") {
|
||||
"../third_party/mini_chromium:base",
|
||||
"../third_party/mini_chromium:base_test_support",
|
||||
]
|
||||
if (crashpad_is_ios) {
|
||||
deps += [ ":test_runner_ios" ]
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,10 @@
|
||||
#include "gmock/gmock.h"
|
||||
#endif // CRASHPAD_TEST_LAUNCHER_GMOCK
|
||||
|
||||
#if defined(OS_IOS)
|
||||
#include "test/gtest_runner_ios.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "test/win/win_child_process.h"
|
||||
#endif // OS_WIN
|
||||
@ -93,5 +97,12 @@ int main(int argc, char* argv[]) {
|
||||
#error #define CRASHPAD_TEST_LAUNCHER_GTEST or 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();
|
||||
#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