blob: 90ab919e1a529e4bc20c3978d38b0b4041eb77cd [file] [log] [blame]
//
// Copyright 2018 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.
//
#import "GTXPluginXCTestCase.h"
#import "GTXAssertions.h"
#import "GTXSwizzler.h"
#import "GTXTestEnvironment.h"
#import "GTXiLibCore.h"
#import <objc/runtime.h>
/**
Reference to XCTestCase class which will be dynamically set when needed, this allows for tests as
well as apps (which dont link to XCTest) use GTX.
*/
static Class gXCTestCaseClass;
@implementation GTXPluginXCTestCase
+ (void)installPlugin {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gXCTestCaseClass = NSClassFromString(@"XCTestCase");
NSAssert(gXCTestCaseClass, @"XCTestCase class was not found, either XCTest framework is not "
@"being linked or not loaded yet.");
[GTXSwizzler addInstanceMethod:@selector(gtx_tearDown) fromClass:self toClass:gXCTestCaseClass];
[GTXSwizzler addInstanceMethod:@selector(gtx_invokeTest)
fromClass:self
toClass:gXCTestCaseClass];
[GTXSwizzler swizzleInstanceMethod:@selector(invokeTest)
withMethod:@selector(gtx_invokeTest)
inClass:gXCTestCaseClass];
[GTXTestEnvironment setupEnvironment];
});
}
#pragma mark - XCTestCase Methods
- (void)tearDown {
NSAssert(NO, @"This method was added only for a reference to the selector but must never be "
@"invoked directly.");
}
- (void)invokeTest {
NSAssert(NO, @"This method was added only for a reference to the selector but must never be "
@"invoked directly.");
}
#pragma mark - Private
/**
@return Closest superclass to @c clazz that has -tearDown method.
*/
+ (Class)gtx_classWithInstanceTearDown:(Class)clazz {
while (clazz != gXCTestCaseClass &&
![clazz instanceMethodForSelector:@selector(tearDown)]) {
clazz = [clazz superclass];
}
return clazz;
}
/**
Wires up teardown in the given class so that -gtx_tearDown is invoked instead.
@param clazz The class to be modified.
*/
+ (void)gtx_wireUpTeardownInClass:(Class)clazz {
Class baseClass = gXCTestCaseClass;
// If gtx_tearDown does not exists in clazz, add it first.
if (baseClass != clazz) {
Method defaultGTXTearDown = class_getInstanceMethod(baseClass, @selector(gtx_tearDown));
struct objc_method_description *desc = method_getDescription(defaultGTXTearDown);
IMP gtxTearDownIMP = [baseClass instanceMethodForSelector:@selector(gtx_tearDown)];
class_addMethod(clazz, @selector(gtx_tearDown), gtxTearDownIMP, desc->types);
}
[GTXSwizzler swizzleInstanceMethod:@selector(tearDown)
withMethod:@selector(gtx_tearDown)
inClass:clazz];
}
/**
Restores @c clazz to its original state (opposite of +gtx_wireUpTeardownInClass).
*/
+ (void)gtx_unWireTeardownInClass:(Class)clazz {
// Swizzle again to restore tearDown/gtx_tearDown to their original IMPs.
[GTXSwizzler swizzleInstanceMethod:@selector(tearDown)
withMethod:@selector(gtx_tearDown)
inClass:clazz];
}
#pragma mark - Swizzled methods
/**
Swizzled method for -invokeTest method that posts notifications when test begins.
*/
- (void)gtx_invokeTest {
id actualSelf = self; // Note that self is of type XCTestCase
Class classWithTearDown =
[[GTXPluginXCTestCase class] gtx_classWithInstanceTearDown:[self class]];
[[GTXPluginXCTestCase class] gtx_wireUpTeardownInClass:classWithTearDown];
[[NSNotificationCenter defaultCenter] postNotificationName:gtxTestCaseDidBeginNotification
object:self
userInfo:@{gtxTestClassUserInfoKey:
[self class],
gtxTestInvocationUserInfoKey:
[actualSelf invocation]}];
@try {
typedef void (*InvokeTestMethodType)(id, SEL);
InvokeTestMethodType method =
(InvokeTestMethodType)[self methodForSelector:@selector(gtx_invokeTest)];
method(self, _cmd);
} @finally {
[[GTXPluginXCTestCase class] gtx_unWireTeardownInClass:classWithTearDown];
}
}
/**
Swizzled method for -tearDown method that posts notifications for teardown.
*/
- (void)gtx_tearDown {
id actualSelf = self; // Note that self is of type XCTestCase
[[NSNotificationCenter defaultCenter] postNotificationName:gtxTestCaseDidEndNotification
object:self
userInfo:@{gtxTestClassUserInfoKey:
[self class],
gtxTestInvocationUserInfoKey:
[actualSelf invocation]}];
typedef void (*TearDownMethodType)(id, SEL);
TearDownMethodType method =
(TearDownMethodType)[self methodForSelector:@selector(gtx_tearDown)];
method(self, _cmd);
}
@end