crashpad/test/ios/google_test_setup.mm
Rohit Rao 39116ab723 Adds support for running iOS unittests via XCTest.
Using XCTest allows us to drive tests from the commandline via
xcodebuild, and it also simplifies running tests on physical devices.

Tests put themselves into "XCTest-mode" if the
"XCTestConfigurationFilePath" environment variable is present.  This
variable is only set when XCTests are running.

Change-Id: If55199a7470f0479f107097eef1dfb1a705015e9
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2033427
Commit-Queue: Rohit Rao <rohitrao@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
2020-01-31 20:14:04 +00:00

134 lines
4.3 KiB
Plaintext

// 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/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."
#endif
@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 <CPTestGoogleTestRunnerDelegate>
@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.
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 {
RegisterTestEndListener();
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(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 test
} // namespace crashpad