123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /*
- * Copyright (c) 2013 Matthew Stevens
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
- #import <Foundation/Foundation.h>
- #import <XCTest/XCTest.h>
- #import <gtest/gtest.h>
- #import <objc/runtime.h>
- using testing::TestCase;
- using testing::TestInfo;
- using testing::TestPartResult;
- using testing::UnitTest;
- static NSString * const GoogleTestDisabledPrefix = @"DISABLED_";
- /**
- * Class prefix used for generated Objective-C class names.
- *
- * If a class name generated for a Google Test case conflicts with an existing
- * class the value of this variable can be changed to add a class prefix.
- */
- static NSString * const GeneratedClassPrefix = @"";
- /**
- * Map of test keys to Google Test filter strings.
- *
- * Some names allowed by Google Test would result in illegal Objective-C
- * identifiers and in such cases the generated class and method names are
- * adjusted to handle this. This map is used to obtain the original Google Test
- * filter string associated with a generated Objective-C test method.
- */
- static NSDictionary *GoogleTestFilterMap;
- /**
- * A Google Test listener that reports failures to XCTest.
- */
- class XCTestListener : public testing::EmptyTestEventListener {
- public:
- XCTestListener(XCTestCase *testCase) :
- _testCase(testCase) {}
-
- void OnTestPartResult(const TestPartResult& test_part_result) {
- if (test_part_result.passed())
- return;
-
- int lineNumber = test_part_result.line_number();
- const char *fileName = test_part_result.file_name();
- NSString *path = fileName ? [@(fileName) stringByStandardizingPath] : nil;
- NSString *description = @(test_part_result.message());
- [_testCase recordFailureWithDescription:description
- inFile:path
- atLine:(lineNumber >= 0 ? (NSUInteger)lineNumber : 0)
- expected:YES];
- }
-
- private:
- XCTestCase *_testCase;
- };
- /**
- * Registers an XCTestCase subclass for each Google Test case.
- *
- * Generating these classes allows Google Test cases to be represented as peers
- * of standard XCTest suites and supports filtering of test runs to specific
- * Google Test cases or individual tests via Xcode.
- */
- @interface GoogleTestLoader : NSObject
- @end
- /**
- * Base class for the generated classes for Google Test cases.
- */
- @interface GoogleTestCase : XCTestCase
- @end
- @implementation GoogleTestCase
- /**
- * Associates generated Google Test classes with the test bundle.
- *
- * This affects how the generated test cases are represented in reports. By
- * associating the generated classes with a test bundle the Google Test cases
- * appear to be part of the same test bundle that this source file is compiled
- * into. Without this association they appear to be part of a bundle
- * representing the directory of an internal Xcode tool that runs the tests.
- */
- + (NSBundle *)bundleForClass {
- return [NSBundle bundleForClass:[GoogleTestLoader class]];
- }
- /**
- * Implementation of +[XCTestCase testInvocations] that returns an array of test
- * invocations for each test method in the class.
- *
- * This differs from the standard implementation of testInvocations, which only
- * adds methods with a prefix of "test".
- */
- + (NSArray *)testInvocations {
- NSMutableArray *invocations = [NSMutableArray array];
-
- unsigned int methodCount = 0;
- Method *methods = class_copyMethodList([self class], &methodCount);
-
- for (unsigned int i = 0; i < methodCount; i++) {
- SEL sel = method_getName(methods[i]);
- NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel];
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
- [invocation setSelector:sel];
- [invocations addObject:invocation];
- }
-
- free(methods);
-
- return invocations;
- }
- @end
- /**
- * Runs a single test.
- */
- static void RunTest(id self, SEL _cmd) {
- XCTestListener *listener = new XCTestListener(self);
- UnitTest *googleTest = UnitTest::GetInstance();
- googleTest->listeners().Append(listener);
-
- NSString *testKey = [NSString stringWithFormat:@"%@.%@", [self class], NSStringFromSelector(_cmd)];
- NSString *testFilter = GoogleTestFilterMap[testKey];
- XCTAssertNotNil(testFilter, @"No test filter found for test %@", testKey);
-
- testing::GTEST_FLAG(filter) = [testFilter UTF8String];
-
- (void)RUN_ALL_TESTS();
-
- delete googleTest->listeners().Release(listener);
-
- int totalTestsRun = googleTest->successful_test_count() + googleTest->failed_test_count();
- XCTAssertEqual(totalTestsRun, 1, @"Expected to run a single test for filter \"%@\"", testFilter);
- }
- @implementation GoogleTestLoader
- /**
- * Performs registration of classes for Google Test cases after our bundle has
- * finished loading.
- *
- * This registration needs to occur before XCTest queries the runtime for test
- * subclasses, but after C++ static initializers have run so that all Google
- * Test cases have been registered. This is accomplished by synchronously
- * observing the NSBundleDidLoadNotification for our own bundle.
- */
- + (void)load {
- NSBundle *bundle = [NSBundle bundleForClass:self];
- [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification object:bundle queue:nil usingBlock:^(NSNotification *notification) {
- [self registerTestClasses];
- }];
- }
- + (void)registerTestClasses {
- // Pass the command-line arguments to Google Test to support the --gtest options
- NSArray *arguments = [[NSProcessInfo processInfo] arguments];
-
- int i = 0;
- int argc = (int)[arguments count];
- const char **argv = (const char **)calloc((unsigned int)argc + 1, sizeof(const char *));
- for (NSString *arg in arguments) {
- argv[i++] = [arg UTF8String];
- }
-
- testing::InitGoogleTest(&argc, (char **)argv);
- UnitTest *googleTest = UnitTest::GetInstance();
- testing::TestEventListeners& listeners = googleTest->listeners();
- delete listeners.Release(listeners.default_result_printer());
- free(argv);
-
- BOOL runDisabledTests = testing::GTEST_FLAG(also_run_disabled_tests);
- NSMutableDictionary *testFilterMap = [NSMutableDictionary dictionary];
- NSCharacterSet *decimalDigitCharacterSet = [NSCharacterSet decimalDigitCharacterSet];
-
- for (int testCaseIndex = 0; testCaseIndex < googleTest->total_test_case_count(); testCaseIndex++) {
- const TestCase *testCase = googleTest->GetTestCase(testCaseIndex);
- NSString *testCaseName = @(testCase->name());
-
- // For typed tests '/' is used to separate the parts of the test case name.
- NSArray *testCaseNameComponents = [testCaseName componentsSeparatedByString:@"/"];
-
- if (runDisabledTests == NO) {
- BOOL testCaseDisabled = NO;
-
- for (NSString *component in testCaseNameComponents) {
- if ([component hasPrefix:GoogleTestDisabledPrefix]) {
- testCaseDisabled = YES;
- break;
- }
- }
-
- if (testCaseDisabled) {
- continue;
- }
- }
-
- // Join the test case name components with '_' rather than '/' to create
- // a valid class name.
- NSString *className = [GeneratedClassPrefix stringByAppendingString:[testCaseNameComponents componentsJoinedByString:@"_"]];
-
- Class testClass = objc_allocateClassPair([GoogleTestCase class], [className UTF8String], 0);
- NSAssert1(testClass, @"Failed to register Google Test class \"%@\", this class may already exist. The value of GeneratedClassPrefix can be changed to avoid this.", className);
- BOOL hasMethods = NO;
-
- for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
- const TestInfo *testInfo = testCase->GetTestInfo(testIndex);
- NSString *testName = @(testInfo->name());
- if (runDisabledTests == NO && [testName hasPrefix:GoogleTestDisabledPrefix]) {
- continue;
- }
-
- // Google Test allows test names starting with a digit, prefix these with an
- // underscore to create a valid method name.
- NSString *methodName = testName;
- if ([methodName length] > 0 && [decimalDigitCharacterSet characterIsMember:[methodName characterAtIndex:0]]) {
- methodName = [@"_" stringByAppendingString:methodName];
- }
-
- NSString *testKey = [NSString stringWithFormat:@"%@.%@", className, methodName];
- NSString *testFilter = [NSString stringWithFormat:@"%@.%@", testCaseName, testName];
- testFilterMap[testKey] = testFilter;
-
- SEL selector = sel_registerName([methodName UTF8String]);
- BOOL added = class_addMethod(testClass, selector, (IMP)RunTest, "v@:");
- NSAssert1(added, @"Failed to add Goole Test method \"%@\", this method may already exist in the class.", methodName);
- hasMethods = YES;
- }
-
- if (hasMethods) {
- objc_registerClassPair(testClass);
- } else {
- objc_disposeClassPair(testClass);
- }
- }
-
- GoogleTestFilterMap = testFilterMap;
- }
- @end
|