blob: b22119032b66c5b8cc2163289f0f258fad707795 [file] [log] [blame]
// 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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
#include "test/ios/google_test_setup.h"
#import <UIKit/UIKit.h>
#include "base/logging.h"
#include "gtest/gtest.h"
#include "test/ios/cptest_google_test_runner_delegate.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
@interface UIApplication (Testing)
- (void)_terminateWithStatus:(int)status;
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 {
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 =
listeners.Append(new IOSRunLoopListener);
} // namespace
@interface CrashpadUnitTestDelegate : NSObject <CPTestGoogleTestRunnerDelegate>
@property(nonatomic, readwrite, strong) UIWindow* window;
- (void)runTests;
@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.
if (![self supportsRunningGoogleTestsWithXCTest]) {
// 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;
- (BOOL)supportsRunningGoogleTestsWithXCTest {
return getenv("XCTestConfigurationFilePath") != nullptr;
- (int)runGoogleTests {
int exitStatus = RUN_ALL_TESTS();
return exitStatus;
- (void)runTests {
DCHECK(![self supportsRunningGoogleTestsWithXCTest]);
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( 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];
namespace crashpad {
namespace test {
void IOSLaunchApplicationAndRunTests(int argc, char* argv[]) {
@autoreleasepool {
int exit_status =
UIApplicationMain(argc, argv, nil, @"CrashpadUnitTestDelegate");
} // namespace test
} // namespace crashpad