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}