From 8d4831d5bf6d0c714d7b03b07d8b393063a40916 Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Tue, 12 Nov 2013 23:00:15 +0000 Subject: Add GoogleTestRunner to GTM. This allows you to easily mix and match SenTest with GoogleTest https://code.google.com/p/googletest/ which is nice when working with C++ code. Also adds GTMCodeCoverage which allows you to do code coverage with Xcode 5 and iOS7. DELTA=424 (424 added, 0 deleted, 0 changed) --- UnitTesting/GTMCodeCoverageApp.h | 33 ++++++ UnitTesting/GTMCodeCoverageApp.m | 56 ++++++++++ UnitTesting/GTMCodeCoverageTestsST.m | 69 +++++++++++++ UnitTesting/GTMCodeCoverageTestsXC.m | 72 +++++++++++++ UnitTesting/GTMGoogleTestRunner.mm | 194 +++++++++++++++++++++++++++++++++++ 5 files changed, 424 insertions(+) create mode 100644 UnitTesting/GTMCodeCoverageApp.h create mode 100644 UnitTesting/GTMCodeCoverageApp.m create mode 100644 UnitTesting/GTMCodeCoverageTestsST.m create mode 100644 UnitTesting/GTMCodeCoverageTestsXC.m create mode 100644 UnitTesting/GTMGoogleTestRunner.mm (limited to 'UnitTesting') diff --git a/UnitTesting/GTMCodeCoverageApp.h b/UnitTesting/GTMCodeCoverageApp.h new file mode 100644 index 0000000..32c92c6 --- /dev/null +++ b/UnitTesting/GTMCodeCoverageApp.h @@ -0,0 +1,33 @@ +// +// GTMCodeCovereageApp.h +// +// Copyright 2013 Google Inc. +// +// 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. +// + +// This code exists for doing code coverage with Xcode and iOS. +// Please read through https://code.google.com/p/coverstory/wiki/UsingCoverstory +// for details. + +#import + +// If you are using this with XCTest (as opposed to SenTestingKit) +// make sure to define GTM_USING_XCTEST. +#define GTMXCTestObserverClassKey @"XCTestObserverClass" +#define GTMXCTestLogClass @"XCTestLog" + +@interface UIApplication(GTMCodeCoverage) +- (void)gtm_gcov_flush; +@end + diff --git a/UnitTesting/GTMCodeCoverageApp.m b/UnitTesting/GTMCodeCoverageApp.m new file mode 100644 index 0000000..3bc7fae --- /dev/null +++ b/UnitTesting/GTMCodeCoverageApp.m @@ -0,0 +1,56 @@ +// +// GTMCodeCovereageApp.m +// +// Copyright 2013 Google Inc. +// +// 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. +// + +// This code exists for doing code coverage with Xcode and iOS. +// Please read through https://code.google.com/p/coverstory/wiki/UsingCoverstory +// for details. + +// This file should be conditionally compiled into your application bundle +// or test rig when you want to do code coverage. + +#import "GTMCodeCoverageApp.h" + +extern void __gcov_flush(); + +@implementation UIApplication(GTMCodeCoverage) + +- (void)gtm_gcov_flush { + __gcov_flush(); +} + +#if GTM_USING_XCTEST + ++ (void)load { + // Using defines and strings so that we don't have to link in + // XCTest here. + // Must set defaults here. If we set them in XCTest we are too late + // for the observer registration. + // See the documentation of XCTestObserverClassKey for why we set this key. + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSString *observers = [defaults stringForKey:GTMXCTestObserverClassKey]; + NSString *className = @"GTMCodeCoverageTests"; + if (observers == nil) { + observers = GTMXCTestLogClass; + } + observers = [NSString stringWithFormat:@"%@,%@", observers, className]; + [defaults setValue:observers forKey:GTMXCTestObserverClassKey]; +} + +#endif // GTM_USING_XCTEST + +@end diff --git a/UnitTesting/GTMCodeCoverageTestsST.m b/UnitTesting/GTMCodeCoverageTestsST.m new file mode 100644 index 0000000..6ace55f --- /dev/null +++ b/UnitTesting/GTMCodeCoverageTestsST.m @@ -0,0 +1,69 @@ +// +// GTMCodeCoverageTestsST.m +// +// Copyright 2013 Google Inc. +// +// 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. +// + +// This code exists for doing code coverage with Xcode and iOS. +// Please read through https://code.google.com/p/coverstory/wiki/UsingCoverstory +// for details. + +// This file should be conditionally compiled into your test bundle +// when you want to do code coverage and are using the SenTestingKit framework. + +#import +#import + +#import "GTMCodeCoverageApp.h" + +static int gSuiteCount = 0; + +@interface GTMCodeCoverageTests : NSObject +@end + +@implementation GTMCodeCoverageTests + ++ (void)load { + // Hook into the notifications so that we know when test suites start and + // stop. Once gSuiteCount is back to 0 we know that all of the suites + // have been run and we can collect our usage data. + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(suiteStarted:) + name:SenTestSuiteDidStartNotification + object:nil]; + [nc addObserver:self + selector:@selector(suiteStopped:) + name:SenTestSuiteDidStopNotification + object:nil]; +} + ++ (void)suiteStarted:(NSNotification *)notification { + #pragma unused (notification); + gSuiteCount += 1; +} + ++ (void)suiteStopped:(NSNotification *)notification { + #pragma unused (notification); + gSuiteCount -= 1; + if (gSuiteCount == 0) { + id application = [UIApplication sharedApplication]; + if ([application respondsToSelector:@selector(gtm_gcov_flush)]) { + [application performSelector:@selector(gtm_gcov_flush)]; + } + } +} + +@end diff --git a/UnitTesting/GTMCodeCoverageTestsXC.m b/UnitTesting/GTMCodeCoverageTestsXC.m new file mode 100644 index 0000000..39e4850 --- /dev/null +++ b/UnitTesting/GTMCodeCoverageTestsXC.m @@ -0,0 +1,72 @@ +// +// GTMCodeCovereageTestsXC.m +// +// Copyright 2013 Google Inc. +// +// 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. +// + +// This code exists for doing code coverage with Xcode and iOS. +// Please read through https://code.google.com/p/coverstory/wiki/UsingCoverstory +// for details. + +// This file should be conditionally compiled into your test bundle +// when you want to do code coverage and are using the XCTest framework. + +#import +#import + +#import "GTMCodeCoverageApp.h" + +@interface GTMCodeCoverageTests : XCTestObserver +@end + +@implementation GTMCodeCoverageTests + +- (void)stopObserving { + [super stopObserving]; + + // Call gtm_gcov_flush in the application executable unit. + id application = [UIApplication sharedApplication]; + if ([application respondsToSelector:@selector(gtm_gcov_flush)]) { + [application performSelector:@selector(gtm_gcov_flush)]; + } + + // Reset defaults back to what they should be. + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [defaults removeObjectForKey:XCTestObserverClassKey]; +} + ++ (void)load { + // Verify that all of our assumptions in [GTMCodeCoverageApp load] still stand + NSString *selfClass = NSStringFromClass(self); + BOOL mustExit = NO; + if (![selfClass isEqual:@"GTMCodeCoverageTests"]) { + NSLog(@"Can't change GTMCodeCoverageTests name to %@ without updating GTMCoverageApp", + selfClass); + mustExit = YES; + } + if (![GTMXCTestObserverClassKey isEqual:XCTestObserverClassKey]) { + NSLog(@"Apple has changed %@ to %@", GTMXCTestObserverClassKey, XCTestObserverClassKey); + mustExit = YES; + } + if (!NSClassFromString(GTMXCTestLogClass)) { + NSLog(@"Apple has gotten rid of the log class %@", GTMXCTestLogClass); + mustExit = YES; + } + if (mustExit) { + exit(1); + } +} + +@end diff --git a/UnitTesting/GTMGoogleTestRunner.mm b/UnitTesting/GTMGoogleTestRunner.mm new file mode 100644 index 0000000..5bc1d5f --- /dev/null +++ b/UnitTesting/GTMGoogleTestRunner.mm @@ -0,0 +1,194 @@ +// +// GTMGoogleTestRunner.mm +// +// Copyright 2013 Google Inc. +// +// 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. +// + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +// This is a SenTest/XCTest based unit test that will run all of the GoogleTest +// https://code.google.com/p/googletest/ +// based tests in the project, and will report results correctly via SenTest so +// that Xcode can pick them up in it's UI. + +// SenTest dynamically creates one SenTest per GoogleTest. +// GoogleTest is set up using a custom event listener (GoogleTestPrinter) +// which knows how to log GoogleTest test results in a manner that SenTest (and +// the Xcode IDE) understand. + +// Note that this does not able you to control individual tests from the Xcode +// UI. You can only turn on/off all of the C++ tests. It does however give +// you output that you can click on in the Xcode UI and immediately jump to a +// test failure. + +// This class is not compiled as part of the standard Google Toolbox For Mac +// project because of it's dependency on https://code.google.com/p/googletest/ + +// To use this: +// - If you are using XCTest (vs SenTest) make sure to define GTM_USING_XCTEST +// in the settings for your testing bundle. +// - Add GTMGoogleTestRunner to your test bundle sources. +// - Add gtest-all.cc from gtest to your test bundle sources. +// - Write some C++ tests and add them to your test bundle sources. +// - Build and run tests. Your C++ tests should just execute. + +#if GTM_USING_XCTEST +#import +#define SenTestCase XCTestCase +#define SenTestSuite XCTestSuite +#else // GTM_USING_XCTEST +#import +#endif // GTM_USING_XCTEST + +#include "third_party/gtest/include/gtest/gtest.h" + +using ::testing::EmptyTestEventListener; +using ::testing::TestCase; +using ::testing::TestEventListeners; +using ::testing::TestInfo; +using ::testing::TestPartResult; +using ::testing::TestResult; +using ::testing::UnitTest; + +namespace { + +// A gtest printer that takes care of reporting gtest results via the +// SenTest interface. Note that a test suite in SenTest == a test case in gtest +// and a test case in SenTest == a test in gtest. +// This will handle fatal and non-fatal gtests properly. +class GoogleTestPrinter : public EmptyTestEventListener { + public: + GoogleTestPrinter(SenTestCase *test_case) : test_case_(test_case) {} + + virtual ~GoogleTestPrinter() {} + + virtual void OnTestPartResult(const TestPartResult &test_part_result) { + if (!test_part_result.passed()) { + NSString *file = @(test_part_result.file_name()); + int line = test_part_result.line_number(); + NSString *summary = @(test_part_result.summary()); + + // gtest likes to give multi-line summaries. These don't look good in + // the Xcode UI, so we clean them up. + NSString *oneLineSummary = + [summary stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; +#if GTM_USING_XCTEST + BOOL expected = test_part_result.nonfatally_failed(); + [test_case_ recordFailureWithDescription:oneLineSummary + inFile:file + atLine:line + expected:expected]; +#else // GTM_USING_XCTEST + NSException *exception = + [NSException failureInFile:file + atLine:line + withDescription:@"%@", oneLineSummary]; + + // failWithException: will log appropriately. + [test_case_ failWithException:exception]; +#endif // GTM_USING_XCTEST + } + } + + private: + SenTestCase *test_case_; +}; + +} // namespace + +// GTMGoogleTestRunner is a GTMTestCase that makes a sub test suite populated +// with all of the GoogleTest unit tests. +@interface GTMGoogleTestRunner : SenTestCase { + NSString *testName_; +} + +// The name for a test is the GoogleTest name which is "TestCase.Test" +- (id)initWithName:(NSString *)testName; +@end + +@implementation GTMGoogleTestRunner + ++ (id)defaultTestSuite { + int argc = 0; + char *argv = NULL; + + // Initialize GoogleTest with no values. + testing::InitGoogleTest(&argc, &argv); + SenTestSuite *result = + [[SenTestSuite alloc] initWithName:NSStringFromClass(self)]; + UnitTest *test = UnitTest::GetInstance(); + + // Walk the GoogleTest tests, adding sub tests and sub suites as appropriate. + int total_test_case_count = test->total_test_case_count(); + for (int i = 0; i < total_test_case_count; ++i) { + const TestCase *test_case = test->GetTestCase(i); + int total_test_count = test_case->total_test_count(); + SenTestSuite *subSuite = + [[SenTestSuite alloc] initWithName:@(test_case->name())]; + [result addTest:subSuite]; + for (int j = 0; j < total_test_count; ++j) { + const TestInfo *test_info = test_case->GetTestInfo(j); + NSString *testName = [NSString stringWithFormat:@"%s.%s", + test_case->name(), test_info->name()]; + SenTestCase *senTest = [[self alloc] initWithName:testName]; + [subSuite addTest:senTest]; + } + } + return result; +} + +- (id)initWithName:(NSString *)testName { + if ((self = [super initWithSelector:@selector(runGoogleTest)])) { + testName_ = testName; + } + return self; +} + +- (NSString *)name { + // A SenTest name must be "-[foo bar]" or it won't be parsed properly. + NSRange dot = [testName_ rangeOfString:@"."]; + return [NSString stringWithFormat:@"-[%@ %@]", + [testName_ substringToIndex:dot.location], + [testName_ substringFromIndex:dot.location + 1]]; +} + +- (void)runGoogleTest { + // Initialize GoogleTest with no values. + int argc = 0; + char *argv = NULL; + testing::InitGoogleTest(&argc, &argv); + + // Gets hold of the event listener list. + TestEventListeners& listeners = UnitTest::GetInstance()->listeners(); + + // Adds a listener to the end. Google Test takes the ownership. + listeners.Append(new GoogleTestPrinter(self)); + + // Remove the default printer. + delete listeners.Release(listeners.default_result_printer()); + + // Since there is no way of running a single GoogleTest directly, we use the + // filter mechanism in GoogleTest to simulate it for us. + ::testing::GTEST_FLAG(filter) = [testName_ UTF8String]; + + // Intentionally ignore return value of RUN_ALL_TESTS. We will be printing + // the output appropriately, and there is no reason to mark this test as + // "failed" if RUN_ALL_TESTS returns non-zero. + (void)RUN_ALL_TESTS(); +} + +@end -- cgit v1.2.3