| // 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 |