GoogleTests.mm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*
  2. * Copyright (c) 2013 Matthew Stevens
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining
  5. * a copy of this software and associated documentation files (the
  6. * "Software"), to deal in the Software without restriction, including
  7. * without limitation the rights to use, copy, modify, merge, publish,
  8. * distribute, sublicense, and/or sell copies of the Software, and to
  9. * permit persons to whom the Software is furnished to do so, subject to
  10. * the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be
  13. * included in all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. */
  23. #import <Foundation/Foundation.h>
  24. #import <XCTest/XCTest.h>
  25. #import <gtest/gtest.h>
  26. #import <objc/runtime.h>
  27. using testing::TestCase;
  28. using testing::TestInfo;
  29. using testing::TestPartResult;
  30. using testing::UnitTest;
  31. static NSString * const GoogleTestDisabledPrefix = @"DISABLED_";
  32. /**
  33. * Class prefix used for generated Objective-C class names.
  34. *
  35. * If a class name generated for a Google Test case conflicts with an existing
  36. * class the value of this variable can be changed to add a class prefix.
  37. */
  38. static NSString * const GeneratedClassPrefix = @"";
  39. /**
  40. * Map of test keys to Google Test filter strings.
  41. *
  42. * Some names allowed by Google Test would result in illegal Objective-C
  43. * identifiers and in such cases the generated class and method names are
  44. * adjusted to handle this. This map is used to obtain the original Google Test
  45. * filter string associated with a generated Objective-C test method.
  46. */
  47. static NSDictionary *GoogleTestFilterMap;
  48. /**
  49. * A Google Test listener that reports failures to XCTest.
  50. */
  51. class XCTestListener : public testing::EmptyTestEventListener {
  52. public:
  53. XCTestListener(XCTestCase *testCase) :
  54. _testCase(testCase) {}
  55. void OnTestPartResult(const TestPartResult& test_part_result) {
  56. if (test_part_result.passed())
  57. return;
  58. int lineNumber = test_part_result.line_number();
  59. const char *fileName = test_part_result.file_name();
  60. NSString *path = fileName ? [@(fileName) stringByStandardizingPath] : nil;
  61. NSString *description = @(test_part_result.message());
  62. [_testCase recordFailureWithDescription:description
  63. inFile:path
  64. atLine:(lineNumber >= 0 ? (NSUInteger)lineNumber : 0)
  65. expected:YES];
  66. }
  67. private:
  68. XCTestCase *_testCase;
  69. };
  70. /**
  71. * Registers an XCTestCase subclass for each Google Test case.
  72. *
  73. * Generating these classes allows Google Test cases to be represented as peers
  74. * of standard XCTest suites and supports filtering of test runs to specific
  75. * Google Test cases or individual tests via Xcode.
  76. */
  77. @interface GoogleTestLoader : NSObject
  78. @end
  79. /**
  80. * Base class for the generated classes for Google Test cases.
  81. */
  82. @interface GoogleTestCase : XCTestCase
  83. @end
  84. @implementation GoogleTestCase
  85. /**
  86. * Associates generated Google Test classes with the test bundle.
  87. *
  88. * This affects how the generated test cases are represented in reports. By
  89. * associating the generated classes with a test bundle the Google Test cases
  90. * appear to be part of the same test bundle that this source file is compiled
  91. * into. Without this association they appear to be part of a bundle
  92. * representing the directory of an internal Xcode tool that runs the tests.
  93. */
  94. + (NSBundle *)bundleForClass {
  95. return [NSBundle bundleForClass:[GoogleTestLoader class]];
  96. }
  97. /**
  98. * Implementation of +[XCTestCase testInvocations] that returns an array of test
  99. * invocations for each test method in the class.
  100. *
  101. * This differs from the standard implementation of testInvocations, which only
  102. * adds methods with a prefix of "test".
  103. */
  104. + (NSArray *)testInvocations {
  105. NSMutableArray *invocations = [NSMutableArray array];
  106. unsigned int methodCount = 0;
  107. Method *methods = class_copyMethodList([self class], &methodCount);
  108. for (unsigned int i = 0; i < methodCount; i++) {
  109. SEL sel = method_getName(methods[i]);
  110. NSMethodSignature *sig = [self instanceMethodSignatureForSelector:sel];
  111. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  112. [invocation setSelector:sel];
  113. [invocations addObject:invocation];
  114. }
  115. free(methods);
  116. return invocations;
  117. }
  118. @end
  119. /**
  120. * Runs a single test.
  121. */
  122. static void RunTest(id self, SEL _cmd) {
  123. XCTestListener *listener = new XCTestListener(self);
  124. UnitTest *googleTest = UnitTest::GetInstance();
  125. googleTest->listeners().Append(listener);
  126. NSString *testKey = [NSString stringWithFormat:@"%@.%@", [self class], NSStringFromSelector(_cmd)];
  127. NSString *testFilter = GoogleTestFilterMap[testKey];
  128. XCTAssertNotNil(testFilter, @"No test filter found for test %@", testKey);
  129. testing::GTEST_FLAG(filter) = [testFilter UTF8String];
  130. (void)RUN_ALL_TESTS();
  131. delete googleTest->listeners().Release(listener);
  132. int totalTestsRun = googleTest->successful_test_count() + googleTest->failed_test_count();
  133. XCTAssertEqual(totalTestsRun, 1, @"Expected to run a single test for filter \"%@\"", testFilter);
  134. }
  135. @implementation GoogleTestLoader
  136. /**
  137. * Performs registration of classes for Google Test cases after our bundle has
  138. * finished loading.
  139. *
  140. * This registration needs to occur before XCTest queries the runtime for test
  141. * subclasses, but after C++ static initializers have run so that all Google
  142. * Test cases have been registered. This is accomplished by synchronously
  143. * observing the NSBundleDidLoadNotification for our own bundle.
  144. */
  145. + (void)load {
  146. NSBundle *bundle = [NSBundle bundleForClass:self];
  147. [[NSNotificationCenter defaultCenter] addObserverForName:NSBundleDidLoadNotification object:bundle queue:nil usingBlock:^(NSNotification *notification) {
  148. [self registerTestClasses];
  149. }];
  150. }
  151. + (void)registerTestClasses {
  152. // Pass the command-line arguments to Google Test to support the --gtest options
  153. NSArray *arguments = [[NSProcessInfo processInfo] arguments];
  154. int i = 0;
  155. int argc = (int)[arguments count];
  156. const char **argv = (const char **)calloc((unsigned int)argc + 1, sizeof(const char *));
  157. for (NSString *arg in arguments) {
  158. argv[i++] = [arg UTF8String];
  159. }
  160. testing::InitGoogleTest(&argc, (char **)argv);
  161. UnitTest *googleTest = UnitTest::GetInstance();
  162. testing::TestEventListeners& listeners = googleTest->listeners();
  163. delete listeners.Release(listeners.default_result_printer());
  164. free(argv);
  165. BOOL runDisabledTests = testing::GTEST_FLAG(also_run_disabled_tests);
  166. NSMutableDictionary *testFilterMap = [NSMutableDictionary dictionary];
  167. NSCharacterSet *decimalDigitCharacterSet = [NSCharacterSet decimalDigitCharacterSet];
  168. for (int testCaseIndex = 0; testCaseIndex < googleTest->total_test_case_count(); testCaseIndex++) {
  169. const TestCase *testCase = googleTest->GetTestCase(testCaseIndex);
  170. NSString *testCaseName = @(testCase->name());
  171. // For typed tests '/' is used to separate the parts of the test case name.
  172. NSArray *testCaseNameComponents = [testCaseName componentsSeparatedByString:@"/"];
  173. if (runDisabledTests == NO) {
  174. BOOL testCaseDisabled = NO;
  175. for (NSString *component in testCaseNameComponents) {
  176. if ([component hasPrefix:GoogleTestDisabledPrefix]) {
  177. testCaseDisabled = YES;
  178. break;
  179. }
  180. }
  181. if (testCaseDisabled) {
  182. continue;
  183. }
  184. }
  185. // Join the test case name components with '_' rather than '/' to create
  186. // a valid class name.
  187. NSString *className = [GeneratedClassPrefix stringByAppendingString:[testCaseNameComponents componentsJoinedByString:@"_"]];
  188. Class testClass = objc_allocateClassPair([GoogleTestCase class], [className UTF8String], 0);
  189. NSAssert1(testClass, @"Failed to register Google Test class \"%@\", this class may already exist. The value of GeneratedClassPrefix can be changed to avoid this.", className);
  190. BOOL hasMethods = NO;
  191. for (int testIndex = 0; testIndex < testCase->total_test_count(); testIndex++) {
  192. const TestInfo *testInfo = testCase->GetTestInfo(testIndex);
  193. NSString *testName = @(testInfo->name());
  194. if (runDisabledTests == NO && [testName hasPrefix:GoogleTestDisabledPrefix]) {
  195. continue;
  196. }
  197. // Google Test allows test names starting with a digit, prefix these with an
  198. // underscore to create a valid method name.
  199. NSString *methodName = testName;
  200. if ([methodName length] > 0 && [decimalDigitCharacterSet characterIsMember:[methodName characterAtIndex:0]]) {
  201. methodName = [@"_" stringByAppendingString:methodName];
  202. }
  203. NSString *testKey = [NSString stringWithFormat:@"%@.%@", className, methodName];
  204. NSString *testFilter = [NSString stringWithFormat:@"%@.%@", testCaseName, testName];
  205. testFilterMap[testKey] = testFilter;
  206. SEL selector = sel_registerName([methodName UTF8String]);
  207. BOOL added = class_addMethod(testClass, selector, (IMP)RunTest, "v@:");
  208. NSAssert1(added, @"Failed to add Goole Test method \"%@\", this method may already exist in the class.", methodName);
  209. hasMethods = YES;
  210. }
  211. if (hasMethods) {
  212. objc_registerClassPair(testClass);
  213. } else {
  214. objc_disposeClassPair(testClass);
  215. }
  216. }
  217. GoogleTestFilterMap = testFilterMap;
  218. }
  219. @end