no message

This commit is contained in:
giuseppenuc committed 2016-01-11 19:34:39 +01:00
1 parent 2b564cb203
commit 03436011a4
204 files changed
+12793 -37

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2012-2014 Specta Team.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+176
View File
@@ -0,0 +1,176 @@
# Specta
A light-weight TDD / BDD framework for Objective-C.
### Status
[![Build Status](https://travis-ci.org/specta/specta.png)](https://travis-ci.org/specta/specta)
[![Coverage Status](https://coveralls.io/repos/specta/specta/badge.svg)](https://coveralls.io/r/specta/specta)
## FEATURES
* An Objective-C RSpec-like BDD DSL
* Quick and easy set up
* Built on top of XCTest
* Excellent Xcode integration
## SCREENSHOT
![Specta Screenshot](https://raw.githubusercontent.com/specta/specta/master/misc/specta_screenshot.jpg)
## SETUP
Use [CocoaPods](http://github.com/CocoaPods/CocoaPods), [Carthage](https://github.com/carthage/carthage) or [Set up manually](#setting-up-manually)
### CocoaPods
1. Add Specta to your project's `Podfile`:
```ruby
target :MyApp do
# your app dependencies
end
target :MyAppTests do
pod 'Specta', '~> 1.0'
# pod 'Expecta', '~> 1.0' # expecta matchers
# pod 'OCMock', '~> 2.2' # OCMock
# pod 'OCHamcrest', '~> 3.0' # hamcrest matchers
# pod 'OCMockito', '~> 1.0' # OCMock
# pod 'LRMocky', '~> 0.9' # LRMocky
end
```
2. Run `pod update` or `pod install` in your project directory.
### Carthage
1. Add Specta to your project's `Cartfile.private`
```
github "specta/specta" ~> 1.0
```
2. Run `carthage update` in your project directory
3. Drag the appropriate `Specta.framework` for your platform (located in Carthage/Build/) into your application’s Xcode project, and add it to your test target(s).
4. If you are building for iOS, a new `Run Script Phase` must be added to copy the framework. The instructions can be found on [Carthage's getting started instructions](https://github.com/carthage/carthage#getting-started)
### SETTING UP MANUALLY
1. Clone from Github.
2. Run `rake` in project root to build.
3. Add a "Cocoa/Cocoa Touch Unit Testing Bundle" target if you don't already have one.
4. Copy and add all header files in `Products` folder to the Test target in your Xcode project.
5. For **OS X projects**, copy and add `Specta.framework` in `Products/osx` folder to the test target in your Xcode project.
For **iOS projects**, copy and add `Specta.framework` in `Products/ios` folder to the test target in your Xcode project.
You can alternatively use `libSpecta.a`, if you prefer to add it as a static library for your project. (iOS 7 and below require this)
6. Add `-ObjC` and `-all_load` to the "Other Linker Flags" build setting for the test target in your Xcode project.
7. If you encounter linking issues with `_llvm_*` symbols, ensure your target's "Generate Test Coverage Files" and "Instrument Program Flow" build settings are set to `Yes`.
## EXAMPLE
```objective-c
#import <Specta/Specta.h> // #import "Specta.h" if you're using libSpecta.a
SharedExamplesBegin(MySharedExamples)
// Global shared examples are shared across all spec files.
sharedExamplesFor(@"foo", ^(NSDictionary *data) {
__block id bar = nil;
beforeEach(^{
bar = data[@"bar"];
});
it(@"should not be nil", ^{
XCTAssertNotNil(bar);
});
});
SharedExamplesEnd
SpecBegin(Thing)
describe(@"Thing", ^{
sharedExamplesFor(@"another shared behavior", ^(NSDictionary *data) {
// Locally defined shared examples can override global shared examples within its scope.
});
beforeAll(^{
// This is run once and only once before all of the examples
// in this group and before any beforeEach blocks.
});
beforeEach(^{
// This is run before each example.
});
it(@"should do stuff", ^{
// This is an example block. Place your assertions here.
});
it(@"should do some stuff asynchronously", ^{
waitUntil(^(DoneCallback done) {
// Async example blocks need to invoke done() callback.
done();
});
});
itShouldBehaveLike(@"a shared behavior", @{@"key" : @"obj"});
itShouldBehaveLike(@"another shared behavior", ^{
// Use a block that returns a dictionary if you need the context to be evaluated lazily,
// e.g. to use an object prepared in a beforeEach block.
return @{@"key" : @"obj"};
});
describe(@"Nested examples", ^{
it(@"should do even more stuff", ^{
// ...
});
});
pending(@"pending example");
pending(@"another pending example", ^{
// ...
});
afterEach(^{
// This is run after each example.
});
afterAll(^{
// This is run once and only once after all of the examples
// in this group and after any afterEach blocks.
});
});
SpecEnd
```
* `beforeEach` and `afterEach` are also aliased as `before` and `after` respectively.
* `describe` is also aliased as `context`.
* `it` is also aliased as `example` and `specify`.
* `itShouldBehaveLike` is also aliased as `itBehavesLike`.
* Use `pending` or prepend `x` to `describe`, `context`, `example`, `it`, and `specify` to mark examples or groups as pending.
* Use `^(DoneCallback done)` as shown in the example above to make examples wait for completion. `done()` callback needs to be invoked to let Specta know that your test is complete. The default timeout is 10.0 seconds but this can be changed by calling the function `setAsyncSpecTimeout(NSTimeInterval timeout)`.
* `(before|after)(Each/All)` also accept `^(DoneCallback done)`s.
* Do `#define SPT_CEDAR_SYNTAX` before importing Specta if you prefer to write `SPEC_BEGIN` and `SPEC_END` instead of `SpecBegin` and `SpecEnd`.
* Prepend `f` to your `describe`, `context`, `example`, `it`, and `specify` to set focus on examples or groups. When specs are focused, all unfocused specs are skipped.
* To use original XCTest reporter, set an environment variable named `SPECTA_REPORTER_CLASS` to `SPTXCTestReporter` in your test scheme.
* Set an environment variable `SPECTA_NO_SHUFFLE` with value `1` to disable test shuffling.
* Set an environment variable `SPECTA_SEED` to specify the random seed for test shuffling.
Standard XCTest matchers such as `XCTAssertEqualObjects` and `XCTAssertNil` work, but you probably want to add a nicer matcher framework - [Expecta](http://github.com/specta/expecta/) to your setup. Or if you really prefer, [OCHamcrest](https://github.com/jonreid/OCHamcrest) works fine too. Also, add a mocking framework: [OCMock](http://ocmock.org/).
## RUNNING TESTS IN COMMAND LINE
* Run `rake test` in the cloned folder.
## CONTRIBUTION GUIDELINES
* Please use only spaces and indent 2 spaces at a time.
* Please prefix instance variable names with a single underscore (`_`).
* Please prefix custom classes and functions defined in the global scope with `SPT`.
## LICENSE
Copyright (c) 2012-2015 [Specta Team](https://github.com/specta?tab=members). This software is licensed under the [MIT License](http://github.com/specta/specta/raw/master/LICENSE).
+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