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
+
+![Specta Screenshot](http://github.com/petejkim/stuff/raw/master/images/specta-screenshot.png)
+
+## 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