aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Example
diff options
context:
space:
mode:
authorGravatar Gil <mcg@google.com>2017-11-08 14:46:50 -0800
committerGravatar GitHub <noreply@github.com>2017-11-08 14:46:50 -0800
commitc4c7777e75bfc84ba144f13014f312230a0cc7ed (patch)
tree958b8279185d6b4e53282dbfff73d25b923cf01f /Firestore/Example
parent07595ce66bbd90be37940facd8601b3d94eec837 (diff)
Run GoogleTest-based C++ tests in Xcode (#420)
* Use GoogleTest as a dependency of Firestore_Tests * Remove top-level leveldb-library from HEADER_SEARCH_PATHS * Add string_util_test to the project and get it to build * Implement FSTGoogleTestTests, a bridge between GoogleTest and XCTest
Diffstat (limited to 'Firestore/Example')
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj28
-rw-r--r--Firestore/Example/Podfile1
-rw-r--r--Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm358
-rw-r--r--Firestore/Example/Tests/GoogleTest/GoogleTest.podspec84
4 files changed, 469 insertions, 2 deletions
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
index 0197deb..50da99b 100644
--- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj
+++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
@@ -24,6 +24,8 @@
/* Begin PBXBuildFile section */
3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
+ 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAA1FAA0C320085E60A /* string_util_test.cc */; };
+ 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; };
54DA12A61F315EE100DD57A1 /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; };
54DA12A71F315EE100DD57A1 /* existence_filter_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */; };
54DA12A81F315EE100DD57A1 /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; };
@@ -181,6 +183,8 @@
3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = "<group>"; };
42491D7DC8C8CD245CC22B93 /* Pods-SwiftBuildTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBuildTest.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest.debug.xcconfig"; sourceTree = "<group>"; };
4EBC5F5ABE1FD097EFE5E224 /* Pods-Firestore_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.release.xcconfig"; sourceTree = "<group>"; };
+ 54764FAA1FAA0C320085E60A /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_util_test.cc; path = ../../Port/string_util_test.cc; sourceTree = "<group>"; };
+ 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FSTGoogleTestTests.mm; path = GoogleTest/FSTGoogleTestTests.mm; sourceTree = "<group>"; };
54DA129C1F315EE100DD57A1 /* collection_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = collection_spec_test.json; sourceTree = "<group>"; };
54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = existence_filter_spec_test.json; sourceTree = "<group>"; };
54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = limbo_spec_test.json; sourceTree = "<group>"; };
@@ -360,6 +364,23 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 54764FAC1FAA0C390085E60A /* GoogleTests */ = {
+ isa = PBXGroup;
+ children = (
+ 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */,
+ 54764FAD1FAA0C650085E60A /* Port */,
+ );
+ name = GoogleTests;
+ sourceTree = "<group>";
+ };
+ 54764FAD1FAA0C650085E60A /* Port */ = {
+ isa = PBXGroup;
+ children = (
+ 54764FAA1FAA0C320085E60A /* string_util_test.cc */,
+ );
+ name = Port;
+ sourceTree = "<group>";
+ };
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
@@ -430,6 +451,7 @@
children = (
DE51B1831F0D48AC0013853F /* API */,
DE51B1A81F0D48AC0013853F /* Core */,
+ 54764FAC1FAA0C390085E60A /* GoogleTests */,
DE2EF06E1F3D07D7003D0CDC /* Immutable */,
DE51B1BB1F0D48AC0013853F /* Integration */,
DE51B1621F0D48AC0013853F /* Local */,
@@ -1012,11 +1034,13 @@
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework",
"${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1159,6 +1183,7 @@
DE51B1F11F0D49140013853F /* FSTMutationTests.m in Sources */,
DE51B1FB1F0D492C0013853F /* FSTMemorySpecTests.m in Sources */,
DE51B1DB1F0D490D0013853F /* FSTLevelDBQueryCacheTests.m in Sources */,
+ 54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */,
54E9282C1F339CAD00C1953E /* XCTestCase+Await.m in Sources */,
DE51B1DF1F0D490D0013853F /* FSTMemoryMutationQueueTests.m in Sources */,
DE51B1F31F0D491B0013853F /* FSTDatastoreTests.m in Sources */,
@@ -1166,6 +1191,7 @@
DE2EF0871F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m in Sources */,
DE51B1E01F0D490D0013853F /* FSTMemoryQueryCacheTests.m in Sources */,
DE51B1E91F0D490D0013853F /* FSTLevelDBMutationQueueTests.mm in Sources */,
+ 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */,
DE51B1E61F0D490D0013853F /* FSTRemoteDocumentCacheTests.m in Sources */,
DE51B1D91F0D490D0013853F /* FSTEagerGarbageCollectorTests.m in Sources */,
DE51B1E21F0D490D0013853F /* FSTMutationQueueTests.m in Sources */,
@@ -1359,7 +1385,6 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/Firebase/Firebase/Firebase\"",
- "\"${PODS_ROOT}/leveldb-library/\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
);
INFOPLIST_FILE = "Firestore/Firestore-Info.plist";
@@ -1380,7 +1405,6 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"\"${PODS_ROOT}/Firebase/Firebase/Firebase\"",
- "\"${PODS_ROOT}/leveldb-library/\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
);
INFOPLIST_FILE = "Firestore/Firestore-Info.plist";
diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile
index 4008af7..c79495d 100644
--- a/Firestore/Example/Podfile
+++ b/Firestore/Example/Podfile
@@ -8,6 +8,7 @@ target 'Firestore_Example' do
target 'Firestore_Tests' do
inherit! :search_paths
pod 'OCMock'
+ pod 'GoogleTest', :podspec => 'Tests/GoogleTest/GoogleTest.podspec'
pod 'leveldb-library'
end
diff --git a/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm b/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm
new file mode 100644
index 0000000..731e698
--- /dev/null
+++ b/Firestore/Example/Tests/GoogleTest/FSTGoogleTestTests.mm
@@ -0,0 +1,358 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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.
+ */
+
+#import <XCTest/XCTest.h>
+#import <objc/runtime.h>
+
+#include "Util/FSTAssert.h"
+#include "gtest/gtest.h"
+
+/**
+ * An XCTest test case that finds C++ test cases written in the GoogleTest framework, runs them, and
+ * reports the results back to Xcode. This allows tests written in C++ that don't rely on XCTest to
+ * coexist in this project.
+ *
+ * As an extra feature, you can run all C++ tests by focusing on the GoogleTests class.
+ *
+ * Each GoogleTest TestCase is mapped to a dynamically generated XCTestCase class. Each GoogleTest
+ * TEST() is mapped to a test method on that XCTestCase.
+ */
+@interface GoogleTests : XCTestCase
+@end
+
+namespace {
+
+// A testing::TestCase named "Foo" corresponds to an XCTestCase named "FooTests".
+NSString *const kTestCaseSuffix = @"Tests";
+
+// A testing::TestInfo named "Foo" corresponds to test method named "testFoo".
+NSString *const kTestMethodPrefix = @"test";
+
+// A map of keys created by TestInfoKey to the corresponding testing::TestInfo (wrapped in an
+// NSValue). The generated XCTestCase classes are discovered and instantiated by XCTest so this is
+// the only means of plumbing per-test-method state into these methods.
+NSDictionary<NSString *, NSValue *> *testInfosByKey;
+
+// If the user focuses on GoogleTests itself, this means force all C++ tests to run.
+BOOL forceAllTests = NO;
+
+/**
+ * Loads this XCTest runner's configuration file and figures out which tests to run based on the
+ * contents of that configuration file.
+ *
+ * @return the set of tests to run, or nil if the user asked for all tests or if there's any
+ * problem loading or parsing the configuration.
+ */
+NSSet<NSString *> *_Nullable LoadXCTestConfigurationTestsToRun() {
+ // Xcode invokes the test runner with an XCTestConfigurationFilePath environment variable set to
+ // the path of a configuration file containing, among other things, the set of tests to run. The
+ // configuration file deserializes to a non-public XCTestConfiguration class.
+ //
+ // This loads that file and then reflectively pulls out the testsToRun set. Just in case any of
+ // these private details should change in the future and something should fail here, the mechanism
+ // complains but fails open. This way the worst that can happen is that users end up running more
+ // tests than they intend, but we never accidentally show a green run that wasn't.
+ static NSString *const configEnvVar = @"XCTestConfigurationFilePath";
+
+ NSDictionary<NSString *, NSString *> *env = [[NSProcessInfo processInfo] environment];
+ NSString *filePath = [env objectForKey:configEnvVar];
+ if (!filePath) {
+ NSLog(@"Missing %@ environment variable; assuming all tests", configEnvVar);
+ return nil;
+ }
+
+ id config = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
+ if (!config) {
+ NSLog(@"Failed to load any configuaration from %@=%@", configEnvVar, filePath);
+ return nil;
+ }
+
+ SEL testsToRunSelector = NSSelectorFromString(@"testsToRun");
+ if (![config respondsToSelector:testsToRunSelector]) {
+ NSLog(@"Invalid configuaration from %@=%@: missing testsToRun", configEnvVar, filePath);
+ return nil;
+ }
+
+ // Invoke the testsToRun selector safely. This indirection is required because just calling
+ // -performSelector: fails to properly retain the NSSet under ARC.
+ typedef NSSet<NSString *> *(*TestsToRunFunction)(id, SEL);
+ IMP testsToRunMethod = [config methodForSelector:testsToRunSelector];
+ auto testsToRunFunction = reinterpret_cast<TestsToRunFunction>(testsToRunMethod);
+ return testsToRunFunction(config, testsToRunSelector);
+}
+
+/**
+ * Creates a GoogleTest filter specification, suitable for passing to the --gtest_filter flag,
+ * that limits GoogleTest to running the same set of tests that Xcode requested.
+ *
+ * Each member of the testsToRun set is mapped as follows:
+ *
+ * * Bare class: "ClassTests" => "Class.*"
+ * * Class and method: "ClassTests/testMethod" => "Class.Method"
+ *
+ * These members are then joined with a ":" as googletest requires.
+ *
+ * @see https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md
+ */
+NSString *CreateTestFiltersFromTestsToRun(NSSet<NSString *> *testsToRun) {
+ NSMutableString *result = [[NSMutableString alloc] init];
+ for (NSString *spec in testsToRun) {
+ NSArray<NSString *> *parts = [spec componentsSeparatedByString:@"/"];
+
+ NSString *gtestCaseName = nil;
+ if (parts.count > 0) {
+ NSString *className = parts[0];
+ if ([className hasSuffix:kTestCaseSuffix]) {
+ gtestCaseName = [className substringToIndex:className.length - kTestCaseSuffix.length];
+ }
+ }
+
+ NSString *gtestMethodName = nil;
+ if (parts.count > 1) {
+ NSString *methodName = parts[1];
+ if ([methodName hasPrefix:kTestMethodPrefix]) {
+ gtestMethodName = [methodName substringFromIndex:kTestMethodPrefix.length];
+ }
+ }
+
+ if (gtestCaseName) {
+ if (result.length > 0) {
+ [result appendString:@":"];
+ }
+ [result appendString:gtestCaseName];
+ [result appendString:@"."];
+ [result appendString:(gtestMethodName ? gtestMethodName : @"*")];
+ }
+ }
+
+ return result;
+}
+
+/** Returns the name of the selector for the test method representing this specific test. */
+NSString *SelectorNameForTestInfo(const testing::TestInfo *testInfo) {
+ return [NSString stringWithFormat:@"%@%s", kTestMethodPrefix, testInfo->name()];
+}
+
+/** Returns the name of the class representing the given testing::TestCase. */
+NSString *ClassNameForTestCase(const testing::TestCase *testCase) {
+ return [NSString stringWithFormat:@"%s%@", testCase->name(), kTestCaseSuffix];
+}
+
+/**
+ * Returns a key name for the testInfosByKey dictionary. Each (class, selector) pair corresponds
+ * to a unique GoogleTest result.
+ */
+NSString *TestInfoKey(Class testClass, SEL testSelector) {
+ return [NSString
+ stringWithFormat:@"%@.%@", NSStringFromClass(testClass), NSStringFromSelector(testSelector)];
+}
+
+/**
+ * Looks up the testing::TestInfo for this test method and reports on the outcome to XCTest, as if
+ * the test actually ran in this method.
+ *
+ * Note: this function is the implementation for each generated test method. It shouldn't be used
+ * directly. The parameter names of self and _cmd match up with the implicit parameters passed to
+ * any Objective-C method. Naming them this way here allows XCTAssert and friends to work.
+ */
+void ReportTestResult(XCTestCase *self, SEL _cmd) {
+ NSString *testInfoKey = TestInfoKey([self class], _cmd);
+ NSValue *holder = testInfosByKey[testInfoKey];
+ auto testInfo = static_cast<const testing::TestInfo *>(holder.pointerValue);
+ if (!testInfo) {
+ return;
+ }
+
+ if (!testInfo->should_run()) {
+ // Test was filtered out by gunit; nothing to report.
+ return;
+ }
+
+ const testing::TestResult *result = testInfo->result();
+ if (result->Passed()) {
+ // Let XCode know that the test ran and succeeded.
+ XCTAssertTrue(true);
+ return;
+ }
+
+ // Test failed :-(. Record the failure such that XCode will navigate directly to the file:line.
+ int parts = result->total_part_count();
+ for (int i = 0; i < parts; i++) {
+ const testing::TestPartResult &part = result->GetTestPartResult(i);
+ [self recordFailureWithDescription:@(part.message())
+ inFile:@(part.file_name() ? part.file_name() : "")
+ atLine:(part.line_number() > 0 ? part.line_number() : 0)
+ expected:YES];
+ }
+}
+
+/**
+ * Generates a new subclass of XCTestCase for the given GoogleTest TestCase. Each TestInfo (which
+ * represents an indivudal test method execution) is translated into a method on the test case.
+ *
+ * @param The testing::TestCase of interest to translate.
+ * @param A map of TestInfoKeys to testing::TestInfos, populated by this method.
+ *
+ * @return A new Class that's a subclass of XCTestCase, that's been registered with the Objective-C
+ * runtime.
+ */
+Class CreateXCTestCaseClass(const testing::TestCase *testCase,
+ NSMutableDictionary<NSString *, NSValue *> *infoMap) {
+ NSString *testCaseName = ClassNameForTestCase(testCase);
+ Class testClass = objc_allocateClassPair([XCTestCase class], [testCaseName UTF8String], 0);
+
+ // Create a method for each TestInfo.
+ int testInfos = testCase->total_test_count();
+ for (int j = 0; j < testInfos; j++) {
+ const testing::TestInfo *testInfo = testCase->GetTestInfo(j);
+
+ NSString *selectorName = SelectorNameForTestInfo(testInfo);
+ SEL selector = sel_registerName([selectorName UTF8String]);
+
+ // Use the ReportTestResult function as the method implementation. The v@: indicates it is a
+ // void objective-C method; this must continue to match the signature of ReportTestResult.
+ IMP method = reinterpret_cast<IMP>(ReportTestResult);
+ class_addMethod(testClass, selector, method, "v@:");
+
+ NSString *infoKey = TestInfoKey(testClass, selector);
+ NSValue *holder = [NSValue valueWithPointer:testInfo];
+ infoMap[infoKey] = holder;
+ }
+ objc_registerClassPair(testClass);
+
+ return testClass;
+}
+
+/**
+ * Creates a test suite containing all C++ tests, used when the user starts the GoogleTests class.
+ *
+ * Note: normally XCTest finds all the XCTestCase classes that are registered with the run time
+ * and asks them to create suites for themselves. When a user focuses on the GoogleTests class,
+ * XCTest no longer does this so we have to force XCTest to see more tests than it would normally
+ * look at so that the indicators in the test navigator update properly.
+ */
+XCTestSuite *CreateAllTestsTestSuite() {
+ XCTestSuite *allTestsSuite = [[XCTestSuite alloc] initWithName:@"All GoogleTest Tests"];
+ [allTestsSuite addTest:[XCTestSuite testSuiteForTestCaseClass:[GoogleTests class]]];
+
+ const testing::UnitTest *master = testing::UnitTest::GetInstance();
+
+ int testCases = master->total_test_case_count();
+ for (int i = 0; i < testCases; i++) {
+ const testing::TestCase *testCase = master->GetTestCase(i);
+ NSString *testCaseName = ClassNameForTestCase(testCase);
+ Class testClass = objc_getClass([testCaseName UTF8String]);
+ [allTestsSuite addTest:[XCTestSuite testSuiteForTestCaseClass:testClass]];
+ }
+
+ return allTestsSuite;
+}
+
+/**
+ * A pseudo-constructor that dynamically generates all the XCTestCase subclasses.
+ *
+ * For background, the Objective-C runtime and XCTest initialize things in the following order:
+ *
+ * 1. Objective-C calls +load on every class in the bundle.
+ * 2. C++ constructors are called.
+ * 3. Any NSPrincipalClass in the test bundle's Info.plist is instantiated.
+ * 4. Objective-C calls +initialize on every class that's referenced, lazily.
+ * 5. XCTest calls +defaultTestSuite on every class that's a subclass of XCTestCase that matches
+ * its notion of which tests to run.
+ *
+ * All stages only run on all XCTestCase classes if the user runs all tests. Otherwise:
+ * * When a user is focusing on a test case XCTest only calls +defaultTestSuite (and triggers
+ * +initialize) on that specific test.
+ * * When a user is focusing on a test method XCTest does not call +defaultTestSuite at all
+ * (+initialize still runs).
+ *
+ * This means that +initialize or +defaultTestSuite on some fixed class like GoogleTests can't be
+ * used to bootstrap the generated classes because these steps can be skipped if the user focuses
+ * on the wrong thing. NSPrincipalClass would work, but requires frobbing the Info.plist, which is
+ * a manual step in project configuration which is error prone.
+ *
+ * Meanwhile even though __attribute__((constructor)) is a GCC and Clang extension those are the
+ * only compilers we care about for Objective-C so it's not that bad.
+ */
+__attribute__((constructor)) void RegisterGoogleTestTests() {
+ NSString *masterTestCaseName = NSStringFromClass([GoogleTests class]);
+
+ // Initialize GoogleTest but don't run the tests yet.
+ int argc = 1;
+ const char *argv[] = {[masterTestCaseName UTF8String]};
+ testing::InitGoogleTest(&argc, const_cast<char **>(argv));
+
+ // Convert XCTest's testToRun set to the equivalent --gtest_filter flag.
+ //
+ // Note that we only set forceAllTests to YES if the user specifically focused on GoogleTests.
+ // This prevents XCTest double-counting test cases (and failures) when a user asks for all tests.
+ NSSet<NSString *> *allTests = [NSSet setWithObject:masterTestCaseName];
+ NSSet<NSString *> *testsToRun = LoadXCTestConfigurationTestsToRun();
+ if (testsToRun) {
+ if ([allTests isEqual:testsToRun]) {
+ forceAllTests = YES;
+ } else {
+ NSString *filters = CreateTestFiltersFromTestsToRun(testsToRun);
+ if (filters) {
+ testing::GTEST_FLAG(filter) = [filters UTF8String];
+ }
+ }
+ }
+
+ // Create XCTestCases and populate the testInfosByKey map
+ const testing::UnitTest *master = testing::UnitTest::GetInstance();
+ NSMutableDictionary<NSString *, NSValue *> *infoMap =
+ [NSMutableDictionary dictionaryWithCapacity:master->total_test_count()];
+
+ int testCases = master->total_test_case_count();
+ for (int i = 0; i < testCases; i++) {
+ const testing::TestCase *testCase = master->GetTestCase(i);
+ CreateXCTestCaseClass(testCase, infoMap);
+ }
+ testInfosByKey = infoMap;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-result"
+ // RUN_ALL_TESTS by default doesn't want you to ignore its result, but it's safe here. Test
+ // failures are already logged by GoogleTest itself (and then again by XCTest). Test failures are
+ // reported via -recordFailureWithDescription:inFile:atLine:expected: which then causes XCTest
+ // itself to fail the run.
+ RUN_ALL_TESTS();
+#pragma clang diagnostic pop
+}
+
+} // namespace
+
+@implementation GoogleTests
+
++ (XCTestSuite *)defaultTestSuite {
+ // Only return all tests beyond GoogleTests if the user is focusing on GoogleTests.
+ if (forceAllTests) {
+ return CreateAllTestsTestSuite();
+ } else {
+ // just run the tests that are a part of this class
+ return [XCTestSuite testSuiteForTestCaseClass:[self class]];
+ }
+}
+
+- (void)testGoogleTestsActuallyRun {
+ // This whole mechanism is sufficiently tricky that we should verify that the build actually
+ // plumbed this together correctly.
+ const testing::UnitTest *master = testing::UnitTest::GetInstance();
+ XCTAssertGreaterThan(master->total_test_case_count(), 0);
+}
+
+@end
diff --git a/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec b/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec
new file mode 100644
index 0000000..064fc59
--- /dev/null
+++ b/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec
@@ -0,0 +1,84 @@
+# Copyright 2017 Google
+#
+# 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.
+
+# A Private podspec for GoogleTest. Suitable only for use inside this source
+# tree.
+
+Pod::Spec.new do |s|
+ s.name = 'GoogleTest'
+ s.version = '1.8.0'
+ s.summary = 'Google Test'
+
+ s.description = <<-DESC
+Google's C++ test framework.
+ DESC
+
+ s.homepage = 'https://github.com/google/googletest/'
+ s.license = 'BSD'
+ s.authors = 'Google, Inc.'
+
+ s.source = {
+ :git => 'https://github.com/google/googletest.git',
+ :tag => 'release-' + s.version.to_s
+ }
+
+ s.ios.deployment_target = '8.0'
+ s.requires_arc = false
+
+ # Exclude include/gtest/internal/custom files from public headers. These
+ # files cause problems because they have the same basenames as other headers
+ # (e.g. gtest.h). We don't need them because they're effectively empty:
+ # they're compile-time hooks for third-party customization that we don't use.
+ s.public_header_files = [
+ 'googletest/include/gtest/*.h',
+ 'googletest/include/gtest/internal/*.h'
+ ]
+ s.header_mappings_dir = 'googletest/include'
+
+ # Internal headers accessed only by the implementation. These can't be
+ # mentioned in source_files because header_mappings_dir will complain about
+ # headers outside its directory.
+ s.preserve_paths = [
+ 'googletest/src/*.h',
+ ]
+
+ s.source_files = [
+ 'googletest/src/*.cc',
+ 'googletest/include/gtest/*.h',
+ 'googletest/include/gtest/internal/*.h'
+ ]
+
+ s.exclude_files = [
+ # A convenience wrapper for a simple command-line build. If included in
+ # this build, results in duplicate symbols
+ 'googletest/src/gtest-all.cc',
+ ]
+
+ s.library = 'c++'
+
+ # When building this pod there are headers in googletest/src.
+ s.pod_target_xcconfig = {
+ 'HEADER_SEARCH_PATHS' =>
+ '"${PODS_ROOT}/GoogleTest/googletest/include" "${PODS_ROOT}/GoogleTest/googletest"'
+ }
+
+ s.prepare_command = <<-'CMD'
+ # Remove includes of files in internal/custom
+ sed -i.bak -e '/include.*internal\/custom/ d' \
+ googletest/include/gtest/gtest-printers.h \
+ googletest/include/gtest/internal/gtest-port.h \
+ googletest/src/gtest-death-test.cc \
+ googletest/src/gtest.cc
+ CMD
+end