Skip to content

Commit

Permalink
Associate generated classes with the unit test bundle
Browse files Browse the repository at this point in the history
This is accomplished by implementing the undocumented +bundleForClass
for the generated classes. This isn't necessary for proper operation,
but it results in more expected output from XCTest reports. With this
change Google Test classes appear to be part of the unit test bundle
that GoogleTests.mm is compiled into, as they did under earlier
implementations. Without it tests are still executed and filtered
properly, but appear in the report as part of a separate bundle for the
application running tests. When run within Xcode this is a command line
app so the bundle is represented as the name of the directory where that
app is located ("Agents").
  • Loading branch information
mattstevens committed Jun 18, 2015
1 parent 0ae73f8 commit 1a5f833
Showing 1 changed file with 35 additions and 16 deletions.
51 changes: 35 additions & 16 deletions Bundle/GoogleTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,45 @@ void OnTestPartResult(const TestPartResult& test_part_result) {
XCTestCase *_testCase;
};

/**
* Registers an XCTestCase subclass for each Google Test case.
*
* Generating these classes allows Google Test cases to be represented as peers
* of standard XCTest suites and supports filtering of test runs to specific
* Google Test cases or individual tests via Xcode.
*/
@interface GoogleTestLoader : NSObject
@end

/**
* Base class for the generated classes for Google Test cases.
*/
@interface GoogleTestCase : XCTestCase
@end

@implementation GoogleTestCase

/**
* Associates generated Google Test classes with the test bundle.
*
* This affects how the generated test cases are represented in reports. By
* associating the generated classes with a test bundle the Google Test cases
* appear to be part of the same test bundle that this source file is compiled
* into. Without this association they appear to be part of a bundle
* representing the directory of an internal Xcode tool that runs the tests.
*/
+ (NSBundle *)bundleForClass {
return [NSBundle bundleForClass:[GoogleTestLoader class]];
}

/**
* Implementation of +[XCTestCase testInvocations] that returns an array of test
* invocations for each test method in the class.
*
* This differs from the standard implementation of testInvocations, which only
* adds methods with a prefix of "test".
*/
static NSArray *TestInvocations(id self, SEL _cmd) {
+ (NSArray *)testInvocations {
NSMutableArray *invocations = [NSMutableArray array];

unsigned int methodCount = 0;
Expand All @@ -103,6 +134,8 @@ void OnTestPartResult(const TestPartResult& test_part_result) {
return invocations;
}

@end

/**
* Runs a single test.
*/
Expand All @@ -125,16 +158,6 @@ static void RunTest(id self, SEL _cmd) {
XCTAssertEqual(totalTestsRun, 1, @"Expected to run a single test for filter \"%@\"", testFilter);
}

/**
* Registers an XCTestCase subclass for each Google Test case.
*
* Generating these classes allows Google Test cases to be represented as peers
* of standard XCTest suites and supports filtering of test runs to specific
* Google Test cases or individual tests via Xcode.
*/
@interface GoogleTestLoader : NSObject
@end

@implementation GoogleTestLoader

/**
Expand Down Expand Up @@ -200,7 +223,7 @@ + (void)registerTestClasses {
// a valid class name.
NSString *className = [GeneratedClassPrefix stringByAppendingString:[testCaseNameComponents componentsJoinedByString:@"_"]];

Class testClass = objc_allocateClassPair([XCTestCase class], [className UTF8String], 0);
Class testClass = objc_allocateClassPair([GoogleTestCase class], [className UTF8String], 0);
NSAssert1(testClass, @"Failed to register Google Test class \"%@\", this class may already exist. The value of GeneratedClassPrefix can be changed to avoid this.", className);
BOOL hasMethods = NO;

Expand Down Expand Up @@ -229,10 +252,6 @@ + (void)registerTestClasses {
}

if (hasMethods) {
Class testMetaClass = object_getClass(testClass);
SEL invocationsSelector = @selector(testInvocations);
Method invocationsMethod = class_getClassMethod(testClass, invocationsSelector);
class_addMethod(testMetaClass, invocationsSelector, (IMP)TestInvocations, method_getTypeEncoding(invocationsMethod));
objc_registerClassPair(testClass);
} else {
objc_disposeClassPair(testClass);
Expand Down

0 comments on commit 1a5f833

Please sign in to comment.