FSTGoogleTestTests.mm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /*
  2. * Copyright 2017 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #import <objc/runtime.h>
  18. #include "gtest/gtest.h"
  19. /**
  20. * An XCTest test case that finds C++ test cases written in the GoogleTest
  21. * framework, runs them, and reports the results back to Xcode. This allows
  22. * tests written in C++ that don't rely on XCTest to coexist in this project.
  23. *
  24. * As an extra feature, you can run all C++ tests by focusing on the GoogleTests
  25. * class.
  26. *
  27. * Each GoogleTest TestCase is mapped to a dynamically generated XCTestCase
  28. * class. Each GoogleTest TEST() is mapped to a test method on that XCTestCase.
  29. */
  30. @interface GoogleTests : XCTestCase
  31. @end
  32. namespace {
  33. // A testing::TestCase named "Foo" corresponds to an XCTestCase named
  34. // "FooTests".
  35. NSString* const kTestCaseSuffix = @"Tests";
  36. // A testing::TestInfo named "Foo" corresponds to test method named "testFoo".
  37. NSString* const kTestMethodPrefix = @"test";
  38. // A map of keys created by TestInfoKey to the corresponding testing::TestInfo
  39. // (wrapped in an NSValue). The generated XCTestCase classes are discovered and
  40. // instantiated by XCTest so this is the only means of plumbing per-test-method
  41. // state into these methods.
  42. NSDictionary<NSString*, NSValue*>* testInfosByKey;
  43. // If the user focuses on GoogleTests itself, this means force all C++ tests to
  44. // run.
  45. bool forceAllTests = false;
  46. void RunGoogleTestTests();
  47. /**
  48. * Loads this XCTest runner's configuration file and figures out which tests to
  49. * run based on the contents of that configuration file.
  50. *
  51. * @return the set of tests to run, or nil if the user asked for all tests or if
  52. * there's any problem loading or parsing the configuration.
  53. */
  54. NSSet<NSString*>* _Nullable LoadXCTestConfigurationTestsToRun() {
  55. // Xcode invokes the test runner with an XCTestConfigurationFilePath
  56. // environment variable set to the path of a configuration file containing,
  57. // among other things, the set of tests to run. The configuration file
  58. // deserializes to a non-public XCTestConfiguration class.
  59. //
  60. // This loads that file and then reflectively pulls out the testsToRun set.
  61. // Just in case any of these private details should change in the future and
  62. // something should fail here, the mechanism complains but fails open. This
  63. // way the worst that can happen is that users end up running more tests than
  64. // they intend, but we never accidentally show a green run that wasn't.
  65. static NSString* const configEnvVar = @"XCTestConfigurationFilePath";
  66. NSDictionary<NSString*, NSString*>* env =
  67. [[NSProcessInfo processInfo] environment];
  68. NSString* filePath = [env objectForKey:configEnvVar];
  69. if (!filePath) {
  70. NSLog(@"Missing %@ environment variable; assuming all tests", configEnvVar);
  71. return nil;
  72. }
  73. id config;
  74. NSError* error;
  75. if (@available(macOS 10.13, iOS 11, tvOS 11, *)) {
  76. NSData* data = [NSData dataWithContentsOfFile:filePath
  77. options:kNilOptions
  78. error:&error];
  79. if (!data) {
  80. NSLog(@"Failed to fill data with contents of file. %@", error);
  81. return nil;
  82. }
  83. config = [NSKeyedUnarchiver unarchivedObjectOfClass:NSObject.class
  84. fromData:data
  85. error:&error];
  86. } else {
  87. #pragma clang diagnostic push
  88. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  89. config = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
  90. #pragma clang diagnostic pop
  91. }
  92. if (!config) {
  93. NSLog(@"Failed to load any configuaration from %@=%@. %@", configEnvVar,
  94. filePath, error);
  95. return nil;
  96. }
  97. SEL testsToRunSelector = NSSelectorFromString(@"testsToRun");
  98. if (![config respondsToSelector:testsToRunSelector]) {
  99. NSLog(@"Invalid configuaration from %@=%@: missing testsToRun",
  100. configEnvVar, filePath);
  101. return nil;
  102. }
  103. // Invoke the testsToRun selector safely. This indirection is required because
  104. // just calling -performSelector: fails to properly retain the NSSet under
  105. // ARC.
  106. typedef NSSet<NSString*>* (*TestsToRunFunction)(id, SEL);
  107. IMP testsToRunMethod = [config methodForSelector:testsToRunSelector];
  108. auto testsToRunFunction =
  109. reinterpret_cast<TestsToRunFunction>(testsToRunMethod);
  110. return testsToRunFunction(config, testsToRunSelector);
  111. }
  112. /**
  113. * Creates a GoogleTest filter specification, suitable for passing to the
  114. * --gtest_filter flag, that limits GoogleTest to running the same set of tests
  115. * that Xcode requested.
  116. *
  117. * Each member of the testsToRun set is mapped as follows:
  118. *
  119. * * Bare class: "ClassTests" => "Class.*"
  120. * * Class and method: "ClassTests/testMethod" => "Class.Method"
  121. *
  122. * These members are then joined with a ":" as googletest requires.
  123. *
  124. * @see
  125. * https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md
  126. */
  127. NSString* CreateTestFiltersFromTestsToRun(NSSet<NSString*>* testsToRun) {
  128. NSMutableString* result = [[NSMutableString alloc] init];
  129. for (NSString* spec in testsToRun) {
  130. NSArray<NSString*>* parts = [spec componentsSeparatedByString:@"/"];
  131. NSString* gtestCaseName = nil;
  132. if (parts.count > 0) {
  133. NSString* className = parts[0];
  134. if ([className hasSuffix:kTestCaseSuffix]) {
  135. gtestCaseName = [className
  136. substringToIndex:className.length - kTestCaseSuffix.length];
  137. }
  138. }
  139. NSString* gtestMethodName = nil;
  140. if (parts.count > 1) {
  141. NSString* methodName = parts[1];
  142. if ([methodName hasPrefix:kTestMethodPrefix]) {
  143. gtestMethodName =
  144. [methodName substringFromIndex:kTestMethodPrefix.length];
  145. }
  146. }
  147. if (gtestCaseName) {
  148. if (result.length > 0) {
  149. [result appendString:@":"];
  150. }
  151. [result appendString:gtestCaseName];
  152. [result appendString:@"."];
  153. [result appendString:(gtestMethodName ? gtestMethodName : @"*")];
  154. }
  155. }
  156. return result;
  157. }
  158. /** Returns the name of the selector for the test method representing this
  159. * specific test. */
  160. NSString* SelectorNameForTestInfo(const testing::TestInfo* testInfo) {
  161. return
  162. [NSString stringWithFormat:@"%@%s", kTestMethodPrefix, testInfo->name()];
  163. }
  164. /** Returns the name of the class representing the given testing::TestCase. */
  165. NSString* ClassNameForTestCase(const testing::TestCase* testCase) {
  166. return [NSString stringWithFormat:@"%s%@", testCase->name(), kTestCaseSuffix];
  167. }
  168. /**
  169. * Returns a key name for the testInfosByKey dictionary. Each (class, selector)
  170. * pair corresponds to a unique GoogleTest result.
  171. */
  172. NSString* TestInfoKey(Class testClass, SEL testSelector) {
  173. return [NSString stringWithFormat:@"%@.%@", NSStringFromClass(testClass),
  174. NSStringFromSelector(testSelector)];
  175. }
  176. /**
  177. * A function that is the implementation for each generated test method. It
  178. * shouldn't be used directly--instead use it with class_addMethod to define the
  179. * behavior of the generated XCTestCase class.
  180. *
  181. * The first invocation of this method runs all GoogleTest tests. Delaying
  182. * execution this way allows XCTest to register to the test runner that it
  183. * actually has started.
  184. *
  185. * Looks up the testing::TestInfo for this test method and reports on the
  186. * outcome to XCTest, as if the test actually ran in this method.
  187. *
  188. * Note: The parameter names of self and _cmd match up with the implicit
  189. * parameters passed to any Objective-C method. Naming them this way here allows
  190. * XCTAssert and friends to work.
  191. */
  192. void XCTestMethod(XCTestCase* self, SEL _cmd) {
  193. RunGoogleTestTests();
  194. NSString* testInfoKey = TestInfoKey([self class], _cmd);
  195. NSValue* holder = testInfosByKey[testInfoKey];
  196. auto testInfo = static_cast<const testing::TestInfo*>(holder.pointerValue);
  197. if (!testInfo) {
  198. return;
  199. }
  200. if (!testInfo->should_run()) {
  201. // Test was filtered out by gunit; nothing to report.
  202. return;
  203. }
  204. const testing::TestResult* result = testInfo->result();
  205. if (result->Passed()) {
  206. // Let XCode know that the test ran and succeeded.
  207. XCTAssertTrue(true);
  208. return;
  209. } else if (result->Skipped()) {
  210. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400 || \
  211. __TV_OS_VERSION_MAX_ALLOWED >= 130400 || \
  212. __MAC_OS_X_VERSION_MAX_ALLOWED >= 101504
  213. // Let XCode know that the test was skipped.
  214. XCTSkip();
  215. #endif
  216. }
  217. // Test failed :-(. Record the failure such that XCode will navigate directly
  218. // to the file:line.
  219. int parts = result->total_part_count();
  220. for (int i = 0; i < parts; i++) {
  221. const testing::TestPartResult& part = result->GetTestPartResult(i);
  222. const char* path = part.file_name() ? part.file_name() : "";
  223. int line = part.line_number() > 0 ? part.line_number() : 0;
  224. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 || \
  225. __MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
  226. // Xcode 12
  227. auto* location = [[XCTSourceCodeLocation alloc] initWithFilePath:@(path)
  228. lineNumber:line];
  229. auto* context = [[XCTSourceCodeContext alloc] initWithLocation:location];
  230. auto* issue = [[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure
  231. compactDescription:@(part.summary())
  232. detailedDescription:@(part.message())
  233. sourceCodeContext:context
  234. associatedError:nil
  235. attachments:@[]];
  236. [self recordIssue:issue];
  237. #else
  238. // Xcode 11 and prior. recordFailureWithDescription:inFile:atLine:expected:
  239. // is deprecated in Xcode 12.
  240. [self recordFailureWithDescription:@(part.message())
  241. inFile:@(path)
  242. atLine:line
  243. expected:true];
  244. #endif
  245. }
  246. }
  247. /**
  248. * Generates a new subclass of XCTestCase for the given GoogleTest TestCase.
  249. * Each TestInfo (which represents an individual test method execution) is
  250. * translated into a method on the test case.
  251. *
  252. * @param testCase The testing::TestCase of interest to translate.
  253. * @param infoMap A map of TestInfoKeys to testing::TestInfos, populated by this
  254. * method.
  255. *
  256. * @return A new Class that's a subclass of XCTestCase, that's been registered
  257. * with the Objective-C runtime.
  258. */
  259. Class CreateXCTestCaseClass(const testing::TestCase* testCase,
  260. NSMutableDictionary<NSString*, NSValue*>* infoMap) {
  261. NSString* testCaseName = ClassNameForTestCase(testCase);
  262. Class testClass =
  263. objc_allocateClassPair([XCTestCase class], [testCaseName UTF8String], 0);
  264. // Create a method for each TestInfo.
  265. int testInfos = testCase->total_test_count();
  266. for (int j = 0; j < testInfos; j++) {
  267. const testing::TestInfo* testInfo = testCase->GetTestInfo(j);
  268. NSString* selectorName = SelectorNameForTestInfo(testInfo);
  269. SEL selector = sel_registerName([selectorName UTF8String]);
  270. // Use the XCTestMethod function as the method implementation. The v@:
  271. // indicates it is a void objective-C method; this must continue to match
  272. // the signature of XCTestMethod.
  273. IMP method = reinterpret_cast<IMP>(XCTestMethod);
  274. class_addMethod(testClass, selector, method, "v@:");
  275. NSString* infoKey = TestInfoKey(testClass, selector);
  276. NSValue* holder = [NSValue valueWithPointer:testInfo];
  277. infoMap[infoKey] = holder;
  278. }
  279. objc_registerClassPair(testClass);
  280. return testClass;
  281. }
  282. /**
  283. * Creates a test suite containing all C++ tests, used when the user starts the
  284. * GoogleTests class.
  285. *
  286. * Note: normally XCTest finds all the XCTestCase classes that are registered
  287. * with the run time and asks them to create suites for themselves. When a user
  288. * focuses on the GoogleTests class, XCTest no longer does this so we have to
  289. * force XCTest to see more tests than it would normally look at so that the
  290. * indicators in the test navigator update properly.
  291. */
  292. XCTestSuite* CreateAllTestsTestSuite() {
  293. XCTestSuite* allTestsSuite =
  294. [[XCTestSuite alloc] initWithName:@"All GoogleTest Tests"];
  295. [allTestsSuite
  296. addTest:[XCTestSuite testSuiteForTestCaseClass:[GoogleTests class]]];
  297. const testing::UnitTest* master = testing::UnitTest::GetInstance();
  298. int testCases = master->total_test_case_count();
  299. for (int i = 0; i < testCases; i++) {
  300. const testing::TestCase* testCase = master->GetTestCase(i);
  301. NSString* testCaseName = ClassNameForTestCase(testCase);
  302. Class testClass = objc_getClass([testCaseName UTF8String]);
  303. [allTestsSuite addTest:[XCTestSuite testSuiteForTestCaseClass:testClass]];
  304. }
  305. return allTestsSuite;
  306. }
  307. /**
  308. * Finds and runs googletest-based tests based on the XCTestConfiguration of the
  309. * current test invocation.
  310. */
  311. void CreateGoogleTestTests() {
  312. NSString* masterTestCaseName = NSStringFromClass([GoogleTests class]);
  313. // Initialize GoogleTest but don't run the tests yet.
  314. int argc = 1;
  315. const char* argv[] = {[masterTestCaseName UTF8String]};
  316. testing::InitGoogleTest(&argc, const_cast<char**>(argv));
  317. // Convert XCTest's testToRun set to the equivalent --gtest_filter flag.
  318. //
  319. // Note that we only set forceAllTests to true if the user specifically
  320. // focused on GoogleTests. This prevents XCTest double-counting test cases
  321. // (and failures) when a user asks for all tests.
  322. NSSet<NSString*>* allTests = [NSSet setWithObject:masterTestCaseName];
  323. NSSet<NSString*>* testsToRun = LoadXCTestConfigurationTestsToRun();
  324. if (testsToRun) {
  325. if ([allTests isEqual:testsToRun]) {
  326. NSLog(@"Forcing all tests to run");
  327. forceAllTests = true;
  328. } else {
  329. NSString* filters = CreateTestFiltersFromTestsToRun(testsToRun);
  330. NSLog(@"Using --gtest_filter=%@", filters);
  331. if (filters) {
  332. testing::GTEST_FLAG(filter) = [filters UTF8String];
  333. }
  334. }
  335. }
  336. // Create XCTestCases and populate the testInfosByKey map
  337. const testing::UnitTest* master = testing::UnitTest::GetInstance();
  338. NSMutableDictionary<NSString*, NSValue*>* infoMap =
  339. [NSMutableDictionary dictionaryWithCapacity:master->total_test_count()];
  340. int testCases = master->total_test_case_count();
  341. for (int i = 0; i < testCases; i++) {
  342. const testing::TestCase* testCase = master->GetTestCase(i);
  343. CreateXCTestCaseClass(testCase, infoMap);
  344. }
  345. testInfosByKey = infoMap;
  346. }
  347. void RunGoogleTestTests() {
  348. static bool firstRun = true;
  349. if (firstRun) {
  350. firstRun = false;
  351. int result = RUN_ALL_TESTS();
  352. // RUN_ALL_TESTS by default doesn't want you to ignore its result, but it's
  353. // safe here. Test failures are already logged by GoogleTest itself (and
  354. // then again by XCTest). Test failures are reported via
  355. // -recordFailureWithDescription:inFile:atLine:expected: which then causes
  356. // XCTest itself to fail the run.
  357. (void)result;
  358. }
  359. }
  360. } // namespace
  361. @implementation GoogleTests
  362. + (XCTestSuite*)defaultTestSuite {
  363. // Only return all tests beyond GoogleTests if the user is focusing on
  364. // GoogleTests.
  365. if (forceAllTests) {
  366. return CreateAllTestsTestSuite();
  367. } else {
  368. // just run the tests that are a part of this class
  369. return [XCTestSuite testSuiteForTestCaseClass:[self class]];
  370. }
  371. }
  372. - (void)testGoogleTestsActuallyRun {
  373. // This whole mechanism is sufficiently tricky that we should verify that the
  374. // build actually plumbed this together correctly.
  375. const testing::UnitTest* master = testing::UnitTest::GetInstance();
  376. XCTAssertGreaterThan(master->total_test_case_count(), 0);
  377. }
  378. @end
  379. /**
  380. * This class is registered as the NSPrincipalClass in the Firestore_Tests
  381. * bundle's Info.plist. XCTest instantiates this class to perform one-time setup
  382. * for the test bundle, as documented here:
  383. *
  384. * https://developer.apple.com/documentation/xctest/xctestobservationcenter
  385. */
  386. @interface FSTGoogleTestsPrincipal : NSObject
  387. @end
  388. @implementation FSTGoogleTestsPrincipal
  389. - (instancetype)init {
  390. self = [super init];
  391. CreateGoogleTestTests();
  392. return self;
  393. }
  394. @end