no message

This commit is contained in:
giuseppenuc committed 2015-12-31 16:06:21 +01:00
1 parent de5bd21a43
commit 568ad5c409
262 files changed
+23226 -78

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
@interface SPTCallSite : NSObject
@property (nonatomic, copy, readonly) NSString *file;
@property (nonatomic, readonly) NSUInteger line;
+ (instancetype)callSiteWithFile:(NSString *)file line:(NSUInteger)line;
- (instancetype)initWithFile:(NSString *)file line:(NSUInteger)line;
@end
+18
View File
@@ -0,0 +1,18 @@
#import "SPTCallSite.h"
@implementation SPTCallSite
+ (instancetype)callSiteWithFile:(NSString *)file line:(NSUInteger)line {
return [[self alloc] initWithFile:file line:line];
}
- (instancetype)initWithFile:(NSString *)file line:(NSUInteger)line {
self = [super init];
if (self) {
_file = file;
_line = line;
}
return self;
}
@end
+17
View File
@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import "SpectaTypes.h"
@interface SPTCompiledExample : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *testCaseName;
@property (nonatomic, copy) SPTSpecBlock block;
@property (nonatomic) BOOL pending;
@property (nonatomic, getter=isFocused) BOOL focused;
@property (nonatomic) SEL testMethodSelector;
- (id)initWithName:(NSString *)name testCaseName:(NSString *)testCaseName block:(SPTSpecBlock)block pending:(BOOL)pending focused:(BOOL)focused;
@end
+17
View File
@@ -0,0 +1,17 @@
#import "SPTCompiledExample.h"
@implementation SPTCompiledExample
- (id)initWithName:(NSString *)name testCaseName:(NSString *)testCaseName block:(SPTSpecBlock)block pending:(BOOL)pending focused:(BOOL)focused {
self = [super init];
if (self) {
self.name = name;
self.testCaseName = testCaseName;
self.block = block;
self.pending = pending;
self.focused = focused;
}
return self;
}
@end
+17
View File
@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import "SpectaTypes.h"
@class SPTCallSite;
@interface SPTExample : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) SPTCallSite *callSite;
@property (nonatomic, copy) SPTVoidBlock block;
@property (nonatomic) BOOL pending;
@property (nonatomic, getter=isFocused) BOOL focused;
- (id)initWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block;
@end
+17
View File
@@ -0,0 +1,17 @@
#import "SPTExample.h"
@implementation SPTExample
- (id)initWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block {
self = [super init];
if (self) {
self.name = name;
self.callSite = callSite;
self.block = block;
self.focused = focused;
self.pending = block == nil;
}
return self;
}
@end
+36
View File
@@ -0,0 +1,36 @@
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "SpectaTypes.h"
@class SPTExample;
@class SPTCallSite;
@interface SPTExampleGroup : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, weak) SPTExampleGroup *root;
@property (nonatomic, weak) SPTExampleGroup *parent;
@property (nonatomic, strong) NSMutableArray *children;
@property (nonatomic, strong) NSMutableArray *beforeAllArray;
@property (nonatomic, strong) NSMutableArray *afterAllArray;
@property (nonatomic, strong) NSMutableArray *beforeEachArray;
@property (nonatomic, strong) NSMutableArray *afterEachArray;
@property (nonatomic, strong) NSMutableDictionary *sharedExamples;
@property (nonatomic) unsigned int exampleCount;
@property (nonatomic) unsigned int ranExampleCount;
@property (nonatomic) unsigned int pendingExampleCount;
@property (nonatomic, getter=isFocused) BOOL focused;
- (id)initWithName:(NSString *)name parent:(SPTExampleGroup *)parent root:(SPTExampleGroup *)root;
- (SPTExampleGroup *)addExampleGroupWithName:(NSString *)name focused:(BOOL)focused;
- (SPTExample *)addExampleWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block;
- (void)addBeforeAllBlock:(SPTVoidBlock)block;
- (void)addAfterAllBlock:(SPTVoidBlock)block;
- (void)addBeforeEachBlock:(SPTVoidBlock)block;
- (void)addAfterEachBlock:(SPTVoidBlock)block;
- (NSArray *)compileExamplesWithStack:(NSArray *)stack;
@end
+335
View File
@@ -0,0 +1,335 @@
#import "SPTExampleGroup.h"
#import "SPTExample.h"
#import "SPTCompiledExample.h"
#import "SPTSpec.h"
#import "SpectaUtility.h"
#import "XCTest+Private.h"
#import "SPTGlobalBeforeAfterEach.h"
#import <libkern/OSAtomic.h>
#import <objc/runtime.h>
static NSArray *ClassesWithClassMethod(SEL classMethodSelector) {
NSMutableArray *classesWithClassMethod = [[NSMutableArray alloc] init];
int numberOfClasses = objc_getClassList(NULL, 0);
if (numberOfClasses > 0) {
Class *classes = (Class *)malloc(sizeof(Class) *numberOfClasses);
numberOfClasses = objc_getClassList(classes, numberOfClasses);
for(int classIndex = 0; classIndex < numberOfClasses; classIndex++) {
Class aClass = classes[classIndex];
if (class_conformsToProtocol(aClass, @protocol(SPTGlobalBeforeAfterEach))) {
Method globalMethod = class_getClassMethod(aClass, classMethodSelector);
if (globalMethod) {
[classesWithClassMethod addObject:aClass];
}
}
}
free(classes);
}
return classesWithClassMethod;
}
static void runExampleBlock(void (^block)(), NSString *name) {
if (!SPTIsBlock(block)) {
return;
}
((SPTVoidBlock)block)();
}
typedef NS_ENUM(NSInteger, SPTExampleGroupOrder) {
SPTExampleGroupOrderOutermostFirst = 1,
SPTExampleGroupOrderInnermostFirst
};
@interface SPTExampleGroup () <SPTGlobalBeforeAfterEach>
- (void)incrementExampleCount;
- (void)incrementPendingExampleCount;
- (void)resetRanExampleCountIfNeeded;
- (void)incrementRanExampleCount;
- (void)runBeforeHooks:(NSString *)compiledName;
- (void)runBeforeAllHooks:(NSString *)compiledName;
- (void)runBeforeEachHooks:(NSString *)compiledName;
- (void)runAfterHooks:(NSString *)compiledName;
- (void)runAfterEachHooks:(NSString *)compiledName;
- (void)runAfterAllHooks:(NSString *)compiledName;
@end
@implementation SPTExampleGroup
- (id)init {
self = [super init];
if (self) {
self.name = nil;
self.root = nil;
self.parent = nil;
self.children = [NSMutableArray array];
self.beforeAllArray = [NSMutableArray array];
self.afterAllArray = [NSMutableArray array];
self.beforeEachArray = [NSMutableArray array];
self.afterEachArray = [NSMutableArray array];
self.sharedExamples = [NSMutableDictionary dictionary];
self.exampleCount = 0;
self.pendingExampleCount = 0;
self.ranExampleCount = 0;
}
return self;
}
- (id)initWithName:(NSString *)name parent:(SPTExampleGroup *)parent root:(SPTExampleGroup *)root {
self = [self init];
if (self) {
self.name = name;
self.parent = parent;
self.root = root;
}
return self;
}
- (SPTExampleGroup *)addExampleGroupWithName:(NSString *)name focused:(BOOL)focused {
SPTExampleGroup *group = [[SPTExampleGroup alloc] initWithName:name parent:self root:self.root];
group.focused = focused;
[self.children addObject:group];
return group;
}
- (SPTExample *)addExampleWithName:(NSString *)name callSite:(SPTCallSite *)callSite focused:(BOOL)focused block:(SPTVoidBlock)block {
SPTExample *example;
@synchronized(self) {
example = [[SPTExample alloc] initWithName:name callSite:callSite focused:focused block:block];
[self.children addObject:example];
[self incrementExampleCount];
if (example.pending) {
[self incrementPendingExampleCount];
}
}
return example;
}
- (void)incrementExampleCount {
SPTExampleGroup *group = self;
while (group != nil) {
group.exampleCount ++;
group = group.parent;
}
}
- (void)incrementPendingExampleCount {
SPTExampleGroup *group = self;
while (group != nil) {
group.pendingExampleCount ++;
group = group.parent;
}
}
- (void)resetRanExampleCountIfNeeded {
SPTExampleGroup *group = self;
while (group != nil) {
if (group.ranExampleCount >= group.exampleCount) {
group.ranExampleCount = 0;
}
group = group.parent;
}
}
- (void)incrementRanExampleCount {
SPTExampleGroup *group = self;
while (group != nil) {
group.ranExampleCount ++;
group = group.parent;
}
}
- (void)addBeforeAllBlock:(SPTVoidBlock)block {
if (!block) return;
[self.beforeAllArray addObject:[block copy]];
}
- (void)addAfterAllBlock:(SPTVoidBlock)block {
if (!block) return;
[self.afterAllArray addObject:[block copy]];
}
- (void)addBeforeEachBlock:(SPTVoidBlock)block {
if (!block) return;
[self.beforeEachArray addObject:[block copy]];
}
- (void)addAfterEachBlock:(SPTVoidBlock)block {
if (!block) return;
[self.afterEachArray addObject:[block copy]];
}
- (void)runGlobalBeforeEachHooks:(NSString *)compiledName {
static NSArray *globalBeforeEachClasses;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
globalBeforeEachClasses = ClassesWithClassMethod(@selector(beforeEach));
});
for (Class class in globalBeforeEachClasses) {
[class beforeEach];
}
}
- (void)runGlobalAfterEachHooks:(NSString *)compiledName {
static NSArray *globalAfterEachClasses;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
globalAfterEachClasses = ClassesWithClassMethod(@selector(afterEach));
});
for (Class class in globalAfterEachClasses) {
[class afterEach];
}
}
// Builds an array of example groups that enclose the current group.
// When in innermost-first order, the most deeply nested example groups,
// beginning with self, are placed at the beginning of the array.
// When in outermost-first order, the opposite is true, and the last
// group in the array (self) is the most deeply nested.
- (NSArray *)exampleGroupStackInOrder:(SPTExampleGroupOrder)order {
NSMutableArray *groups = [NSMutableArray array];
SPTExampleGroup *group = self;
while (group != nil) {
switch (order) {
case SPTExampleGroupOrderOutermostFirst:
[groups insertObject:group atIndex:0];
break;
case SPTExampleGroupOrderInnermostFirst:
[groups addObject:group];
break;
}
group = group.parent;
}
return [groups copy];
}
- (void)runBeforeHooks:(NSString *)compiledName {
[self runBeforeAllHooks:compiledName];
[self runBeforeEachHooks:compiledName];
}
- (void)runBeforeAllHooks:(NSString *)compiledName {
for(SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderOutermostFirst]) {
if (group.ranExampleCount == 0) {
for (id beforeAllBlock in group.beforeAllArray) {
runExampleBlock(beforeAllBlock, [NSString stringWithFormat:@"%@ - before all block", compiledName]);
}
}
}
}
- (void)runBeforeEachHooks:(NSString *)compiledName {
[self runGlobalBeforeEachHooks:compiledName];
for (SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderOutermostFirst]) {
for (id beforeEachBlock in group.beforeEachArray) {
runExampleBlock(beforeEachBlock, [NSString stringWithFormat:@"%@ - before each block", compiledName]);
}
}
}
- (void)runAfterHooks:(NSString *)compiledName {
[self runAfterEachHooks:compiledName];
[self runAfterAllHooks:compiledName];
}
- (void)runAfterEachHooks:(NSString *)compiledName {
for (SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderInnermostFirst]) {
for (id afterEachBlock in group.afterEachArray) {
runExampleBlock(afterEachBlock, [NSString stringWithFormat:@"%@ - after each block", compiledName]);
}
}
[self runGlobalAfterEachHooks:compiledName];
}
- (void)runAfterAllHooks:(NSString *)compiledName {
for (SPTExampleGroup *group in [self exampleGroupStackInOrder:SPTExampleGroupOrderInnermostFirst]) {
if (group.ranExampleCount == group.exampleCount) {
for (id afterAllBlock in group.afterAllArray) {
runExampleBlock(afterAllBlock, [NSString stringWithFormat:@"%@ - after all block", compiledName]);
}
}
}
}
- (BOOL)isFocusedOrHasFocusedAncestor {
SPTExampleGroup *ancestor = self;
while (ancestor != nil) {
if (ancestor.focused) {
return YES;
} else {
ancestor = ancestor.parent;
}
}
return NO;
}
- (NSArray *)compileExamplesWithStack:(NSArray *)stack {
BOOL groupIsFocusedOrHasFocusedAncestor = [self isFocusedOrHasFocusedAncestor];
NSArray *compiled = @[];
for(id child in self.children) {
if ([child isKindOfClass:[SPTExampleGroup class]]) {
SPTExampleGroup *group = child;
NSArray *newStack = [stack arrayByAddingObject:group];
compiled = [compiled arrayByAddingObjectsFromArray:[group compileExamplesWithStack:newStack]];
} else if ([child isKindOfClass:[SPTExample class]]) {
SPTExample *example = child;
NSArray *newStack = [stack arrayByAddingObject:example];
NSString *compiledName = [spt_map(newStack, ^id(id obj, NSUInteger idx) {
return [obj name];
}) componentsJoinedByString:@" "];
NSString *testCaseName = [spt_map(newStack, ^id(id obj, NSUInteger idx) {
return spt_underscorize([obj name]);
}) componentsJoinedByString:@"__"];
// If example is pending, run only before- and after-all hooks.
// Otherwise, run the example and all before and after hooks.
SPTSpecBlock compiledBlock = example.pending ? ^(SPTSpec *spec){
@synchronized(self.root) {
[self resetRanExampleCountIfNeeded];
[self runBeforeAllHooks:compiledName];
[self incrementRanExampleCount];
[self runAfterAllHooks:compiledName];
}
} : ^(SPTSpec *spec) {
@synchronized(self.root) {
[self resetRanExampleCountIfNeeded];
[self runBeforeHooks:compiledName];
}
@try {
runExampleBlock(example.block, compiledName);
}
@catch(NSException *e) {
[spec spt_handleException:e];
}
@finally {
@synchronized(self.root) {
[self incrementRanExampleCount];
[self runAfterHooks:compiledName];
}
}
};
SPTCompiledExample *compiledExample = [[SPTCompiledExample alloc] initWithName:compiledName testCaseName:testCaseName block:compiledBlock pending:example.pending focused:(groupIsFocusedOrHasFocusedAncestor || example.focused)];
compiled = [compiled arrayByAddingObject:compiledExample];
}
}
return compiled;
}
@end
@@ -0,0 +1,10 @@
/*
* Copyright (c) 2015 Specta Team. All rights reserved.
*/
#import <Foundation/Foundation.h>
// This protocol was used for blacklisting classes for global beforeEach and afterEach blocks.
// Now, instead, classes are whitelisted by implementing the SPTGlobalBeforeAfterEach protocol.
__deprecated_msg("Please whitelist classes instead with the SPTGlobalBeforeAfterEach protocol")
@protocol SPTExcludeGlobalBeforeAfterEach <NSObject>
@end
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2015 Specta Team. All rights reserved.
*/
#import <Foundation/Foundation.h>
// This protocol is used for whitelisting classes for global beforeEach and afterEach blocks.
// If you want a class to participate in those just add this protocol to a category and it will be
// included.
@protocol SPTGlobalBeforeAfterEach <NSObject>
@optional
+ (void)beforeEach;
+ (void)afterEach;
@end
@@ -0,0 +1,17 @@
#import <XCTest/XCTest.h>
#import <Specta/XCTestCase+Specta.h>
#import <Specta/SpectaTypes.h>
@class _XCTestCaseImplementation;
@class SPTExampleGroup;
@interface SPTSharedExampleGroups : XCTestCase
+ (void)addSharedExampleGroupWithName:(NSString *)name block:(SPTDictionaryBlock)block exampleGroup:(SPTExampleGroup *)exampleGroup;
+ (SPTDictionaryBlock)sharedExampleGroupWithName:(NSString *)name exampleGroup:(SPTExampleGroup *)exampleGroup;
- (void)sharedExampleGroups;
@end
@@ -0,0 +1,74 @@
#import "SPTSharedExampleGroups.h"
#import "SPTExampleGroup.h"
#import "SPTSpec.h"
#import "SpectaUtility.h"
#import <objc/runtime.h>
NSMutableDictionary *globalSharedExampleGroups = nil;
BOOL initialized = NO;
@implementation SPTSharedExampleGroups
+ (void)initialize {
Class SPTSharedExampleGroupsClass = [SPTSharedExampleGroups class];
if ([self class] == SPTSharedExampleGroupsClass) {
if (!initialized) {
initialized = YES;
globalSharedExampleGroups = [[NSMutableDictionary alloc] init];
Class *classes = NULL;
int numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = (Class *)malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
Class klass, superClass;
for(uint i = 0; i < numClasses; i++) {
klass = classes[i];
superClass = class_getSuperclass(klass);
if (superClass == SPTSharedExampleGroupsClass) {
[[[klass alloc] init] sharedExampleGroups];
}
}
free(classes);
}
}
}
}
+ (void)addSharedExampleGroupWithName:(NSString *)name block:(SPTDictionaryBlock)block exampleGroup:(SPTExampleGroup *)exampleGroup {
(exampleGroup == nil ? globalSharedExampleGroups : exampleGroup.sharedExamples)[name] = [block copy];
}
+ (SPTDictionaryBlock)sharedExampleGroupWithName:(NSString *)name exampleGroup:(SPTExampleGroup *)exampleGroup {
SPTDictionaryBlock sharedExampleGroup = nil;
while (exampleGroup != nil) {
if ((sharedExampleGroup = exampleGroup.sharedExamples[name])) {
return sharedExampleGroup;
}
exampleGroup = exampleGroup.parent;
}
return globalSharedExampleGroups[name];
}
- (void)sharedExampleGroups {}
- (void)spt_handleException:(NSException *)exception {
[SPTCurrentSpec spt_handleException:exception];
}
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected {
[SPTCurrentSpec recordFailureWithDescription:description inFile:filename atLine:lineNumber expected:expected];
}
- (void)_recordUnexpectedFailureWithDescription:(NSString *)description exception:(NSException *)exception {
[SPTCurrentSpec _recordUnexpectedFailureWithDescription:description exception:exception];
}
- (_XCTestCaseImplementation *)internalImplementation {
return [SPTCurrentSpec internalImplementation];
}
@end
+28
View File
@@ -0,0 +1,28 @@
#import <XCTest/XCTest.h>
#import <Specta/XCTestCase+Specta.h>
@class
SPTTestSuite
, SPTCompiledExample
;
@interface SPTSpec : XCTestCase
@property (strong) XCTestCaseRun *spt_run;
@property (nonatomic) BOOL spt_pending;
@property (nonatomic) BOOL spt_skipped;
+ (BOOL)spt_isDisabled;
+ (void)spt_setDisabled:(BOOL)disabled;
+ (BOOL)spt_focusedExamplesExist;
+ (SEL)spt_convertToTestMethod:(SPTCompiledExample *)example;
+ (SPTTestSuite *)spt_testSuite;
+ (void)spt_setCurrentTestSuite;
+ (void)spt_unsetCurrentTestSuite;
+ (void)spt_setCurrentTestSuiteFileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber;
- (void)spec;
- (BOOL)spt_shouldRunExample:(SPTCompiledExample *)example;
- (void)spt_runExample:(SPTCompiledExample *)example;
@end
+210
View File
@@ -0,0 +1,210 @@
#import "SPTSpec.h"
#import "SPTTestSuite.h"
#import "SPTCompiledExample.h"
#import "SPTSharedExampleGroups.h"
#import "SpectaUtility.h"
#import <objc/runtime.h>
#import "XCTest+Private.h"
@implementation SPTSpec
+ (void)initialize {
[SPTSharedExampleGroups initialize];
SPTTestSuite *testSuite = [[SPTTestSuite alloc] init];
SPTSpec *spec = [[[self class] alloc] init];
NSString *specName = NSStringFromClass([self class]);
objc_setAssociatedObject(self, "spt_testSuite", testSuite, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self spt_setCurrentTestSuite];
@try {
[spec spec];
}
@catch (NSException *exception) {
fprintf(stderr, "%s: An exception has occured outside of tests, aborting.\n\n%s (%s) \n", [specName UTF8String], [[exception name] UTF8String], [[exception reason] UTF8String]);
if ([exception respondsToSelector:@selector(callStackSymbols)]) {
NSArray *callStackSymbols = [exception callStackSymbols];
if (callStackSymbols) {
NSString *callStack = [NSString stringWithFormat:@"\n Call Stack:\n %@\n", [callStackSymbols componentsJoinedByString:@"\n "]];
fprintf(stderr, "%s", [callStack UTF8String]);
}
}
exit(1);
}
@finally {
[self spt_unsetCurrentTestSuite];
}
[testSuite compile];
[[self class] testInvocations];
[super initialize];
}
+ (SPTTestSuite *)spt_testSuite {
return objc_getAssociatedObject(self, "spt_testSuite");
}
+ (BOOL)spt_isDisabled {
return [self spt_testSuite].disabled;
}
+ (void)spt_setDisabled:(BOOL)disabled {
[self spt_testSuite].disabled = disabled;
}
+ (NSArray *)spt_allSpecClasses {
static NSArray *allSpecClasses = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableArray *specClasses = [[NSMutableArray alloc] init];
int numberOfClasses = objc_getClassList(NULL, 0);
if (numberOfClasses > 0) {
Class *classes = (Class *)malloc(sizeof(Class) * numberOfClasses);
numberOfClasses = objc_getClassList(classes, numberOfClasses);
for (int classIndex = 0; classIndex < numberOfClasses; classIndex++) {
Class aClass = classes[classIndex];
if (spt_isSpecClass(aClass)) {
[specClasses addObject:aClass];
}
}
free(classes);
}
allSpecClasses = [specClasses copy];
});
return allSpecClasses;
}
+ (BOOL)spt_focusedExamplesExist {
for (Class specClass in [self spt_allSpecClasses]) {
SPTTestSuite *testSuite = [specClass spt_testSuite];
if (testSuite.disabled == NO && [testSuite hasFocusedExamples]) {
return YES;
}
}
return NO;
}
+ (SEL)spt_convertToTestMethod:(SPTCompiledExample *)example {
@synchronized(example) {
if (!example.testMethodSelector) {
IMP imp = imp_implementationWithBlock(^(SPTSpec *self) {
[self spt_runExample:example];
});
SEL sel;
unsigned int i = 0;
do {
i++;
if (i == 1) {
sel = NSSelectorFromString([NSString stringWithFormat:@"test_%@", example.testCaseName]);
} else {
sel = NSSelectorFromString([NSString stringWithFormat:@"test_%@_%u", example.testCaseName, i]);
}
} while([self instancesRespondToSelector:sel]);
class_addMethod(self, sel, imp, "@@:");
example.testMethodSelector = sel;
}
}
return example.testMethodSelector;
}
+ (void)spt_setCurrentTestSuite {
SPTTestSuite *testSuite = [self spt_testSuite];
[[NSThread currentThread] threadDictionary][spt_kCurrentTestSuiteKey] = testSuite;
}
+ (void)spt_unsetCurrentTestSuite {
[[[NSThread currentThread] threadDictionary] removeObjectForKey:spt_kCurrentTestSuiteKey];
}
+ (void)spt_setCurrentTestSuiteFileName:(NSString *)fileName lineNumber:(NSUInteger)lineNumber {
SPTTestSuite *testSuite = [self spt_testSuite];
testSuite.fileName = fileName;
testSuite.lineNumber = lineNumber;
}
- (void)spec {}
- (BOOL)spt_shouldRunExample:(SPTCompiledExample *)example {
return [[self class] spt_isDisabled] == NO &&
(example.focused || [[self class] spt_focusedExamplesExist] == NO);
}
- (void)spt_runExample:(SPTCompiledExample *)example {
[[NSThread currentThread] threadDictionary][spt_kCurrentSpecKey] = self;
if ([self spt_shouldRunExample:example]) {
self.spt_pending = example.pending;
example.block(self);
} else if (!example.pending) {
self.spt_skipped = YES;
}
[[[NSThread currentThread] threadDictionary] removeObjectForKey:spt_kCurrentSpecKey];
}
#pragma mark - XCTestCase overrides
+ (NSArray *)testInvocations {
NSArray *compiledExamples = [self spt_testSuite].compiledExamples;
[NSMutableArray arrayWithCapacity:[compiledExamples count]];
NSMutableSet *addedSelectors = [NSMutableSet setWithCapacity:[compiledExamples count]];
NSMutableArray *selectors = [NSMutableArray arrayWithCapacity:[compiledExamples count]];
// dynamically generate test methods with compiled examples
for (SPTCompiledExample *example in compiledExamples) {
SEL sel = [self spt_convertToTestMethod:example];
NSString *selName = NSStringFromSelector(sel);
[selectors addObject: selName];
[addedSelectors addObject: selName];
}
// look for any other test methods that may be present in class.
unsigned int n;
Method *imethods = class_copyMethodList(self, &n);
for (NSUInteger i = 0; i < n; i++) {
struct objc_method_description *desc = method_getDescription(imethods[i]);
char *types = desc->types;
SEL sel = desc->name;
NSString *selName = NSStringFromSelector(sel);
if (strcmp(types, "@@:") == 0 && [selName hasPrefix:@"test"] && ![addedSelectors containsObject:selName]) {
[selectors addObject:NSStringFromSelector(sel)];
}
}
free(imethods);
// create invocations from test method selectors
NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:[selectors count]];
for (NSString *selName in selectors) {
SEL sel = NSSelectorFromString(selName);
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self instanceMethodSignatureForSelector:sel]];
[inv setSelector:sel];
[invocations addObject:inv];
}
return spt_shuffle(invocations);
}
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected {
SPTSpec *currentSpec = SPTCurrentSpec;
[currentSpec.spt_run recordFailureWithDescription:description inFile:filename atLine:lineNumber expected:expected];
}
- (void)performTest:(XCTestRun *)run {
self.spt_run = (XCTestCaseRun *)run;
[super performTest:run];
}
@end
+21
View File
@@ -0,0 +1,21 @@
#import <Foundation/Foundation.h>
@class
SPTExample
, SPTExampleGroup
;
@interface SPTTestSuite : NSObject
@property (nonatomic, strong) SPTExampleGroup *rootGroup;
@property (nonatomic, strong) NSMutableArray *groupStack;
@property (nonatomic, strong) NSArray *compiledExamples;
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic) NSUInteger lineNumber;
@property (nonatomic, getter = isDisabled) BOOL disabled;
@property (nonatomic) BOOL hasFocusedExamples;
- (SPTExampleGroup *)currentGroup;
- (void)compile;
@end
+31
View File
@@ -0,0 +1,31 @@
#import "SPTTestSuite.h"
#import "SPTExampleGroup.h"
#import "SPTCompiledExample.h"
@implementation SPTTestSuite
- (id)init {
self = [super init];
if (self) {
self.rootGroup = [[SPTExampleGroup alloc] init];
self.rootGroup.root = self.rootGroup;
self.groupStack = [NSMutableArray arrayWithObject:self.rootGroup];
}
return self;
}
- (SPTExampleGroup *)currentGroup {
return [self.groupStack lastObject];
}
- (void)compile {
self.compiledExamples = [self.rootGroup compileExamplesWithStack:@[]];
for (SPTCompiledExample *example in self.compiledExamples) {
if (example.focused) {
self.hasFocusedExamples = YES;
break;
}
}
}
@end
+14
View File
@@ -0,0 +1,14 @@
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
//! Project version number for Specta.
FOUNDATION_EXPORT double SpectaVersionNumber;
//! Project version string for Specta.
FOUNDATION_EXPORT const unsigned char SpectaVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Specta/PublicHeader.h>
#import <Specta/SpectaDSL.h>
#import <Specta/SPTSpec.h>
#import <Specta/SPTSharedExampleGroups.h>
+90
View File
@@ -0,0 +1,90 @@
#import <XCTest/XCTest.h>
#define SpecBegin(name) _SPTSpecBegin(name, __FILE__, __LINE__)
#define SpecEnd _SPTSpecEnd
#define SharedExamplesBegin(name) _SPTSharedExampleGroupsBegin(name)
#define SharedExamplesEnd _SPTSharedExampleGroupsEnd
#define SharedExampleGroupsBegin(name) _SPTSharedExampleGroupsBegin(name)
#define SharedExampleGroupsEnd _SPTSharedExampleGroupsEnd
typedef void (^DoneCallback)(void);
OBJC_EXTERN void describe(NSString *name, void (^block)());
OBJC_EXTERN void fdescribe(NSString *name, void (^block)());
OBJC_EXTERN void context(NSString *name, void (^block)());
OBJC_EXTERN void fcontext(NSString *name, void (^block)());
OBJC_EXTERN void it(NSString *name, void (^block)());
OBJC_EXTERN void fit(NSString *name, void (^block)());
OBJC_EXTERN void example(NSString *name, void (^block)());
OBJC_EXTERN void fexample(NSString *name, void (^block)());
OBJC_EXTERN void specify(NSString *name, void (^block)());
OBJC_EXTERN void fspecify(NSString *name, void (^block)());
#define pending(...) spt_pending_(__VA_ARGS__, nil)
#define xdescribe(...) spt_pending_(__VA_ARGS__, nil)
#define xcontext(...) spt_pending_(__VA_ARGS__, nil)
#define xexample(...) spt_pending_(__VA_ARGS__, nil)
#define xit(...) spt_pending_(__VA_ARGS__, nil)
#define xspecify(...) spt_pending_(__VA_ARGS__, nil)
OBJC_EXTERN void beforeAll(void (^block)());
OBJC_EXTERN void afterAll(void (^block)());
OBJC_EXTERN void beforeEach(void (^block)());
OBJC_EXTERN void afterEach(void (^block)());
OBJC_EXTERN void before(void (^block)());
OBJC_EXTERN void after(void (^block)());
OBJC_EXTERN void sharedExamplesFor(NSString *name, void (^block)(NSDictionary *data));
OBJC_EXTERN void sharedExamples(NSString *name, void (^block)(NSDictionary *data));
#define itShouldBehaveLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__)
#define itBehavesLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__)
OBJC_EXTERN void waitUntil(void (^block)(DoneCallback done));
/**
* Runs the @c block and waits until the @c done block is called or the
* @c timeout has passed.
*
* @param timeout timeout for this @c block only; does not affect the global
* timeout, as @c setAsyncSpecTimeout() does.
* @param ^block runs test code
*/
OBJC_EXTERN void waitUntilTimeout(NSTimeInterval timeout, void (^block)(DoneCallback done));
OBJC_EXTERN void setAsyncSpecTimeout(NSTimeInterval timeout);
// ----------------------------------------------------------------------------
#define _SPTSpecBegin(name, file, line) \
@interface name##Spec : SPTSpec \
@end \
@implementation name##Spec \
- (void)spec { \
[[self class] spt_setCurrentTestSuiteFileName:(@(file)) lineNumber:(line)];
#define _SPTSpecEnd \
} \
@end
#define _SPTSharedExampleGroupsBegin(name) \
@interface name##SharedExampleGroups : SPTSharedExampleGroups \
@end \
@implementation name##SharedExampleGroups \
- (void)sharedExampleGroups {
#define _SPTSharedExampleGroupsEnd \
} \
@end
OBJC_EXTERN void spt_it_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)());
OBJC_EXTERN void spt_fit_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)());
OBJC_EXTERN void spt_pending_(NSString *name, ...);
OBJC_EXTERN void spt_itShouldBehaveLike_(NSString *fileName, NSUInteger lineNumber, NSString *name, id dictionaryOrBlock);
OBJC_EXTERN void spt_itShouldBehaveLike_block(NSString *fileName, NSUInteger lineNumber, NSString *name, NSDictionary *(^block)());
+189
View File
@@ -0,0 +1,189 @@
#import "SpectaDSL.h"
#import "SpectaTypes.h"
#import "SpectaUtility.h"
#import "SPTTestSuite.h"
#import "SPTExampleGroup.h"
#import "SPTSharedExampleGroups.h"
#import "SPTSpec.h"
#import "SPTCallSite.h"
#import <libkern/OSAtomic.h>
static NSTimeInterval asyncSpecTimeout = 10.0;
static void spt_defineItBlock(NSString *name, NSString *fileName, NSUInteger lineNumber, BOOL focused, void (^block)()) {
SPTReturnUnlessBlockOrNil(block);
SPTCallSite *site = nil;
if (lineNumber && fileName) {
site = [SPTCallSite callSiteWithFile:fileName line:lineNumber];
}
[SPTCurrentGroup addExampleWithName:name callSite:site focused:focused block:block];
}
static void spt_defineDescribeBlock(NSString *name, BOOL focused, void (^block)()) {
if (block) {
[SPTGroupStack addObject:[SPTCurrentGroup addExampleGroupWithName:name focused:focused]];
block();
[SPTGroupStack removeLastObject];
} else {
spt_defineItBlock(name, nil, 0, focused, nil);
}
}
void spt_it_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()) {
spt_defineItBlock(name, fileName, lineNumber, NO, block);
}
void spt_fit_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)()) {
spt_defineItBlock(name, fileName, lineNumber, YES, block);
}
void spt_pending_(NSString *name, ...) {
spt_defineItBlock(name, nil, 0, NO, nil);
}
void spt_itShouldBehaveLike_(NSString *fileName, NSUInteger lineNumber, NSString *name, id dictionaryOrBlock) {
SPTDictionaryBlock block = [SPTSharedExampleGroups sharedExampleGroupWithName:name exampleGroup:SPTCurrentGroup];
if (block) {
if (SPTIsBlock(dictionaryOrBlock)) {
id (^dataBlock)(void) = [dictionaryOrBlock copy];
describe(name, ^{
__block NSMutableDictionary *dataDict = [[NSMutableDictionary alloc] init];
beforeEach(^{
NSDictionary *blockData = dataBlock();
[dataDict removeAllObjects];
[dataDict addEntriesFromDictionary:blockData];
});
block(dataDict);
afterAll(^{
dataDict = nil;
});
});
} else {
NSDictionary *data = dictionaryOrBlock;
describe(name, ^{
block(data);
});
}
} else {
SPTSpec *currentSpec = SPTCurrentSpec;
if (currentSpec) {
[currentSpec recordFailureWithDescription:@"itShouldBehaveLike should not be invoked inside an example block!" inFile:fileName atLine:lineNumber expected:NO];
} else {
it(name, ^{
[SPTCurrentSpec recordFailureWithDescription:[NSString stringWithFormat:@"Shared example group \"%@\" does not exist.", name] inFile:fileName atLine:lineNumber expected:NO];
});
}
}
}
void spt_itShouldBehaveLike_block(NSString *fileName, NSUInteger lineNumber, NSString *name, NSDictionary *(^block)()) {
spt_itShouldBehaveLike_(fileName, lineNumber, name, (id)block);
}
void describe(NSString *name, void (^block)()) {
spt_defineDescribeBlock(name, NO, block);
}
void fdescribe(NSString *name, void (^block)()) {
spt_defineDescribeBlock(name, YES, block);
}
void context(NSString *name, void (^block)()) {
describe(name, block);
}
void fcontext(NSString *name, void (^block)()) {
fdescribe(name, block);
}
void it(NSString *name, void (^block)()) {
spt_defineItBlock(name, nil, 0, NO, block);
}
void fit(NSString *name, void (^block)()) {
spt_defineItBlock(name, nil, 0, YES, block);
}
void example(NSString *name, void (^block)()) {
it(name, block);
}
void fexample(NSString *name, void (^block)()) {
fit(name, block);
}
void specify(NSString *name, void (^block)()) {
it(name, block);
}
void fspecify(NSString *name, void (^block)()) {
fit(name, block);
}
void beforeAll(void (^block)()) {
SPTReturnUnlessBlockOrNil(block);
[SPTCurrentGroup addBeforeAllBlock:block];
}
void afterAll(void (^block)()) {
SPTReturnUnlessBlockOrNil(block);
[SPTCurrentGroup addAfterAllBlock:block];
}
void beforeEach(void (^block)()) {
SPTReturnUnlessBlockOrNil(block);
[SPTCurrentGroup addBeforeEachBlock:block];
}
void afterEach(void (^block)()) {
SPTReturnUnlessBlockOrNil(block);
[SPTCurrentGroup addAfterEachBlock:block];
}
void before(void (^block)()) {
beforeEach(block);
}
void after(void (^block)()) {
afterEach(block);
}
void sharedExamplesFor(NSString *name, void (^block)(NSDictionary *data)) {
[SPTSharedExampleGroups addSharedExampleGroupWithName:name block:block exampleGroup:SPTCurrentGroup];
}
void sharedExamples(NSString *name, void (^block)(NSDictionary *data)) {
sharedExamplesFor(name, block);
}
void waitUntil(void (^block)(DoneCallback done)) {
waitUntilTimeout(asyncSpecTimeout, block);
}
void waitUntilTimeout(NSTimeInterval timeout, void (^block)(DoneCallback done)) {
__block uint32_t complete = 0;
dispatch_async(dispatch_get_main_queue(), ^{
block(^{
OSAtomicOr32Barrier(1, &complete);
});
});
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
while (!complete && [timeoutDate timeIntervalSinceNow] > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
if (!complete) {
NSString *message = [NSString stringWithFormat:@"failed to invoke done() callback before timeout (%f seconds)", timeout];
SPTSpec *currentSpec = SPTCurrentSpec;
SPTTestSuite *testSuite = [[currentSpec class] spt_testSuite];
[currentSpec recordFailureWithDescription:message inFile:testSuite.fileName atLine:testSuite.lineNumber expected:YES];
}
}
void setAsyncSpecTimeout(NSTimeInterval timeout) {
asyncSpecTimeout = timeout;
}
+5
View File
@@ -0,0 +1,5 @@
@class SPTSpec;
typedef void (^SPTVoidBlock)();
typedef void (^SPTSpecBlock)(SPTSpec *spec);
typedef void (^SPTDictionaryBlock)(NSDictionary *dictionary);
+18
View File
@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
extern NSString * const spt_kCurrentTestSuiteKey;
extern NSString * const spt_kCurrentSpecKey;
#define SPTCurrentTestSuite [[NSThread mainThread] threadDictionary][spt_kCurrentTestSuiteKey]
#define SPTCurrentSpec [[NSThread mainThread] threadDictionary][spt_kCurrentSpecKey]
#define SPTCurrentGroup [SPTCurrentTestSuite currentGroup]
#define SPTGroupStack [SPTCurrentTestSuite groupStack]
#define SPTReturnUnlessBlockOrNil(block) if ((block) && !SPTIsBlock((block))) return;
#define SPTIsBlock(obj) [(obj) isKindOfClass:NSClassFromString(@"NSBlock")]
BOOL spt_isSpecClass(Class aClass);
NSString *spt_underscorize(NSString *string);
NSArray *spt_map(NSArray *array, id (^block)(id obj, NSUInteger idx));
NSArray *spt_shuffle(NSArray *array);
unsigned int spt_seed();
+79
View File
@@ -0,0 +1,79 @@
#import "SpectaUtility.h"
#import "SPTSpec.h"
#import <objc/runtime.h>
NSString * const spt_kCurrentTestSuiteKey = @"SPTCurrentTestSuite";
NSString * const spt_kCurrentSpecKey = @"SPTCurrentSpec";
static unsigned int seed = 0;
BOOL spt_isSpecClass(Class aClass) {
Class superclass = class_getSuperclass(aClass);
while (superclass != Nil) {
if (superclass == [SPTSpec class]) {
return YES;
} else {
superclass = class_getSuperclass(superclass);
}
}
return NO;
}
NSString *spt_underscorize(NSString *string) {
static NSMutableCharacterSet *invalidCharSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
invalidCharSet = [[NSMutableCharacterSet alloc] init];
[invalidCharSet formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]];
[invalidCharSet formUnionWithCharacterSet:[NSCharacterSet illegalCharacterSet]];
[invalidCharSet formUnionWithCharacterSet:[NSCharacterSet newlineCharacterSet]];
[invalidCharSet formUnionWithCharacterSet:[NSCharacterSet nonBaseCharacterSet]];
[invalidCharSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
[invalidCharSet formUnionWithCharacterSet:[NSCharacterSet symbolCharacterSet]];
});
NSString *stripped = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
stripped = [[stripped componentsSeparatedByCharactersInSet:invalidCharSet] componentsJoinedByString:@""];
NSArray *components = [stripped componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
stripped = [[components objectsAtIndexes:[components indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return ![obj isEqualToString:@""];
}]] componentsJoinedByString:@"_"];
return stripped;
}
NSArray *spt_map(NSArray *array, id (^block)(id obj, NSUInteger idx)) {
NSMutableArray *mapped = [NSMutableArray arrayWithCapacity:[array count]];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[mapped addObject:block(obj, idx)];
}];
return mapped;
}
NSArray *spt_shuffle(NSArray *array) {
if (![[[[NSProcessInfo processInfo] environment] objectForKey:@"SPECTA_SHUFFLE"] isEqualToString:@"1"]) {
return array;
}
spt_seed();
NSMutableArray *shuffled = [array mutableCopy];
NSUInteger count = [shuffled count];
for (NSUInteger i = 0; i < count; i++) {
NSUInteger r = random() % count;
[shuffled exchangeObjectAtIndex:i withObjectAtIndex:r];
}
return shuffled;
}
unsigned int spt_seed() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *envSeed = [[[NSProcessInfo processInfo] environment] objectForKey:@"SPECTA_SEED"];
if (envSeed) {
sscanf([envSeed UTF8String], "%u", &seed);
} else {
seed = arc4random();
}
srandom(seed);
printf("Test Seed: %u\n", seed);
});
return seed;
}
+41
View File
@@ -0,0 +1,41 @@
#import <XCTest/XCTest.h>
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
@interface XCTestObservationCenter (SPTTestSuspention)
- (void)_suspendObservationForBlock:(void (^)(void))block;
@end
#else
@interface XCTestObservationCenter : NSObject
+ (id)sharedObservationCenter;
- (void)_suspendObservationForBlock:(void (^)(void))block;
@end
@protocol XCTestObservation <NSObject>
@end
#endif
@interface _XCTestDriverTestObserver : NSObject <XCTestObservation>
- (void)stopObserving;
- (void)startObserving;
@end
@interface _XCTestCaseImplementation : NSObject
@end
@interface XCTestCase ()
- (_XCTestCaseImplementation *)internalImplementation;
- (void)_recordUnexpectedFailureWithDescription:(NSString *)description exception:(NSException *)exception;
@end
+7
View File
@@ -0,0 +1,7 @@
#import <XCTest/XCTest.h>
@interface XCTestCase (Specta)
- (void)spt_handleException:(NSException *)exception;
@end
+65
View File
@@ -0,0 +1,65 @@
#import <objc/runtime.h>
#import "XCTestCase+Specta.h"
#import "SPTSpec.h"
#import "SPTExample.h"
#import "SPTSharedExampleGroups.h"
#import "SpectaUtility.h"
#import "XCTest+Private.h"
@interface XCTestCase (xct_allSubclasses)
- (NSArray *)allSubclasses;
- (void)_dequeueFailures;
@end
@implementation XCTestCase (Specta)
+ (void)load {
Method allSubclasses = class_getClassMethod(self, @selector(allSubclasses));
Method allSubclasses_swizzle = class_getClassMethod(self , @selector(spt_allSubclasses_swizzle));
method_exchangeImplementations(allSubclasses, allSubclasses_swizzle);
Method dequeueFailures = class_getInstanceMethod(self, @selector(_dequeueFailures));
Method dequeueFailures_swizzle = class_getInstanceMethod(self, @selector(spt_dequeueFailures));
method_exchangeImplementations(dequeueFailures, dequeueFailures_swizzle);
}
+ (NSArray *)spt_allSubclasses_swizzle {
NSArray *subclasses = [self spt_allSubclasses_swizzle]; // call original
NSMutableArray *filtered = [NSMutableArray arrayWithCapacity:[subclasses count]];
// exclude SPTSpec base class and all subclasses of SPTSharedExampleGroups
for (id subclass in subclasses) {
if (subclass != [SPTSpec class] && ![subclass isKindOfClass:[SPTSharedExampleGroups class]]) {
[filtered addObject:subclass];
}
}
return spt_shuffle(filtered);
}
- (void)spt_dequeueFailures {
void(^dequeueFailures)() = ^() {
[self spt_dequeueFailures];
};
if ([NSThread isMainThread]) {
dequeueFailures();
} else {
dispatch_sync(dispatch_get_main_queue(), dequeueFailures);
}
}
- (void)spt_handleException:(NSException *)exception {
NSString *description = [exception reason];
if ([exception userInfo]) {
id line = [exception userInfo][@"line"];
id file = [exception userInfo][@"file"];
if ([line isKindOfClass:[NSNumber class]] && [file isKindOfClass:[NSString class]]) {
[self recordFailureWithDescription:description inFile:file atLine:[line unsignedIntegerValue] expected:YES];
return;
}
}
[self _recordUnexpectedFailureWithDescription:description exception:exception];
}
@end