]> git.wincent.com - WOTest.git/blob - WOMock.m
Remove private API support code
[WOTest.git] / WOMock.m
1 //
2 //  WOMock.m
3 //  WOTest
4 //
5 //  Created by Wincent Colaiuta on 14 June 2005.
6 //
7 //  Copyright 2005-2007 Wincent Colaiuta.
8 //  This program is free software: you can redistribute it and/or modify
9 //  it under the terms of the GNU General Public License as published by
10 //  the Free Software Foundation, either version 3 of the License, or
11 //  (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 //
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 //
21
22 // class header
23 #import "WOMock.h"
24
25 // system headers
26 #import <objc/objc-class.h>
27
28 // framework headers
29 #import "NSInvocation+WOTest.h"
30 #import "NSObject+WOTest.h"
31 #import "NSProxy+WOTest.h"
32 #import "NSValue+WOTest.h"
33 #import "WOClassMock.h"
34 #import "WOObjectMock.h"
35 #import "WOProtocolMock.h"
36
37 @implementation WOMock
38
39 #pragma mark -
40 #pragma mark Creation
41
42 + (id)mockForObjectClass:(Class)aClass
43 {
44     // avoid infinite loops if called by subclass
45     if (self != [WOMock class])
46         [NSException raise:NSInternalInconsistencyException format:@"mockForObjectClass: called from WOMock subclass"];
47
48     return [WOObjectMock mockForClass:aClass];
49 }
50
51 + (id)mockForClass:(Class)aClass
52 {
53     // avoid infinite loops if called by subclass
54     if (self != [WOMock class])
55         [NSException raise:NSInternalInconsistencyException format:@"mockForClass: called from WOMock subclass"];
56
57     return [WOClassMock mockForClass:aClass];
58 }
59
60 + (id)mockForProtocol:(Protocol *)aProtocol
61 {
62     // avoid infinite loops if called by subclass
63     if (self != [WOMock class])
64         [NSException raise:NSInternalInconsistencyException format:@"mockForProtocol: called from WOMock subclass"];
65
66     return [WOProtocolMock mockForProtocol:aProtocol];
67 }
68
69 // a true Apple-style cluster would do this by allocating a placeholder object
70 - (id)initWithObjectClass:(Class)aClass
71 {
72     // avoid infinite loops if called by subclass
73     if ([self class] != [WOMock class])
74         [NSException raise:NSInternalInconsistencyException format:@"initWithObjectClass: called from WOMock subclass"];
75
76     return [[WOObjectMock allocWithZone:[self zone]] initWithClass:aClass];
77 }
78
79 // a true Apple-style cluster would do this by allocating a placeholder object
80 - (id)initWithClass:(Class)aClass
81 {
82     // avoid infinite loops if called by subclass
83     if ([self class] != [WOMock class])
84         [NSException raise:NSInternalInconsistencyException format:@"initWithClass: called from WOMock subclass"];
85
86     return [[WOClassMock allocWithZone:[self zone]] initWithClass:aClass];
87 }
88
89 // a true Apple-style cluster would do this by allocating a placeholder object
90 - (id)initWithProtocol:(Protocol *)aProtocol
91 {
92     // avoid infinite loops if called by subclass
93     if ([self class] != [WOMock class])
94         [NSException raise:NSInternalInconsistencyException format:@"initWithProtocol: called from WOMock subclass"];
95
96     return [[WOProtocolMock allocWithZone:[self zone]] initWithProtocol:aProtocol];
97 }
98
99 - (id)init
100 {
101     // super (NSProxy) has no init method
102     expectedInOrder     = [NSMutableArray array];               // there are no accessors (to avoid namespace pollution)
103     accepted            = [NSMutableSet set];
104     acceptedOnce        = [NSMutableSet set];
105     expected            = [NSMutableSet set];
106     expectedOnce        = [NSMutableSet set];
107     rejected            = [NSMutableSet set];
108     methodSignatures    = [NSMutableDictionary dictionary];
109     return self;
110 }
111
112 // TODO: given that the time at which finalize is called is indeterminate consider coming up with some other mechanism for
113 // triggering verification
114 - (void)finalize
115 {
116     [self verify];
117     [super finalize];
118 }
119
120 - (id)accept
121 {
122     [NSException raise:NSInternalInconsistencyException format:@"accept invoked on WOMock abstract class"];
123     return nil;
124 }
125
126 - (id)acceptOnce
127 {
128     [NSException raise:NSInternalInconsistencyException format:@"acceptOnce invoked on WOMock abstract class"];
129     return nil;
130 }
131
132 - (id)reject
133 {
134     [NSException raise:NSInternalInconsistencyException format:@"reject invoked on WOMock abstract class"];
135     return nil;
136 }
137
138 - (id)expect
139 {
140     [NSException raise:NSInternalInconsistencyException format:@"expect invoked on WOMock abstract class"];
141     return nil;
142 }
143
144 - (id)expectOnce
145 {
146     [NSException raise:NSInternalInconsistencyException format:@"expectOnce invoked on WOMock abstract class"];
147     return nil;
148 }
149
150 - (id)expectInOrder
151 {
152     [NSException raise:NSInternalInconsistencyException format:@"expectInOrder invoked on WOMock abstract class"];
153     return nil;
154 }
155
156 - (void)clear
157 {
158     [accepted           removeAllObjects];
159     [acceptedOnce       removeAllObjects];
160     [expected           removeAllObjects];
161     [expectedOnce       removeAllObjects];
162     [expectedInOrder    removeAllObjects];
163     [rejected           removeAllObjects];
164 }
165
166 - (void)verify
167 {
168     NSAssert(([expected count] == 0),           @"verification failure ('expected' set not empty)");
169     NSAssert(([expectedOnce count] == 0),       @"verification failure ('expectedOnce' set not empty)");
170     NSAssert(([expectedInOrder count] == 0),    @"verification failure ('expectedInOrder' set not empty)");
171 }
172
173 #pragma mark -
174 #pragma mark Utility methods
175
176 - (void)storeReturnValue:(NSValue *)aValue forInvocation:(NSInvocation *)invocation
177 {
178     NSParameterAssert(invocation != nil);
179     if (!aValue) return; // nothing to do
180     const char *returnType = [[invocation methodSignature] methodReturnType];
181     const char *storedType = [aValue objCType];
182     NSAssert2((strcmp(returnType, storedType) == 0),
183              @"Cannot store mismatched return type in invocation (%s, %s)", returnType, storedType);
184
185     size_t bufferSize = [aValue WOTest_bufferSize];
186     void *buffer = malloc(bufferSize);
187     NSAssert1((buffer != NULL), @"malloc() failed (size %d)", bufferSize);
188     [aValue getValue:buffer];
189     [invocation setReturnValue:buffer];
190 }
191
192 - (void)setObjCTypes:(NSString *)types forSelector:(SEL)aSelector
193 {
194     [methodSignatures setObject:[NSMethodSignature signatureWithObjCTypes:[types UTF8String]]
195                          forKey:NSStringFromSelector(aSelector)];
196 }
197
198 #pragma mark -
199 #pragma mark NSProxy (private)
200
201 - forward:(SEL)sel :(marg_list)args
202 {
203     // let standard event flow take place (but note that NSProxy implementation raises so subclasses must do the real work)
204     if ([self methodSignatureForSelector:sel])
205         return [super forward:sel :args];
206
207     // fallback to internal lookup
208     NSMethodSignature *signature = [methodSignatures objectForKey:NSStringFromSelector(sel)];
209
210     // at this point it would be great to be able to improvise but it's not possible to figure out an accurate method signature
211     NSAssert((signature != nil), ([NSString stringWithFormat:@"no method signature for selector %@", NSStringFromSelector(sel)]));
212
213     NSInvocation *forwardInvocation = [NSInvocation invocationWithMethodSignature:signature];
214     [forwardInvocation setSelector:sel];
215
216     // store arguments in invocation
217     int offset = 0;
218     for (unsigned i = 0, max = [signature numberOfArguments]; i < max; i++)
219     {
220         const char *type = [signature getArgumentTypeAtIndex:i];    // always id, SEL, ...
221
222 #if defined(__ppc__)
223
224         // TODO: finish version in WOStub and copy it here
225         // leave the compiler warnings about "unused variable 'type'" and "unused variable 'offset'" to remind me to do it
226         // may be able to use libffi to help here
227
228 #elif defined(__i386__)
229
230         // on i386 the marg_getRef macro and its helper, marg_adjustedOffset, should work fine
231         if (strcmp(type, @encode(id)) == 0)
232         {
233             id *ref = marg_getRef(args, offset, id);
234             [forwardInvocation WOTest_setArgumentValue:[NSValue valueWithBytes:ref objCType:type] atIndex:i];
235         }
236         else if (strcmp(type, @encode(SEL)) == 0)
237         {
238             SEL *ref = marg_getRef(args, offset, SEL);
239             [forwardInvocation WOTest_setArgumentValue:[NSValue valueWithBytes:ref objCType:type] atIndex:i];
240         }
241         else
242             [NSException raise:NSGenericException format:@"type %s not supported", type];
243
244         offset += [NSValue WOTest_sizeForType:[NSString stringWithUTF8String:type]];
245
246 #elif defined(__ppc64__)
247         // there is no objc-msg-ppc.s so for now just omit support rather than make assumptions
248 #error ppc64 not supported yet
249
250 #else
251
252 #error Unsupported architecture
253
254 #endif
255
256     }
257     [self forwardInvocation:forwardInvocation]; // stores return value in invocation
258
259     unsigned bufferSize = [signature methodReturnLength];
260     void *returnBuffer = malloc(bufferSize);
261     NSAssert1((returnBuffer != NULL), @"malloc() failed (size %d)", bufferSize);
262     [forwardInvocation getReturnValue:&returnBuffer];
263     return returnBuffer; // TODO: cast according to the return type
264 }
265
266 #pragma mark -
267 #pragma mark Accessors
268
269 - (BOOL)acceptsByDefault
270 {
271     return acceptsByDefault;
272 }
273
274 - (void)setAcceptsByDefault:(BOOL)flag
275 {
276     acceptsByDefault = flag;
277 }
278
279 - (NSMutableDictionary *)methodSignatures
280 {
281     return methodSignatures;
282 }
283
284 @end