no message
This commit is contained in:
1 parent
2b564cb203
commit
03436011a4
204 files changed
+12793
-37
No files matched your search
Generated
+20
@@ -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.
|
||||
|
||||
Generated
+176
@@ -0,0 +1,176 @@
|
||||
# Specta
|
||||
|
||||
A light-weight TDD / BDD framework for Objective-C.
|
||||
|
||||
### Status
|
||||
[](https://travis-ci.org/specta/specta)
|
||||
[](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
|
||||
|
||||

|
||||
|
||||
## 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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
|
||||
@class SPTSpec;
|
||||
|
||||
typedef void (^SPTVoidBlock)();
|
||||
typedef void (^SPTSpecBlock)(SPTSpec *spec);
|
||||
typedef void (^SPTDictionaryBlock)(NSDictionary *dictionary);
|
||||
+18
@@ -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
@@ -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
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface XCTestCase (Specta)
|
||||
|
||||
- (void)spt_handleException:(NSException *)exception;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
Reference in new issue
Block a user