main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWExample.h"
  8#import "KWExampleGroupBuilder.h"
  9#import "KWContextNode.h"
 10#import "KWMatcherFactory.h"
 11#import "KWExistVerifier.h"
 12#import "KWMatchVerifier.h"
 13#import "KWAsyncVerifier.h"
 14#import "KWFailure.h"
 15#import "KWContextNode.h"
 16#import "KWBeforeEachNode.h"
 17#import "KWBeforeAllNode.h"
 18#import "KWItNode.h"
 19#import "KWAfterEachNode.h"
 20#import "KWAfterAllNode.h"
 21#import "KWPendingNode.h"
 22#import "KWRegisterMatchersNode.h"
 23#import "KWWorkarounds.h"
 24#import "KWIntercept.h"
 25#import "KWExampleNode.h"
 26#import "KWExampleSuite.h"
 27
 28
 29@interface KWExample ()
 30
 31@property (nonatomic, readonly) NSMutableArray *verifiers;
 32@property (nonatomic, readonly) KWMatcherFactory *matcherFactory;
 33@property (nonatomic, assign) id<KWExampleDelegate> delegate;
 34@property (nonatomic, assign) BOOL didNotFinish;
 35
 36- (void)reportResultForExampleNodeWithLabel:(NSString *)label;
 37
 38@end
 39
 40@implementation KWExample
 41
 42@synthesize matcherFactory;
 43@synthesize verifiers;
 44@synthesize delegate = _delegate;
 45@synthesize suite;
 46@synthesize lastInContexts;
 47@synthesize didNotFinish;
 48
 49- (id)initWithExampleNode:(id<KWExampleNode>)node
 50{
 51  if ((self = [super init])) {
 52    exampleNode = [node retain];
 53    matcherFactory = [[KWMatcherFactory alloc] init];
 54    verifiers = [[NSMutableArray alloc] init];
 55    lastInContexts = [[NSMutableArray alloc] init];
 56    passed = YES;
 57  }
 58  return self;
 59}
 60
 61- (void)dealloc 
 62{
 63  [lastInContexts release];
 64  [exampleNode release];
 65  [matcherFactory release];
 66  [verifiers release];
 67  [super dealloc];
 68}
 69
 70- (BOOL)isLastInContext:(KWContextNode *)context
 71{
 72  for (KWContextNode *contextWhereItLast in lastInContexts) {
 73    if (context == contextWhereItLast) {
 74      return YES;
 75    }
 76  }
 77  return NO;
 78}
 79
 80- (NSString *)description
 81{
 82  return [NSString stringWithFormat:@"<KWExample: %@>", exampleNode.description];
 83}
 84
 85#pragma mark - Adding Verifiers
 86
 87- (id)addVerifier:(id<KWVerifying>)aVerifier {
 88  if (![self.verifiers containsObject:aVerifier])
 89    [self.verifiers addObject:aVerifier];
 90  
 91  return aVerifier;
 92}
 93
 94- (id)addExistVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite {
 95  id verifier = [KWExistVerifier existVerifierWithExpectationType:anExpectationType callSite:aCallSite reporter:self];
 96  [self addVerifier:verifier];
 97  return verifier;
 98}
 99
