FSTGoogleTestTests.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /*
  2. * Copyright 2017 Google
  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. #include "Firestore/Source/Util/FSTAssert.h"
  20. /**
  21. * An XCTest test case that finds C++ test cases written in the GoogleTest framework, runs them, and
  22. * reports the results back to Xcode. This allows tests written in C++ that don't rely on XCTest to
  23. * coexist in this project.
  24. *
  25. * As an extra feature, you can run all C++ tests by focusing on the GoogleTests class.
  26. *
  27. * Each GoogleTest TestCase is mapped to a dynamically generated XCTestCase class. Each GoogleTest
  28. * 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 "FooTests".
  34. NSString *const kTestCaseSuffix = @"Tests";
  35. // A testing::TestInfo named "Foo" corresponds to test method named "testFoo".
  36. NSString *const kTestMethodPrefix = @"test";
  37. // A map of keys created by TestInfoKey to the corresponding testing::TestInfo (wrapped in an
  38. // NSValue). The generated XCTestCase classes are discovered and instantiated by XCTest so this is
  39. // the only means of plumbing per-test-method state into these methods.
  40. NSDictionary<NSString *, NSValue *> *testInfosByKey;
  41. // If the user focuses on GoogleTests itself, this means force all C++ tests to run.
  42. BOOL forceAllTests = NO;
  43. /**
  44. * Loads this XCTest runner's configuration file and figures out which tests to run based on the
  45. * contents of that configuration file.
  46. *
  47. * @return the set of tests to run, or nil if the user asked for all tests or if there's any
  48. * problem loading or parsing the configuration.
  49. */
  50. NSSet<NSString *> *_Nullable LoadXCTestConfigurationTestsToRun() {
  51. // Xcode invokes the test runner with an XCTestConfigurationFilePath environment variable set to
  52. // the path of a configuration file containing, among other things, the set of tests to run. The
  53. // configuration file deserializes to a non-public XCTestConfiguration class.
  54. //
  55. // This loads that file and then reflectively pulls out the testsToRun set. Just in case any of
  56. // these private details should change in the future and something should fail here, the mechanism
  57. // complains but fails open. This way the worst that can happen is that users end up running more
  58. // tests than they intend, but we never accidentally show a green run that wasn't.
  59. static NSString *const configEnvVar = @"XCTestConfigurationFilePath";
  60. NSDictionary<NSString *, NSString *> *env = [[NSProcessInfo processInfo] environment];
  61. NSString *filePath = [env objectForKey:configEnvVar];
  62. if (!filePath) {
  63. NSLog(@"Missing %@ environment variable; assuming all tests", configEnvVar);
  64. return nil;
  65. }
  66. id config = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
  67. if (!config) {
  68. NSLog(@"Failed to load any configuaration from %@=%@", configEnvVar, filePath);
  69. return nil;
  70. }
  71. SEL testsToRunSelector = NSSelectorFromString(@"testsToRun");
  72. if (![config respondsToSelector:testsToRunSelector]) {
  73. NSLog(@"Invalid configuaration from %@=%@: missing testsToRun", configEnvVar, filePath);
  74. return nil;
  75. }
  76. // Invoke the testsToRun selector safely. This indirection is required because just calling
  77. // -performSelector: fails to properly retain the NSSet under ARC.
  78. typedef NSSet<NSString *> *(*TestsToRunFunction)(id, SEL);
  79. IMP testsToRunMethod = [config methodForSelector:testsToRunSelector];
  80. auto testsToRunFunction = reinterpret_cast<TestsToRunFunction>(testsToRunMethod);
  81. return testsToRunFunction(config, testsToRunSelector);
  82. }
  83. /**
  84. * Creates a GoogleTest filter specification, suitable for passing to the --gtest_filter flag,
  85. * that limits GoogleTest to running the same set of tests that Xcode requested.
  86. *
  87. * Each member of the testsToRun set is mapped as follows:
  88. *
  89. * * Bare class: "ClassTests" => "Class.*"
  90. * * Class and method: "ClassTests/testMethod" => "Class.Method"
  91. *
  92. * These members are then joined with a ":" as googletest requires.
  93. *
  94. * @see https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md
  95. */
  96. NSString *CreateTestFiltersFromTestsToRun(NSSet<NSString *> *testsToRun) {
  97. NSMutableString *result = [[NSMutableString alloc] init];
  98. for (NSString *spec in testsToRun) {
  99. NSArray<NSString *> *parts = [spec componentsSeparatedByString:@"/"];
  100. NSString *gtestCaseName = nil;
  101. if (parts.count > 0) {
  102. NSString *className = parts[0];
  103. if ([className hasSuffix:kTestCaseSuffix]) {
  104. gtestCaseName = [className substringToIndex:className.length - kTestCaseSuffix.length];
  105. }
  106. }
  107. NSString *gtestMethodName = nil;
  108. if (parts.count > 1) {
  109. NSString *methodName = parts[1];
  110. if ([methodName hasPrefix:kTestMethodPrefix]) {
  111. gtestMethodName = [methodName substringFromIndex:kTestMethodPrefix.length];
  112. }
  113. }
  114. if (gtestCaseName) {
  115. if (result.length > 0) {
  116. [result appendString:@":"];
  117. }
  118. [result appendString:gtestCaseName];
  119. [result appendString:@"."];
  120. [result appendString:(gtestMethodName ? gtestMethodName : @"*")];
  121. }
  122. }
  123. return result;
  124. }
  125. /** Returns the name of the selector for the test method representing this specific test. */
  126. NSString *SelectorNameForTestInfo(const testing::TestInfo *testInfo) {
  127. return [NSString stringWithFormat:@"%@%s", kTestMethodPrefix, testInfo->name()];
  128. }
  129. /** Returns the name of the class representing the given testing::TestCase. */
  130. NSString *ClassNameForTestCase(const testing::TestCase *testCase) {
  131. return [NSString stringWithFormat:@"%s%@", testCase->name(), kTestCaseSuffix];
  132. }
  133. /**
  134. * Returns a key name for the testInfosByKey dictionary. Each (class, selector) pair corresponds
  135. * to a unique GoogleTest result.
  136. */
  137. NSString *TestInfoKey(Class testClass, SEL testSelector) {
  138. return [NSString
  139. stringWithFormat:@"%@.%@", NSStringFromClass(testClass), NSStringFromSelector(testSelector)];
  140. }
  141. /**
  142. * Looks up the testing::TestInfo for this test method and reports on the outcome to XCTest, as if
  143. * the test actually ran in this method.
  144. *
  145. * Note: this function is the implementation for each generated test method. It shouldn't be used
  146. * directly. The parameter names of self and _cmd match up with the implicit parameters passed to
  147. * any Objective-C method. Naming them this way here allows XCTAssert and friends to work.
  148. */
  149. void ReportTestResult(XCTestCase *self, SEL _cmd) {
  150. NSString *testInfoKey = TestInfoKey([self class], _cmd);
  151. NSValue *holder = testInfosByKey[testInfoKey];
  152. auto testInfo = static_cast<const testing::TestInfo *>(holder.pointerValue);
  153. if (!testInfo) {
  154. return;
  155. }
  156. if (!testInfo->should_run()) {
  157. // Test was filtered out by gunit; nothing to report.
  158. return;
  159. }
  160. const testing::TestResult *result = testInfo->result();
  161. if (result->Passed()) {
  162. // Let XCode know that the test ran and succeeded.
  163. XCTAssertTrue(true);
  164. return;
  165. }
  166. // Test failed :-(. Record the failure such that XCode will navigate directly to the file:line.
  167. int parts = result->total_part_count();
  168. for (int i = 0; i < parts; i++) {
  169. const testing::TestPartResult &part = result->GetTestPartResult(i);
  170. [self recordFailureWithDescription:@(part.message())
  171. inFile:@(part.file_name() ? part.file_name() : "")
  172. atLine:(part.line_number() > 0 ? part.line_number() : 0)
  173. expected:YES];
  174. }
  175. }
  176. /**
  177. * Generates a new subclass of XCTestCase for the given GoogleTest TestCase. Each TestInfo (which
  178. * represents an indivudal test method execution) is translated into a method on the test case.
  179. *
  180. * @param The testing::TestCase of interest to translate.
  181. * @param A map of TestInfoKeys to testing::TestInfos, populated by this method.
  182. *
  183. * @return A new Class that's a subclass of XCTestCase, that's been registered with the Objective-C
  184. * runtime.
  185. */
  186. Class CreateXCTestCaseClass(const testing::TestCase *testCase,
  187. NSMutableDictionary<NSString *, NSValue *> *infoMap) {
  188. NSString *testCaseName = ClassNameForTestCase(testCase);
  189. Class testClass = objc_allocateClassPair([XCTestCase class], [testCaseName UTF8String], 0);
  190. // Create a method for each TestInfo.
  191. int testInfos = testCase->total_test_count();
  192. for (int j = 0; j < testInfos; j++) {
  193. const testing::TestInfo *testInfo = testCase->GetTestInfo(j);
  194. NSString *selectorName = SelectorNameForTestInfo(testInfo);
  195. SEL selector = sel_registerName([selectorName UTF8String]);
  196. // Use the ReportTestResult function as the method implementation. The v@: indicates it is a
  197. // void objective-C method; this must continue to match the signature of ReportTestResult.
  198. IMP method = reinterpret_cast<IMP>(ReportTestResult);
  199. class_addMethod(testClass, selector, method, "v@:");
  200. NSString *infoKey = TestInfoKey(testClass, selector);
  201. NSValue *holder = [NSValue valueWithPointer:testInfo];
  202. infoMap[infoKey] = holder;
  203. }
  204. objc_registerClassPair(testClass);
  205. return testClass;
  206. }
  207. /**
  208. * Creates a test suite containing all C++ tests, used when the user starts the GoogleTests class.
  209. *
  210. * Note: normally XCTest finds all the XCTestCase classes that are registered with the run time
  211. * and asks them to create suites for themselves. When a user focuses on the GoogleTests class,
  212. * XCTest no longer does this so we have to force XCTest to see more tests than it would normally
  213. * look at so that the indicators in the test navigator update properly.
  214. */
  215. XCTestSuite *CreateAllTestsTestSuite() {
  216. XCTestSuite *allTestsSuite = [[XCTestSuite alloc] initWithName:@"All GoogleTest Tests"];
  217. [allTestsSuite addTest:[XCTestSuite testSuiteForTestCaseClass:[GoogleTests class]]];
  218. const testing::UnitTest *master = testing::UnitTest::GetInstance();
  219. int testCases = master->total_test_case_count();
  220. for (int i = 0; i < testCases; i++) {
  221. const testing::TestCase *testCase = master->GetTestCase(i);
  222. NSString *testCaseName = ClassNameForTestCase(testCase);
  223. Class testClass = objc_getClass([testCaseName UTF8String]);
  224. [allTestsSuite addTest:[XCTestSuite testSuiteForTestCaseClass:testClass]];
  225. }
  226. return allTestsSuite;
  227. }
  228. /**
  229. * Finds and runs googletest-based tests based on the XCTestConfiguration of the current test
  230. * invocation.
  231. */
  232. void RunGoogleTestTests() {
  233. NSString *masterTestCaseName = NSStringFromClass([GoogleTests class]);
  234. // Initialize GoogleTest but don't run the tests yet.
  235. int argc = 1;
  236. const char *argv[] = {[masterTestCaseName UTF8String]};
  237. testing::InitGoogleTest(&argc, const_cast<char **>(argv));
  238. // Convert XCTest's testToRun set to the equivalent --gtest_filter flag.
  239. //
  240. // Note that we only set forceAllTests to YES if the user specifically focused on GoogleTests.
  241. // This prevents XCTest double-counting test cases (and failures) when a user asks for all tests.
  242. NSSet<NSString *> *allTests = [NSSet setWithObject:masterTestCaseName];
  243. NSSet<NSString *> *testsToRun = LoadXCTestConfigurationTestsToRun();
  244. if (testsToRun) {
  245. if ([allTests isEqual:testsToRun]) {
  246. NSLog(@"Forcing all tests to run");
  247. forceAllTests = YES;
  248. } else {
  249. NSString *filters = CreateTestFiltersFromTestsToRun(testsToRun);
  250. NSLog(@"Using --gtest_filter=%@", filters);
  251. if (filters) {
  252. testing::GTEST_FLAG(filter) = [filters UTF8String];
  253. }
  254. }
  255. }
  256. // Create XCTestCases and populate the testInfosByKey map
  257. const testing::UnitTest *master = testing::UnitTest::GetInstance();
  258. NSMutableDictionary<NSString *, NSValue *> *infoMap =
  259. [NSMutableDictionary dictionaryWithCapacity:master->total_test_count()];
  260. int testCases = master->total_test_case_count();
  261. for (int i = 0; i < testCases; i++) {
  262. const testing::TestCase *testCase = master->GetTestCase(i);
  263. CreateXCTestCaseClass(testCase, infoMap);
  264. }
  265. testInfosByKey = infoMap;
  266. #pragma clang diagnostic push
  267. #pragma clang diagnostic ignored "-Wunused-result"
  268. // RUN_ALL_TESTS by default doesn't want you to ignore its result, but it's safe here. Test
  269. // failures are already logged by GoogleTest itself (and then again by XCTest). Test failures are
  270. // reported via -recordFailureWithDescription:inFile:atLine:expected: which then causes XCTest
  271. // itself to fail the run.
  272. RUN_ALL_TESTS();
  273. #pragma clang diagnostic pop
  274. }
  275. } // namespace
  276. @implementation GoogleTests
  277. + (XCTestSuite *)defaultTestSuite {
  278. // Only return all tests beyond GoogleTests if the user is focusing on GoogleTests.
  279. if (forceAllTests) {
  280. return CreateAllTestsTestSuite();
  281. } else {
  282. // just run the tests that are a part of this class
  283. return [XCTestSuite testSuiteForTestCaseClass:[self class]];
  284. }
  285. }
  286. - (void)testGoogleTestsActuallyRun {
  287. // This whole mechanism is sufficiently tricky that we should verify that the build actually
  288. // plumbed this together correctly.
  289. const testing::UnitTest *master = testing::UnitTest::GetInstance();
  290. XCTAssertGreaterThan(master->total_test_case_count(), 0);
  291. }
  292. @end
  293. /**
  294. * This class is registered as the NSPrincipalClass in the Firestore_Tests bundle's Info.plist.
  295. * XCTest instantiates this class to perform one-time setup for the test bundle, as documented
  296. * here:
  297. *
  298. * https://developer.apple.com/documentation/xctest/xctestobservationcenter
  299. */
  300. @interface FSTGoogleTestsPrincipal : NSObject
  301. @end
  302. @implementation FSTGoogleTestsPrincipal
  303. - (instancetype)init {
  304. self = [super init];
  305. RunGoogleTestTests();
  306. return self;
  307. }
  308. @end