main
1//
2// Licensed under the terms in License.txt
3//
4// Copyright 2010 Allen Ding. All rights reserved.
5//
6
7#import "KWStub.h"
8#import "KWMessagePattern.h"
9#import "KWObjCUtilities.h"
10#import "KWStringUtilities.h"
11#import "KWValue.h"
12
13#import "NSInvocation+OCMAdditions.h"
14
15@implementation KWStub
16
17#pragma mark -
18#pragma mark Initializing
19
20- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern {
21 return [self initWithMessagePattern:aMessagePattern value:nil];
22}
23
24- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue {
25 if ((self = [super init])) {
26 messagePattern = [aMessagePattern retain];
27 value = [aValue retain];
28 }
29
30 return self;
31}
32
33- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern block:(id (^)(NSArray *params))aBlock {
34 if ((self = [super init])) {
35 messagePattern = [aMessagePattern retain];
36 block = [aBlock copy];
37 }
38
39 return self;
40}
41
42- (id)initWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue times:(id)times afterThatReturn:(id)aSecondValue {
43 if ((self = [super init])) {
44 messagePattern = [aMessagePattern retain];
45 value = [aValue retain];
46 returnValueTimes = [times retain];
47 secondValue = [aSecondValue retain];
48 }
49
50 return self;
51}
52
53+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern {
54 return [self stubWithMessagePattern:aMessagePattern value:nil];
55}
56
57+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue {
58 return [[[self alloc] initWithMessagePattern:aMessagePattern value:aValue] autorelease];
59}
60
61+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern block:(id (^)(NSArray *params))aBlock {
62 return [[[self alloc] initWithMessagePattern:aMessagePattern block:aBlock] autorelease];
63}
64
65+ (id)stubWithMessagePattern:(KWMessagePattern *)aMessagePattern value:(id)aValue times:(id)times afterThatReturn:(id)aSecondValue {
66 return [[[self alloc] initWithMessagePattern:aMessagePattern value:aValue times:times afterThatReturn:aSecondValue] autorelease];
67}
68
69- (void)dealloc {
70 [messagePattern release];
71 [value release];
72 [returnValueTimes release];
73 [secondValue release];
74 [block release];
75 [super dealloc];
76}
77
78#pragma mark -
79#pragma mark Properties
80
81@synthesize messagePattern;
82@synthesize value;
83@synthesize secondValue;
84@synthesize returnValueTimes;
85@synthesize returnedValueTimes;
86
87#pragma mark -
88#pragma mark Processing Invocations
89
90- (void)writeZerosToInvocationReturnValue:(NSInvocation *)anInvocation {
91 NSUInteger returnLength = [[anInvocation methodSignature] methodReturnLength];
92
93 if (returnLength == 0)
94 return;
95
96 void *bytes = malloc(returnLength);
97 memset(bytes, 0, returnLength);
98 [anInvocation setReturnValue:bytes];
99 free(bytes);
100}
101
102- (NSData *)valueDataWithObjCType:(const char *)objCType {
103 assert(self.value && "self.value must not be nil");
104 NSData *data = [self.value dataForObjCType:objCType];
105
106 if (data == nil) {
107 [NSException raise:@"KWStubException" format:@"wrapped stub value type (%s) could not be converted to the target type (%s)",
108 [self.value objCType],
109 objCType];
110 }
111
112 return data;
113}
114
115- (void)writeWrappedValueToInvocationReturnValue:(NSInvocation *)anInvocation {
116 assert(self.value && "self.value must not be nil");
117 const char *returnType = [[anInvocation methodSignature] methodReturnType];
118 NSData *data = nil;
119
120 NSData *choosedForData = [self.value dataValue];
121
122 if (returnValueTimes != nil) {
123 NSString *returnValueTimesString = returnValueTimes;
124 int returnValueTimesInt = [returnValueTimesString intValue];
125
126 if (returnedValueTimes >= returnValueTimesInt) {
127 choosedForData = [self.secondValue dataValue];
128 }
129 returnedValueTimes++;
130 }
131
132
133 // When the return type is not the same as the type of the wrapped value,
134 // attempt to convert the wrapped value to the desired type.
135
136 if (KWObjCTypeEqualToObjCType([self.value objCType], returnType))
137 data = choosedForData;
138 else
139 data = [self valueDataWithObjCType:returnType];
140
141 [anInvocation setReturnValue:(void *)[data bytes]];
142}
143
144- (void)writeObjectValueToInvocationReturnValue:(NSInvocation *)anInvocation {
145 assert(self.value && "self.value must not be nil");
146
147 void *choosedForData = &value;
148
149 if (returnValueTimes != nil) {
150 NSString *returnValueTimesString = returnValueTimes;
151 int returnValueTimesInt = [returnValueTimesString intValue];
152
153 if (returnedValueTimes >= returnValueTimesInt) {
154 choosedForData = &secondValue;
155 }
156 returnedValueTimes++;
157 }
158
159 [anInvocation setReturnValue:choosedForData];
160
161#ifndef __clang_analyzer__
162 NSString *selectorString = NSStringFromSelector([anInvocation selector]);
163
164 // To conform to memory management conventions, retain if writing a result
165 // that begins with alloc, new or contains copy. This shows up as a false
166 // positive in clang due to the runtime conditional, so ignore it.
167 if (KWStringHasWordPrefix(selectorString, @"alloc") ||
168 KWStringHasWordPrefix(selectorString, @"new") ||
169 KWStringHasWord(selectorString, @"copy") ||
170 KWStringHasWord(selectorString, @"Copy")) {
171 [self.value retain];
172 }
173#endif
174}
175
176- (BOOL)processInvocation:(NSInvocation *)anInvocation {
177 if (![self.messagePattern matchesInvocation:anInvocation])
178 return NO;
179
180 if (block) {
181 NSUInteger numberOfArguments = [[anInvocation methodSignature] numberOfArguments];
182 NSMutableArray *args = [NSMutableArray arrayWithCapacity:(numberOfArguments-2)];
183 for (NSUInteger i = 2; i < numberOfArguments; ++i) {
184 id arg = [anInvocation getArgumentAtIndexAsObject:i];
185
186 const char *argType = [[anInvocation methodSignature] getArgumentTypeAtIndex:i];
187 if (strcmp(argType, "@?") == 0) arg = [[arg copy] autorelease];
188
189 if (arg == nil)
190 arg = [NSNull null];
191
192 [args addObject:arg];
193 }
194
195 id newValue = block(args);
196 if (newValue != value) {
197 [value release];
198 value = [newValue retain];
199 }
200
201 [args removeAllObjects]; // We don't want these objects to be in autorelease pool
202 }
203
204 if (self.value == nil)
205 [self writeZerosToInvocationReturnValue:anInvocation];
206 else if ([self.value isKindOfClass:[KWValue class]])
207 [self writeWrappedValueToInvocationReturnValue:anInvocation];
208 else
209 [self writeObjectValueToInvocationReturnValue:anInvocation];
210
211 return YES;
212}
213
214#pragma mark -
215#pragma mark Debugging
216
217- (NSString *)description {
218 return [NSString stringWithFormat:@"messagePattern: %@\nvalue: %@", self.messagePattern, self.value];
219}
220
221@end