Project import
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ca257c0
--- /dev/null
+++ b/LICENSE
@@ -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.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4150a6e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,165 @@
+# Specta
+
+A light-weight TDD / BDD framework for Objective-C.
+
+## WHAT'S NEW IN 0.3.0 beta 1
+
+* Xcode 6 / iOS 8 support.
+* Option to shuffle tests. (Set environment variable `SPECTA_SHUFFLE` with value `1` to enable this feature.)
+
+## BREAKING CHANGES IN 0.3.0 beta 1
+
+* `^AsyncBlock` is replaced by `waitUntil`. See example for usage.
+
+## FEATURES
+
+* Support for both 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)
+
+```ruby
+target :MyApp do
+ # your app dependencies
+end
+
+target :MyAppTests do
+ pod 'Specta', '~> 0.3.0.beta1'
+ # pod 'Expecta', '~> 0.3.1' # expecta matchers
+ # pod 'OCMock', '~> 2.2.1' # OCMock
+ # pod 'OCHamcrest', '~> 3.0.0' # hamcrest matchers
+ # pod 'OCMockito', '~> 1.0.0' # OCMock
+ # pod 'LRMocky', '~> 0.9.1' # LRMocky
+end
+```
+
+or:
+
+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. Add the following to your test code.
+
+```objective-c
+#import <Specta/Specta.h> // #import "Specta.h" if you're using cocoapods or libSpecta.a
+```
+
+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/).
+
+## EXAMPLE
+
+```objective-c
+#import <Specta/Specta.h> // #import "Specta.h" if you're using cocoapods or libSpecta.a
+
+SharedExamplesBegin(MySharedExamples)
+// Global shared examples are shared across all spec files.
+
+sharedExamplesFor(@"a shared behavior", ^(NSDictionary *data) {
+ it(@"should do some stuff", ^{
+ id obj = data[@"key"];
+ // ...
+ });
+});
+
+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.
+
+## RUNNING TESTS IN COMMAND LINE
+
+* Use Facebook's [xctool](https://github.com/facebook/xctool/).
+
+## 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-2014 [Specta Team](https://github.com/specta?tab=members). This software is licensed under the [MIT License](http://github.com/specta/specta/raw/master/LICENSE).
diff --git a/Specta/Specta/SPTCallSite.h b/Specta/Specta/SPTCallSite.h
new file mode 100644
index 0000000..a5ba565
--- /dev/null
+++ b/Specta/Specta/SPTCallSite.h
@@ -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
\ No newline at end of file
diff --git a/Specta/Specta/SPTCallSite.m b/Specta/Specta/SPTCallSite.m
new file mode 100644
index 0000000..baa817a
--- /dev/null
+++ b/Specta/Specta/SPTCallSite.m
@@ -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
\ No newline at end of file
diff --git a/Specta/Specta/SPTCompiledExample.h b/Specta/Specta/SPTCompiledExample.h
new file mode 100644
index 0000000..49d55e0
--- /dev/null
+++ b/Specta/Specta/SPTCompiledExample.h
@@ -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
\ No newline at end of file
diff --git a/Specta/Specta/SPTCompiledExample.m b/Specta/Specta/SPTCompiledExample.m
new file mode 100644
index 0000000..0f3060d
--- /dev/null
+++ b/Specta/Specta/SPTCompiledExample.m
@@ -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
\ No newline at end of file
diff --git a/Specta/Specta/SPTExample.h b/Specta/Specta/SPTExample.h
new file mode 100644
index 0000000..04df08e
--- /dev/null
+++ b/Specta/Specta/SPTExample.h
@@ -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
\ No newline at end of file
diff --git a/Specta/Specta/SPTExample.m b/Specta/Specta/SPTExample.m
new file mode 100644
index 0000000..b7a0c91
--- /dev/null
+++ b/Specta/Specta/SPTExample.m
@@ -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
\ No newline at end of file
diff --git a/Specta/Specta/SPTExampleGroup.h b/Specta/Specta/SPTExampleGroup.h
new file mode 100644
index 0000000..f3d0060
--- /dev/null
+++ b/Specta/Specta/SPTExampleGroup.h
@@ -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, strong) SPTExampleGroup *root;
+@property (nonatomic, strong) 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
diff --git a/Specta/Specta/SPTExampleGroup.m b/Specta/Specta/SPTExampleGroup.m
new file mode 100644
index 0000000..69eea2d
--- /dev/null
+++ b/Specta/Specta/SPTExampleGroup.m
@@ -0,0 +1,339 @@
+#import "SPTExampleGroup.h"
+#import "SPTExample.h"
+#import "SPTCompiledExample.h"
+#import "SPTSpec.h"
+#import "SpectaUtility.h"
+#import "XCTest+Private.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 (strcmp("UIAccessibilitySafeCategory__NSObject", class_getName(aClass))) {
+ Method globalMethod = class_getClassMethod(aClass, classMethodSelector);
+ if (globalMethod) {
+ [classesWithClassMethod addObject:aClass];
+ }
+ }
+ }
+
+ free(classes);
+ }
+
+ return classesWithClassMethod;
+}
+
+@interface NSObject (SpectaGlobalBeforeAfterEach)
+
++ (void)beforeEach;
++ (void)afterEach;
+
+@end
+
+static void runExampleBlock(void (^block)(), NSString *name) {
+ if (!SPTIsBlock(block)) {
+ return;
+ }
+
+ ((SPTVoidBlock)block)();
+}
+
+typedef NS_ENUM(NSInteger, SPTExampleGroupOrder) {
+ SPTExampleGroupOrderOutermostFirst = 1,
+ SPTExampleGroupOrderInnermostFirst
+};
+
+@interface SPTExampleGroup ()
+
+- (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.pendingExampleCount == 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 runBeforeAllHooks:compiledName];
+ [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
diff --git a/Specta/Specta/SPTSharedExampleGroups.h b/Specta/Specta/SPTSharedExampleGroups.h
new file mode 100644
index 0000000..090acba
--- /dev/null
+++ b/Specta/Specta/SPTSharedExampleGroups.h
@@ -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
+
diff --git a/Specta/Specta/SPTSharedExampleGroups.m b/Specta/Specta/SPTSharedExampleGroups.m
new file mode 100644
index 0000000..35b405c
--- /dev/null
+++ b/Specta/Specta/SPTSharedExampleGroups.m
@@ -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
diff --git a/Specta/Specta/SPTSpec.h b/Specta/Specta/SPTSpec.h
new file mode 100644
index 0000000..ada4ea2
--- /dev/null
+++ b/Specta/Specta/SPTSpec.h
@@ -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
diff --git a/Specta/Specta/SPTSpec.m b/Specta/Specta/SPTSpec.m
new file mode 100644
index 0000000..7671783
--- /dev/null
+++ b/Specta/Specta/SPTSpec.m
@@ -0,0 +1,209 @@
+#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];
+ [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
diff --git a/Specta/Specta/SPTTestSuite.h b/Specta/Specta/SPTTestSuite.h
new file mode 100644
index 0000000..9bd9016
--- /dev/null
+++ b/Specta/Specta/SPTTestSuite.h
@@ -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
diff --git a/Specta/Specta/SPTTestSuite.m b/Specta/Specta/SPTTestSuite.m
new file mode 100644
index 0000000..7053edd
--- /dev/null
+++ b/Specta/Specta/SPTTestSuite.m
@@ -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
diff --git a/Specta/Specta/Specta.h b/Specta/Specta/Specta.h
new file mode 100644
index 0000000..dda17f9
--- /dev/null
+++ b/Specta/Specta/Specta.h
@@ -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>
diff --git a/Specta/Specta/SpectaDSL.h b/Specta/Specta/SpectaDSL.h
new file mode 100644
index 0000000..c845a7d
--- /dev/null
+++ b/Specta/Specta/SpectaDSL.h
@@ -0,0 +1,81 @@
+#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);
+
+void describe(NSString *name, void (^block)());
+void fdescribe(NSString *name, void (^block)());
+
+void context(NSString *name, void (^block)());
+void fcontext(NSString *name, void (^block)());
+
+void it(NSString *name, void (^block)());
+void fit(NSString *name, void (^block)());
+
+void example(NSString *name, void (^block)());
+void fexample(NSString *name, void (^block)());
+
+void specify(NSString *name, void (^block)());
+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)
+
+void beforeAll(void (^block)());
+void afterAll(void (^block)());
+
+void beforeEach(void (^block)());
+void afterEach(void (^block)());
+
+void before(void (^block)());
+void after(void (^block)());
+
+void sharedExamplesFor(NSString *name, void (^block)(NSDictionary *data));
+void sharedExamples(NSString *name, void (^block)(NSDictionary *data));
+
+#define itShouldBehaveLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__)
+#define itBehavesLike(...) spt_itShouldBehaveLike_(@(__FILE__), __LINE__, __VA_ARGS__)
+
+void waitUntil(void (^block)(DoneCallback done));
+
+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
+
+void spt_it_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)());
+void spt_fit_(NSString *name, NSString *fileName, NSUInteger lineNumber, void (^block)());
+void spt_pending_(NSString *name, ...);
+void spt_itShouldBehaveLike_(NSString *fileName, NSUInteger lineNumber, NSString *name, id dictionaryOrBlock);
+void spt_itShouldBehaveLike_block(NSString *fileName, NSUInteger lineNumber, NSString *name, NSDictionary *(^block)());
\ No newline at end of file
diff --git a/Specta/Specta/SpectaDSL.m b/Specta/Specta/SpectaDSL.m
new file mode 100644
index 0000000..861a4e1
--- /dev/null
+++ b/Specta/Specta/SpectaDSL.m
@@ -0,0 +1,186 @@
+#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)) {
+ __block uint32_t complete = 0;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ block(^{
+ OSAtomicOr32Barrier(1, &complete);
+ });
+ });
+ NSTimeInterval timeout = asyncSpecTimeout;
+ 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;
+}
diff --git a/Specta/Specta/SpectaTypes.h b/Specta/Specta/SpectaTypes.h
new file mode 100644
index 0000000..f1f0ae3
--- /dev/null
+++ b/Specta/Specta/SpectaTypes.h
@@ -0,0 +1,5 @@
+@class SPTSpec;
+
+typedef void (^SPTVoidBlock)();
+typedef void (^SPTSpecBlock)(SPTSpec *spec);
+typedef void (^SPTDictionaryBlock)(NSDictionary *dictionary);
diff --git a/Specta/Specta/SpectaUtility.h b/Specta/Specta/SpectaUtility.h
new file mode 100644
index 0000000..b10189b
--- /dev/null
+++ b/Specta/Specta/SpectaUtility.h
@@ -0,0 +1,18 @@
+#import <Foundation/Foundation.h>
+
+extern NSString * const spt_kCurrentTestSuiteKey;
+extern NSString * const spt_kCurrentSpecKey;
+
+#define SPTCurrentTestSuite [[NSThread currentThread] threadDictionary][spt_kCurrentTestSuiteKey]
+#define SPTCurrentSpec [[NSThread currentThread] 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();
\ No newline at end of file
diff --git a/Specta/Specta/SpectaUtility.m b/Specta/Specta/SpectaUtility.m
new file mode 100644
index 0000000..956c11b
--- /dev/null
+++ b/Specta/Specta/SpectaUtility.m
@@ -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;
+}
\ No newline at end of file
diff --git a/Specta/Specta/XCTest+Private.h b/Specta/Specta/XCTest+Private.h
new file mode 100644
index 0000000..7eb6849
--- /dev/null
+++ b/Specta/Specta/XCTest+Private.h
@@ -0,0 +1,28 @@
+#import <XCTest/XCTest.h>
+
+@interface XCTestObservationCenter : NSObject
+
++ (id)sharedObservationCenter;
+- (void)_suspendObservationForBlock:(void (^)(void))block;
+
+@end
+
+@protocol XCTestObservation <NSObject>
+@end
+
+@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
\ No newline at end of file
diff --git a/Specta/Specta/XCTestCase+Specta.h b/Specta/Specta/XCTestCase+Specta.h
new file mode 100644
index 0000000..873b997
--- /dev/null
+++ b/Specta/Specta/XCTestCase+Specta.h
@@ -0,0 +1,7 @@
+#import <XCTest/XCTest.h>
+
+@interface XCTestCase (Specta)
+
+- (void)spt_handleException:(NSException *)exception;
+
+@end
\ No newline at end of file
diff --git a/Specta/Specta/XCTestCase+Specta.m b/Specta/Specta/XCTestCase+Specta.m
new file mode 100644
index 0000000..cd8047a
--- /dev/null
+++ b/Specta/Specta/XCTestCase+Specta.m
@@ -0,0 +1,48 @@
+#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;
+
+@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);
+}
+
++ (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_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
\ No newline at end of file