blob: 42fafa39b4f68be1a7db7862bed9103842114668 [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 "GTXTestSuite.h"
#import <objc/runtime.h>
@implementation GTXTestSuite {
NSMutableArray<GTXTestCase *> *mutableTests;
}
- (instancetype)init {
self = [super init];
if (self) {
mutableTests = [[NSMutableArray alloc] init];
_tests = mutableTests;
}
return self;
}
+ (instancetype)suiteWithAllTestsFromAllClassesInheritedFromClass:(Class)baseClass {
// Get a list of all classes.
int totalClassesCount = objc_getClassList(NULL, 0);
Class *allClasses =
(__unsafe_unretained Class *)malloc(sizeof(Class) * (unsigned long)totalClassesCount);
totalClassesCount = objc_getClassList(allClasses, totalClassesCount);
NSMutableSet *applicableClasses = [[NSMutableSet alloc] initWithObjects:baseClass, nil];
for (NSInteger i = 0; i < totalClassesCount; i++) {
// Add all classes that inherit from the given baseClass
Class currentClass = allClasses[i];
Class currentSuperClass = currentClass;
do {
currentSuperClass = class_getSuperclass(currentSuperClass);
} while(currentSuperClass && currentSuperClass != baseClass);
if (currentSuperClass == baseClass) {
[applicableClasses addObject:currentClass];
}
}
// Create a suite with all the tests in applicableClasses set.
GTXTestSuite *suite = [[GTXTestSuite alloc] init];
for (Class currentClass in applicableClasses) {
for (NSString *methodName in [self _testMethodsInAllBaseClassesFromClass:currentClass]) {
[suite->mutableTests addObject:
[[GTXTestCase alloc] initWithTestClass:currentClass
selector:NSSelectorFromString(methodName)]];
}
}
free(allClasses);
return suite;
}
+ (instancetype)suiteWithAllTestsInClass:(Class)testClass {
GTXTestSuite *newSuite = [[GTXTestSuite alloc] init];
for (NSString *methodName in [self _testMethodsInClass:testClass]) {
GTXTestCase *testCase =
[[GTXTestCase alloc] initWithTestClass:testClass
selector:NSSelectorFromString(methodName)];
[newSuite->mutableTests addObject:testCase];
}
return newSuite;
}
+ (instancetype)suiteWithClass:(Class)testClass
andTests:(SEL)testMethod1, ... NS_REQUIRES_NIL_TERMINATION {
GTXTestSuite *newSuite = [[GTXTestSuite alloc] init];
va_list args;
va_start(args, testMethod1);
SEL testMethod = testMethod1;
while (testMethod) {
NSAssert([testClass instancesRespondToSelector:testMethod],
@"Method %@ does not exist in %@", NSStringFromSelector(testMethod), testClass);
GTXTestCase *testCase = [[GTXTestCase alloc] initWithTestClass:testClass selector:testMethod];
[newSuite->mutableTests addObject:testCase];
testMethod = va_arg(args, SEL);
}
va_end(args);
return newSuite;
}
+ (instancetype)suiteWithClass:(Class)testClass
exceptTests:(SEL)testMethod1, ... NS_REQUIRES_NIL_TERMINATION {
GTXTestSuite *suite = [GTXTestSuite suiteWithAllTestsInClass:testClass];
// Remove the specified tests from the suite.
va_list args;
va_start(args, testMethod1);
SEL testMethod = testMethod1;
while (testMethod) {
GTXTestCase *testCaseWithTestMethod;
for (GTXTestCase *testCase in suite.tests) {
if (testCase.testCaseSelector == testMethod) {
testCaseWithTestMethod = testCase;
break;
}
}
NSAssert(testCaseWithTestMethod, @"Test %@ doesnot exist in %@ class",
NSStringFromSelector(testMethod), testClass);
[suite->mutableTests removeObject:testCaseWithTestMethod];
testMethod = va_arg(args, SEL);
}
va_end(args);
return suite;
}
- (GTXTestSuite *)suiteByAppendingSuite:(GTXTestSuite *)suite {
GTXTestSuite *newSuite = [[GTXTestSuite alloc] init];
[newSuite->mutableTests addObjectsFromArray:self.tests];
[newSuite->mutableTests addObjectsFromArray:suite.tests];
return newSuite;
}
- (BOOL)hasTestCaseWithClass:(Class)testClass testMethod:(SEL)testMethod {
for (GTXTestCase *testCase in self.tests) {
if (testCase.testCaseSelector == testMethod && testCase.testClass == testClass) {
return YES;
}
}
return NO;
}
- (GTXTestSuite *)intersection:(GTXTestSuite *)suite {
GTXTestSuite *intersection = [[GTXTestSuite alloc] init];
for (GTXTestCase *first in self.tests) {
for (GTXTestCase *second in suite.tests) {
if (first.testClass == second.testClass &&
first.testCaseSelector == second.testCaseSelector) {
[intersection->mutableTests addObject:first];
}
}
}
return intersection;
}
- (NSString *)description {
NSMutableArray *items = [[NSMutableArray alloc] init];
for (GTXTestCase *testcase in self.tests) {
[items addObject:[NSString stringWithFormat:@"<Testcase Class=%@, Method=%@>",
testcase.testClass,
NSStringFromSelector(testcase.testCaseSelector)]];
}
return [NSString stringWithFormat:@"@[\n%@\n]", [items componentsJoinedByString:@"\n"]];
}
#pragma mark - Private
/**
@return An array of all test methods in the given class and all of its super classes.
*/
+ (NSArray *)_testMethodsInAllBaseClassesFromClass:(Class)inClass {
Class currentClass = inClass;
NSMutableSet *testMethods = [[NSMutableSet alloc] init];
while (currentClass) {
[testMethods addObjectsFromArray:[self _testMethodsInClass:currentClass]];
currentClass = [currentClass superclass];
}
return [testMethods allObjects];
}
/**
@return An array of all test methods only in the given class (methods defined in its super classes
are omitted).
*/
+ (NSArray *)_testMethodsInClass:(Class)inClass {
NSMutableSet *testMethods = [[NSMutableSet alloc] init];
unsigned int allMethodsCount = 0;
Method *allMethods = class_copyMethodList(inClass, &allMethodsCount);
for (unsigned int i = 0; i < allMethodsCount; i++) {
SEL methodSel = method_getName(allMethods[i]);
NSString *methodName = NSStringFromSelector(methodSel);
if ([methodName hasPrefix:@"test"]) {
[testMethods addObject:methodName];
}
}
free(allMethods);
return [testMethods allObjects];
}
@end