main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWMatchVerifier.h"
  8#import "KWFailure.h"
  9#import "KWFormatter.h"
 10#import "KWInvocationCapturer.h"
 11#import "KWMatcherFactory.h"
 12#import "KWReporting.h"
 13#import "KWStringUtilities.h"
 14#import "KWWorkarounds.h"
 15#import "NSInvocation+KiwiAdditions.h"
 16#import "NSMethodSignature+KiwiAdditions.h"
 17
 18@interface KWMatchVerifier()
 19
 20#pragma mark -
 21#pragma mark Properties
 22
 23@property (nonatomic, readwrite, retain) id<KWMatching> endOfExampleMatcher;
 24@property (nonatomic, readwrite, retain) id<KWMatching> matcher;
 25
 26@end
 27
 28@implementation KWMatchVerifier
 29
 30@synthesize matcher;
 31
 32#pragma mark -
 33#pragma mark Initializing
 34
 35- (id)initForShouldWithCallSite:(KWCallSite *)aCallSite matcherFactory:(KWMatcherFactory *)aMatcherFactory reporter:(id<KWReporting>)aReporter {
 36    return [self initWithExpectationType:KWExpectationTypeShould callSite:aCallSite matcherFactory:aMatcherFactory reporter:aReporter];
 37}
 38
 39- (id)initForShouldNotWithCallSite:(KWCallSite *)aCallSite matcherFactory:(KWMatcherFactory *)aMatcherFactory reporter:(id<KWReporting>)aReporter {
 40    return [self initWithExpectationType:KWExpectationTypeShouldNot callSite:aCallSite matcherFactory:aMatcherFactory reporter:aReporter];
 41}
 42
 43- (id)initWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite matcherFactory:(KWMatcherFactory *)aMatcherFactory reporter:(id<KWReporting>)aReporter {
 44    if ((self = [super init])) {
 45        expectationType = anExpectationType;
 46        callSite = [aCallSite retain];
 47        matcherFactory = aMatcherFactory;
 48        reporter = aReporter;
 49    }
 50
 51    return self;
 52}
 53
 54+ (id)matchVerifierWithExpectationType:(KWExpectationType)anExpectationType callSite:(KWCallSite *)aCallSite matcherFactory:(KWMatcherFactory *)aMatcherFactory reporter:(id<KWReporting>)aReporter {
 55    return [[[self alloc] initWithExpectationType:anExpectationType callSite:aCallSite matcherFactory:aMatcherFactory reporter:aReporter] autorelease];
 56}
 57
 58- (void)dealloc {
 59    [subject release];
 60    [callSite release];
 61    [matcher release];
 62    [endOfExampleMatcher release];
 63    [super dealloc];
 64}
 65
 66- (NSString *)descriptionForAnonymousItNode
 67{
 68  NSString *typeString = @"";
 69
 70  switch (self.expectationType) {
 71    case KWExpectationTypeShould:
 72      typeString = @"should";
 73      break;
 74    case KWExpectationTypeShouldNot:
 75      typeString = @"should not";
 76  }
 77  id<KWMatching> actualMatcher = (self.endOfExampleMatcher == nil) ? self.matcher : self.endOfExampleMatcher;
 78  return [NSString stringWithFormat:@"%@ %@", typeString, actualMatcher];
 79}
 80
 81#pragma mark -
 82#pragma mark Properties
 83
 84@synthesize expectationType;
 85@synthesize callSite;
 86@synthesize matcherFactory;
 87@synthesize reporter;
 88@synthesize subject;
 89@synthesize endOfExampleMatcher;
 90
 91#pragma mark -
 92#pragma mark Verifying
 93
 94- (void)verifyWithMatcher:(id<KWMatching>)aMatcher {
 95    @try {
 96        BOOL matchResult = [aMatcher evaluate];
 97
 98        if (self.expectationType == KWExpectationTypeShould && !matchResult) {
 99            NSString *message = [aMatcher failureMessageForShould];
100            KWFailure *failure = [KWFailure failureWithCallSite:self.callSite message:message];
101            [self.reporter reportFailure:failure];
102        } else if (self.expectationType == KWExpectationTypeShouldNot && matchResult) {
103            NSString *message = [aMatcher failureMessageForShouldNot];
104            KWFailure *failure = [KWFailure failureWithCallSite:self.callSite message:message];
105            [self.reporter reportFailure:failure];
106        }
107    } @catch (NSException *exception) {
108        KWFailure *failure = [KWFailure failureWithCallSite:self.callSite message:[exception description]];
109        [self.reporter reportFailure:failure];
110    }
111}
112
113#pragma mark -
114#pragma mark Ending Examples
115
116- (void)exampleWillEnd {
117    if (self.endOfExampleMatcher == nil)
118        return;
119
120    [self verifyWithMatcher:self.endOfExampleMatcher];
121}
122
123#pragma mark -
124#pragma mark Handling Invocations
125
126- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
127    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
128
129    if (signature != nil)
130        return signature;
131
132    signature = [self.matcherFactory methodSignatureForMatcherSelector:aSelector];
133
134    if (signature != nil)
135        return signature;
136
137    // Return a dummy method signature so that problems can be handled in
138    // -forwardInvocation:.
139    NSString *encoding = KWEncodingForVoidMethod();
140    return [NSMethodSignature signatureWithObjCTypes:[encoding UTF8String]];
141}
142
143- (void)forwardInvocation:(NSInvocation *)anInvocation {
144#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
145    @try {
146#endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
147
148    self.matcher = (id<KWMatching>)[self.matcherFactory matcherFromInvocation:anInvocation subject:self.subject];
149
150    if (self.matcher == nil) {
151      KWFailure *failure = [KWFailure failureWithCallSite:self.callSite format:@"could not create matcher for -%@",
152                 NSStringFromSelector(anInvocation.selector)];
153      [self.reporter reportFailure:failure];
154    }
155    [anInvocation invokeWithTarget:self.matcher];
156
157#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
158    // A matcher might have set an exception within the -invokeWithTarget, so
159    // raise if one was set.
160    NSException *exception = KWGetAndClearExceptionFromAcrossInvocationBoundary();
161    [exception raise];
162#endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
163
164    if ([self.matcher respondsToSelector:@selector(shouldBeEvaluatedAtEndOfExample)] && [self.matcher shouldBeEvaluatedAtEndOfExample]) {
165        self.endOfExampleMatcher = self.matcher;
166        self.matcher = nil;
167    }
168    else {
169        [self verifyWithMatcher:self.matcher];
170    }
171
172#if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
173    } @catch (NSException *exception) {
174        KWFailure *failure = [KWFailure failureWithCallSite:self.callSite format:[exception reason]];
175        [self.reporter reportFailure:failure];
176        return;
177    }
178#endif // #if KW_TARGET_HAS_INVOCATION_EXCEPTION_BUG
179}
180
181@end