100- (id)addMatchVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite {
101  id verifier = [KWMatchVerifier matchVerifierWithExpectationType:anExpectationType callSite:aCallSite matcherFactory:self.matcherFactory reporter:self];
102  [self addVerifier:verifier];
103  return verifier;
104}
105
106- (id)addAsyncVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite timeout:(NSInteger)timeout {
107  id verifier = [KWAsyncVerifier asyncVerifierWithExpectationType:anExpectationType callSite:aCallSite matcherFactory:self.matcherFactory reporter:self probeTimeout:timeout];
108  [self addVerifier:verifier];
109  return verifier;
110}
111
112#pragma mark - Running examples
113
114- (void)runWithDelegate:(id<KWExampleDelegate>)delegate;
115{
116  self.delegate = delegate;
117  [self.matcherFactory registerMatcherClassesWithNamespacePrefix:@"KW"];
118  [[KWExampleGroupBuilder sharedExampleGroupBuilder] setCurrentExample:self];
119  [exampleNode acceptExampleNodeVisitor:self]; 
120}
121
122#pragma mark - Reporting failure
123
124- (NSString *)descriptionForExampleContext {
125  NSMutableArray *parts = [NSMutableArray array];
126
127  for (KWContextNode *context in [[exampleNode contextStack] reverseObjectEnumerator]) {
128    if ([context description] != nil) {
129      [parts addObject:[[context description] stringByAppendingString:@","]];
130    }
131  }
132  
133  return [parts componentsJoinedByString:@" "];
134}
135
136- (KWFailure *)outputReadyFailureWithFailure:(KWFailure *)aFailure {
137  NSString *annotatedFailureMessage = [NSString stringWithFormat:@"'%@ %@' [FAILED], %@",
138                                       [self descriptionForExampleContext], [exampleNode description],
139                                       aFailure.message];
140  
141#if TARGET_IPHONE_SIMULATOR
142  // \uff1a is the unicode for a fill width colon, as opposed to a regular
143  // colon character (':'). This escape is performed so that Xcode doesn't
144  // truncate the error output in the build results window, which is running
145  // build time specs.
146  annotatedFailureMessage = [annotatedFailureMessage stringByReplacingOccurrencesOfString:@":" withString:@"\uff1a"];
147#endif // #if TARGET_IPHONE_SIMULATOR
148  
149  return [KWFailure failureWithCallSite:aFailure.callSite message:annotatedFailureMessage];
150}
151
152- (void)reportFailure:(KWFailure *)failure
153{
154  passed = NO;
155  [self.delegate example:self didFailWithFailure:[self outputReadyFailureWithFailure:failure]];
156}
157
158- (void)reportResultForExampleNodeWithLabel:(NSString *)label
159{
160  NSLog(@"+ '%@ %@' [%@]", [self descriptionForExampleContext], [exampleNode description], label);
161}
162
163#pragma mark - Full description with context
164
165/** Pending cases will be marked yellow by XCode as not finished, because their description differs for -[SenTestCaseRun start] and -[SenTestCaseRun stop] methods
166 */
167
168- (NSString *)pendingNotFinished {
169    BOOL reportPending = self.didNotFinish;
170    self.didNotFinish = YES;
171    return reportPending ? @"(PENDING)" : @"";
172}
173    
174- (NSString *)descriptionWithContext {
175    NSString *descriptionWithContext = [NSString stringWithFormat:@"%@ %@", 
176                                        [self descriptionForExampleContext], 
177                                        [exampleNode description] ? [exampleNode description] : @""];
178    BOOL isPending = [exampleNode isKindOfClass:[KWPendingNode class]];
179    return isPending ? [descriptionWithContext stringByAppendingString:[self pendingNotFinished]] : descriptionWithContext;
180}
181
182#pragma mark - Visiting Nodes
183
184- (void)visitRegisterMatchersNode:(KWRegisterMatchersNode *)aNode {
185  [self.matcherFactory registerMatcherClassesWithNamespacePrefix:aNode.namespacePrefix];
186}
187
188- (void)visitBeforeAllNode:(KWBeforeAllNode *)aNode {
189  if (aNode.block == nil)
190    return;
191  
192  aNode.block();
193}
194
195- (void)visitAfterAllNode:(KWAfterAllNode *)aNode {
196  if (aNode.block == nil)
197    return;
198  
199  aNode.block();
200}
201
202- (void)visitBeforeEachNode:(KWBeforeEachNode *)aNode {
203  if (aNode.block == nil)
204    return;
205  
206  aNode.block();
207}
208
209- (void)visitAfterEachNode:(KWAfterEachNode *)aNode {
210  if (aNode.block == nil)
211    return;
212  
213  aNode.block();
214}
215
216- (void)visitItNode:(KWItNode *)aNode {
217  if (aNode.block == nil || aNode != exampleNode)
218    return;
219  
220  aNode.example = self;
221  
222  [aNode.context performExample:self withBlock:^{
223  
224    @try {      
225      
226      aNode.block();
227      
228#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
229      NSException *invocationException = KWGetAndClearExceptionFromAcrossInvocationBoundary();
230      [invocationException raise];
231#endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
232      
233      // Finish verifying and clear
234      for (id<KWVerifying> verifier in self.verifiers) {
235        [verifier exampleWillEnd];
236      }
237      
238    } @catch (NSException *exception) {
239      KWFailure *failure = [KWFailure failureWithCallSite:aNode.callSite format:@"%@ \"%@\" raised",
240                            [exception name],
241                            [exception reason]];
242      [self reportFailure:failure];
243    }
244    
245    if (passed) {
246      [self reportResultForExampleNodeWithLabel:@"PASSED"];
247    }
248    
249    // Always clear stubs and spies at the end of it blocks
250      KWClearStubsAndSpies();
251  }];
252}
253
254- (void)visitPendingNode:(KWPendingNode *)aNode {
255  if (aNode != exampleNode)
256    return;
257
258  [self reportResultForExampleNodeWithLabel:@"PENDING"];
259}
260
261- (NSString *)generateDescriptionForAnonymousItNode
262{
263  // anonymous specify blocks should only have one verifier, but use the first in any case
264  return [(self.verifiers)[0] descriptionForAnonymousItNode];
265}
266
267@end
268
269#pragma mark -
270#pragma mark Building Example Groups
271
272void describe(NSString *aDescription, KWVoidBlock aBlock) {
273    describeWithCallSite(nil, aDescription, aBlock);
274}
275
276void context(NSString *aDescription, KWVoidBlock aBlock) {
277    contextWithCallSite(nil, aDescription, aBlock);
278}
279
280void registerMatchers(NSString *aNamespacePrefix) {
281    registerMatchersWithCallSite(nil, aNamespacePrefix);
282}
283
284void beforeAll(KWVoidBlock aBlock) {
285    beforeAllWithCallSite(nil, aBlock);
286}
287
288void afterAll(KWVoidBlock aBlock) {
289    afterAllWithCallSite(nil, aBlock);
290}
291
292void beforeEach(KWVoidBlock aBlock) {
293    beforeEachWithCallSite(nil, aBlock);
294}
295
296void afterEach(KWVoidBlock aBlock) {
297    afterEachWithCallSite(nil, aBlock);
298}
299
300void it(NSString *aDescription, KWVoidBlock aBlock) {
301    itWithCallSite(nil, aDescription, aBlock);
302}
303
304void specify(KWVoidBlock aBlock)
305{
306    itWithCallSite(nil, nil, aBlock);
307}
308
309void pending_(NSString *aDescription, KWVoidBlock ignoredBlock) {
310    pendingWithCallSite(nil, aDescription, ignoredBlock);
311}
312
313void describeWithCallSite(KWCallSite *aCallSite, NSString *aDescription, KWVoidBlock aBlock) {
314    contextWithCallSite(aCallSite, aDescription, aBlock);
315}
316
317void contextWithCallSite(KWCallSite *aCallSite, NSString *aDescription, KWVoidBlock aBlock) {
318    [[KWExampleGroupBuilder sharedExampleGroupBuilder] pushContextNodeWithCallSite:aCallSite description:aDescription];
319    aBlock();
320    [[KWExampleGroupBuilder sharedExampleGroupBuilder] popContextNode];
321}
322
323void registerMatchersWithCallSite(KWCallSite *aCallSite, NSString *aNamespacePrefix) {
324    [[KWExampleGroupBuilder sharedExampleGroupBuilder] setRegisterMatchersNodeWithCallSite:aCallSite namespacePrefix:aNamespacePrefix];
325}
326
327void beforeAllWithCallSite(KWCallSite *aCallSite, KWVoidBlock aBlock) {
328    [[KWExampleGroupBuilder sharedExampleGroupBuilder] setBeforeAllNodeWithCallSite:aCallSite block:aBlock];
329}
330
331void afterAllWithCallSite(KWCallSite *aCallSite, KWVoidBlock aBlock) {
332    [[KWExampleGroupBuilder sharedExampleGroupBuilder] setAfterAllNodeWithCallSite:aCallSite block:aBlock];
333}
334
335void beforeEachWithCallSite(KWCallSite *aCallSite, KWVoidBlock aBlock) {
336    [[KWExampleGroupBuilder sharedExampleGroupBuilder] setBeforeEachNodeWithCallSite:aCallSite block:aBlock];
337}
338
339void afterEachWithCallSite(KWCallSite *aCallSite, KWVoidBlock aBlock) {
340    [[KWExampleGroupBuilder sharedExampleGroupBuilder] setAfterEachNodeWithCallSite:aCallSite block:aBlock];
341}
342
343void itWithCallSite(KWCallSite *aCallSite, NSString *aDescription, KWVoidBlock aBlock) {
344    [[KWExampleGroupBuilder sharedExampleGroupBuilder] addItNodeWithCallSite:aCallSite description:aDescription block:aBlock];
345}
346
347void pendingWithCallSite(KWCallSite *aCallSite, NSString *aDescription, KWVoidBlock ignoredBlock) {
348    [[KWExampleGroupBuilder sharedExampleGroupBuilder] addPendingNodeWithCallSite:aCallSite description:aDescription];
349}