| // |
| // GTMGoogleTestRunner.mm |
| // |
| // Copyright 2013 Google Inc. |
| // |
| // 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. |
| // |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| // This is a XCTest based unit test that will run all of the GoogleTest |
| // https://code.google.com/p/googletest/ |
| // based tests in the project, and will report results correctly via XCTest so |
| // that Xcode can pick them up in it's UI. |
| |
| // XCTest dynamically creates one XCTest per GoogleTest. |
| // GoogleTest is set up using a custom event listener (GoogleTestPrinter) |
| // which knows how to log GoogleTest test results in a manner that XCTest (and |
| // the Xcode IDE) understand. |
| |
| // Note that this does not able you to control individual tests from the Xcode |
| // UI. You can only turn on/off all of the C++ tests. It does however give |
| // you output that you can click on in the Xcode UI and immediately jump to a |
| // test failure. |
| |
| // This class is not compiled as part of the standard Google Toolbox For Mac |
| // project because of it's dependency on https://code.google.com/p/googletest/ |
| |
| // To use this: |
| // - Add GTMGoogleTestRunner to your test bundle sources. |
| // - Add gtest-all.cc from gtest to your test bundle sources. |
| // - Write some C++ tests and add them to your test bundle sources. |
| // - Build and run tests. Your C++ tests should just execute. |
| |
| // NOTE: |
| // A key difference between how GTMGoogleTestRunner runs tests versus how a |
| // "standard" unit test package runs tests is that SetUpTestSuite/SetupTestCase |
| // and TeardownTestSuite/TeardownTestCase are going to be called before/after |
| // *every* individual test. Unfortunately this is due to restrictions in the |
| // design of GoogleTest in that the only way to run individual tests is to |
| // use a filter to focus on a specific test, and then "run" all the tests |
| // multiple times. |
| // If you have state that you need maintained across tests (not normally a |
| // great idea anyhow), using SetUp*, Teardown* is not going to work for you. |
| // |
| // Also note that if you are using Bazel/Tulsi that clicking on the tests in |
| // Xcode is not going to jump to the code. Unfortunately Bazel compiles relative |
| // paths into the tests, and Xcode requires full paths. |
| |
| #import <XCTest/XCTest.h> |
| #import <objc/runtime.h> |
| |
| #include "third_party/gtest/include/gtest/gtest.h" |
| |
| // This is a SPI for dealing with skipped tests. |
| @interface XCTSkippedTestContext : NSObject |
| |
| - (instancetype)initWithExplanation:(NSString *)explanation |
| evaluatedExpression:(NSString *)evaluatedExpression |
| message:(NSString *)message |
| sourceCodeContext:(XCTSourceCodeContext *)sourceCodeContext; |
| |
| @end |
| |
| using ::testing::EmptyTestEventListener; |
| using ::testing::TestCase; |
| using ::testing::TestEventListener; |
| using ::testing::TestEventListeners; |
| using ::testing::TestInfo; |
| using ::testing::TestPartResult; |
| using ::testing::TestResult; |
| using ::testing::UnitTest; |
| |
| // GTMGoogleTestRunner is a XCTestCase that makes a sub test suite populated |
| // with all of the GoogleTest unit tests. |
| @interface GTMGoogleTestRunner : XCTestCase { |
| NSString *testName_; |
| } |
| |
| // If a test has been skipped this will be set with an exception describing |
| // the skipped test. |
| @property(nonatomic) _XCTSkipFailureException *skipException; |
| |
| // The name for a test is the GoogleTest name which is "TestCase.Test" |
| - (instancetype)initWithName:(NSString *)testName; |
| @end |
| |
| namespace { |
| |
| // A gtest printer that takes care of reporting gtest results via the |
| // XCTest interface. Note that a test suite in XCTest == a test case in gtest |
| // and a test case in XCTest == a test in gtest. |
| // This will handle fatal and non-fatal gtests properly. |
| class GoogleTestPrinter : public EmptyTestEventListener { |
| public: |
| GoogleTestPrinter(GTMGoogleTestRunner *test_case) : test_case_(test_case) {} |
| |
| virtual ~GoogleTestPrinter() {} |
| |
| virtual void OnTestPartResult(const TestPartResult &test_part_result) { |
| if (test_part_result.passed()) { |
| return; |
| } |
| |
| const char *file_name = test_part_result.file_name(); |
| NSString *file = @(file_name ? file_name : "<file name unavailable>"); |
| int line = test_part_result.line_number(); |
| NSString *summary = @(test_part_result.summary()); |
| |
| // gtest likes to give multi-line summaries. These don't look good in |
| // the Xcode UI, so we clean them up. |
| NSString *oneLineSummary = |
| [summary stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; |
| BOOL expected = test_part_result.nonfatally_failed(); |
| XCTSourceCodeLocation *location = |
| [[XCTSourceCodeLocation alloc] initWithFilePath:file lineNumber:line]; |
| XCTSourceCodeContext *codeContext = |
| [[XCTSourceCodeContext alloc] initWithLocation:location]; |
| |
| if (test_part_result.skipped()) { |
| // Test skipping works differently than other test failures in XCTest in |
| // that it is done via exceptions. We can't throw the exception from |
| // here though because it will be caught by the GUnit test |
| // infrastructure and reported as an error. So we record all of the data |
| // we need, and then throw the exception in -runGoogleTest once we are |
| // above the GUnit exception handling code. |
| XCTSkippedTestContext *skippedContext = |
| [[XCTSkippedTestContext alloc] initWithExplanation:oneLineSummary |
| evaluatedExpression:nil |
| message:nil |
| sourceCodeContext:codeContext]; |
| NSDictionary *userInfo = |
| @{@"XCTestErrorUserInfoKeySkippedTestContext" : skippedContext}; |
| test_case_.skipException = [[_XCTSkipFailureException alloc] |
| initWithName:@"_XCTSkipFailureException" |
| reason:@"Test skipped" |
| userInfo:userInfo]; |
| } else { |
| XCTIssue *issue = [[XCTIssue alloc] |
| initWithType:expected ? XCTIssueTypeAssertionFailure |
| : XCTIssueTypeUncaughtException |
| compactDescription:oneLineSummary |
| detailedDescription:summary |
| sourceCodeContext:codeContext |
| associatedError:nil |
| attachments:@[]]; |
| [test_case_ recordIssue:issue]; |
| } |
| } |
| |
| private: |
| GTMGoogleTestRunner *test_case_; |
| }; |
| |
| NSString *SelectorNameFromGTestName(NSString *testName) { |
| NSRange dot = [testName rangeOfString:@"."]; |
| return [NSString stringWithFormat:@"%@::%@", [testName substringToIndex:dot.location], |
| [testName substringFromIndex:dot.location + 1]]; |
| } |
| |
| } // namespace |
| |
| |
| @implementation GTMGoogleTestRunner |
| |
| + (void)initGoogleTest { |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| NSArray *arguments = [NSProcessInfo processInfo].arguments; |
| int argc = (int)arguments.count; |
| char **argv = static_cast<char **>(alloca((sizeof(char *) * (argc + 1)))); |
| for (int index = 0; index < argc; index++) { |
| argv[index] = const_cast<char *>([arguments[index] UTF8String]); |
| } |
| argv[argc] = NULL; |
| |
| testing::InitGoogleTest(&argc, argv); |
| }); |
| } |
| |
| + (XCTestSuite *)defaultTestSuite { |
| [GTMGoogleTestRunner initGoogleTest]; |
| XCTestSuite *result = [[XCTestSuite alloc] initWithName:NSStringFromClass(self)]; |
| UnitTest *test = UnitTest::GetInstance(); |
| |
| // Walk the GoogleTest tests, adding sub tests and sub suites as appropriate. |
| int total_test_case_count = test->total_test_case_count(); |
| for (int i = 0; i < total_test_case_count; ++i) { |
| const TestCase *test_case = test->GetTestCase(i); |
| int total_test_count = test_case->total_test_count(); |
| XCTestSuite *subSuite = [[XCTestSuite alloc] initWithName:@(test_case->name())]; |
| [result addTest:subSuite]; |
| for (int j = 0; j < total_test_count; ++j) { |
| const TestInfo *test_info = test_case->GetTestInfo(j); |
| NSString *testName = [NSString |
| stringWithFormat:@"%s.%s", test_case->name(), test_info->name()]; |
| XCTestCase *xcTest = [[self alloc] initWithName:testName]; |
| [subSuite addTest:xcTest]; |
| } |
| } |
| return result; |
| } |
| |
| - (instancetype)initWithName:(NSString *)testName { |
| // Xcode 6.1 started taking the testName from the selector instead of calling |
| // -name. |
| // So we will add selectors to GTMGoogleTestRunner. |
| // They should all be unique because the selectors are named cppclass.method |
| // Filed as radar 18798444. |
| Class cls = [self class]; |
| NSString *selectorTestName = SelectorNameFromGTestName(testName); |
| SEL selector = sel_registerName([selectorTestName UTF8String]); |
| Method method = class_getInstanceMethod(cls, @selector(runGoogleTest)); |
| IMP implementation = method_getImplementation(method); |
| const char *encoding = method_getTypeEncoding(method); |
| // We may be called more than once for the same testName. Check before adding new method to avoid |
| // failure from adding multiple methods with the same name. |
| if (!class_getInstanceMethod(cls, selector) && |
| !class_addMethod(cls, selector, implementation, encoding)) { |
| // If we can't add a method, we should blow up here. |
| [NSException raise:NSInternalInconsistencyException |
| format:@"Unable to add %@ to %@.", testName, cls]; |
| } |
| if ((self = [super initWithSelector:selector])) { |
| testName_ = testName; |
| } |
| return self; |
| } |
| |
| - (NSString *)name { |
| // An XCTest name must be "-[foo bar]" or it won't be parsed properly. |
| NSRange dot = [testName_ rangeOfString:@"."]; |
| return [NSString |
| stringWithFormat:@"-[%@ %@]", [testName_ substringToIndex:dot.location], |
| [testName_ substringFromIndex:dot.location + 1]]; |
| } |
| |
| - (void)runGoogleTest { |
| [GTMGoogleTestRunner initGoogleTest]; |
| |
| // Gets hold of the event listener list. |
| TestEventListeners &listeners = UnitTest::GetInstance()->listeners(); |
| |
| // Adds a listener to the end. |
| GoogleTestPrinter printer = GoogleTestPrinter(self); |
| listeners.Append(&printer); |
| |
| // Remove the default printer if it exists. |
| TestEventListener *defaultListener = listeners.default_result_printer(); |
| if (defaultListener) { |
| delete listeners.Release(defaultListener); |
| } |
| |
| // Since there is no way of running a single GoogleTest directly, we use the |
| // filter mechanism in GoogleTest to simulate it for us. |
| GTEST_FLAG_SET(filter, [testName_ UTF8String]); |
| |
| // Intentionally ignore return value of RUN_ALL_TESTS. We will be printing |
| // the output appropriately, and there is no reason to mark this test as |
| // "failed" if RUN_ALL_TESTS returns non-zero. |
| (void)RUN_ALL_TESTS(); |
| |
| // Remove the listener that we added. |
| listeners.Release(&printer); |
| |
| // If there was a test skip, skipException will be set. |
| NSException *exception = self.skipException; |
| if (exception) { |
| self.skipException = nil; |
| @throw exception; |
| } |
| } |
| |
| @end |