OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (c) 2014-2015 Erik Doernenburg and contributors |
| 3 * |
| 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 5 * not use these files except in compliance with the License. You may obtain |
| 6 * a copy of the License at |
| 7 * |
| 8 * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 * |
| 10 * Unless required by applicable law or agreed to in writing, software |
| 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 13 * License for the specific language governing permissions and limitations |
| 14 * under the License. |
| 15 */ |
| 16 |
| 17 #import <objc/runtime.h> |
| 18 #import "OCMFunctionsPrivate.h" |
| 19 #import "OCMLocation.h" |
| 20 #import "OCClassMockObject.h" |
| 21 #import "OCPartialMockObject.h" |
| 22 |
| 23 |
| 24 #pragma mark Known private API |
| 25 |
| 26 @interface NSException(OCMKnownExceptionMethods) |
| 27 + (NSException *)failureInFile:(NSString *)file atLine:(int)line withDescription
:(NSString *)formatString, ...; |
| 28 @end |
| 29 |
| 30 @interface NSObject(OCMKnownTestCaseMethods) |
| 31 - (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)
file atLine:(NSUInteger)line expected:(BOOL)expected; |
| 32 - (void)failWithException:(NSException *)exception; |
| 33 @end |
| 34 |
| 35 |
| 36 #pragma mark Functions related to ObjC type system |
| 37 |
| 38 BOOL OCMIsObjectType(const char *objCType) |
| 39 { |
| 40 objCType = OCMTypeWithoutQualifiers(objCType); |
| 41 |
| 42 if(strcmp(objCType, @encode(id)) == 0 || strcmp(objCType, @encode(Class)) ==
0) |
| 43 return YES; |
| 44 |
| 45 // if the returnType is a typedef to an object, it has the form ^{OriginClas
s=#} |
| 46 NSString *regexString = @"^\\^\\{(.*)=#.*\\}"; |
| 47 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPatte
rn:regexString options:0 error:NULL]; |
| 48 NSString *type = [NSString stringWithCString:objCType encoding:NSASCIIString
Encoding]; |
| 49 if([regex numberOfMatchesInString:type options:0 range:NSMakeRange(0, type.l
ength)] > 0) |
| 50 return YES; |
| 51 |
| 52 // if the return type is a block we treat it like an object |
| 53 // TODO: if the runtime were to encode the block's argument and/or return ty
pes, this test would not be sufficient |
| 54 if(strcmp(objCType, @encode(void(^)())) == 0) |
| 55 return YES; |
| 56 |
| 57 return NO; |
| 58 } |
| 59 |
| 60 |
| 61 const char *OCMTypeWithoutQualifiers(const char *objCType) |
| 62 { |
| 63 while(strchr("rnNoORV", objCType[0]) != NULL) |
| 64 objCType += 1; |
| 65 return objCType; |
| 66 } |
| 67 |
| 68 |
| 69 /* |
| 70 * Sometimes an external type is an opaque struct (which will have an @encode of
"{structName}" |
| 71 * or "{structName=}") but the actual method return type, or property type, will
know the contents |
| 72 * of the struct (so will have an objcType of say "{structName=iiSS}". This fun
ction will determine |
| 73 * those are equal provided they have the same structure name, otherwise everyth
ing else will be |
| 74 * compared textually. This can happen particularly for pointers to such struct
ures, which still |
| 75 * encode what is being pointed to. |
| 76 * |
| 77 * For some types some runtime functions throw exceptions, which is why we wrap
this in an |
| 78 * exception handler just below. |
| 79 */ |
| 80 static BOOL OCMEqualTypesAllowingOpaqueStructsInternal(const char *type1, const
char *type2) |
| 81 { |
| 82 type1 = OCMTypeWithoutQualifiers(type1); |
| 83 type2 = OCMTypeWithoutQualifiers(type2); |
| 84 |
| 85 switch (type1[0]) |
| 86 { |
| 87 case '{': |
| 88 case '(': |
| 89 { |
| 90 if (type2[0] != type1[0]) |
| 91 return NO; |
| 92 char endChar = type1[0] == '{'? '}' : ')'; |
| 93 |
| 94 const char *type1End = strchr(type1, endChar); |
| 95 const char *type2End = strchr(type2, endChar); |
| 96 const char *type1Equals = strchr(type1, '='); |
| 97 const char *type2Equals = strchr(type2, '='); |
| 98 |
| 99 /* Opaque types either don't have an equals sign (just the name and
the end brace), or |
| 100 * empty content after the equals sign. |
| 101 * We want that to compare the same as a type of the same name but w
ith the content. |
| 102 */ |
| 103 BOOL type1Opaque = (type1Equals == NULL || (type1End < type1Equals)
|| type1Equals[1] == endChar); |
| 104 BOOL type2Opaque = (type2Equals == NULL || (type2End < type2Equals)
|| type2Equals[1] == endChar); |
| 105 const char *type1NameEnd = (type1Equals == NULL || (type1End < type1
Equals)) ? type1End : type1Equals; |
| 106 const char *type2NameEnd = (type2Equals == NULL || (type2End < type2
Equals)) ? type2End : type2Equals; |
| 107 intptr_t type1NameLen = type1NameEnd - type1; |
| 108 intptr_t type2NameLen = type2NameEnd - type2; |
| 109 |
| 110 /* If the names are not equal, return NO */ |
| 111 if (type1NameLen != type2NameLen || strncmp(type1, type2, type1NameL
en)) |
| 112 return NO; |
| 113 |
| 114 /* If the same name, and at least one is opaque, that is close enoug
h. */ |
| 115 if (type1Opaque || type2Opaque) |
| 116 return YES; |
| 117 |
| 118 /* Otherwise, compare all the elements. Use NSGetSizeAndAlignment t
o walk through the struct elements. */ |
| 119 type1 = type1Equals + 1; |
| 120 type2 = type2Equals + 1; |
| 121 while (type1[0] != endChar && type1[0] != '\0') |
| 122 { |
| 123 if (!OCMEqualTypesAllowingOpaqueStructs(type1, type2)) |
| 124 return NO; |
| 125 type1 = NSGetSizeAndAlignment(type1, NULL, NULL); |
| 126 type2 = NSGetSizeAndAlignment(type2, NULL, NULL); |
| 127 } |
| 128 return YES; |
| 129 } |
| 130 case '^': |
| 131 /* for a pointer, make sure the other is a pointer, then recursively
compare the rest */ |
| 132 if (type2[0] != type1[0]) |
| 133 return NO; |
| 134 return OCMEqualTypesAllowingOpaqueStructs(type1 + 1, type2 + 1); |
| 135 |
| 136 case '?': |
| 137 return type2[0] == '?'; |
| 138 |
| 139 case '\0': |
| 140 return type2[0] == '\0'; |
| 141 |
| 142 default: |
| 143 { |
| 144 // Move the type pointers past the current types, then compare that
region |
| 145 const char *afterType1 = NSGetSizeAndAlignment(type1, NULL, NULL); |
| 146 const char *afterType2 = NSGetSizeAndAlignment(type2, NULL, NULL); |
| 147 intptr_t type1Len = afterType1 - type1; |
| 148 intptr_t type2Len = afterType2 - type2; |
| 149 |
| 150 return (type1Len == type2Len && (strncmp(type1, type2, type1Len) ==
0)); |
| 151 } |
| 152 } |
| 153 } |
| 154 |
| 155 BOOL OCMEqualTypesAllowingOpaqueStructs(const char *type1, const char *type2) |
| 156 { |
| 157 @try |
| 158 { |
| 159 return OCMEqualTypesAllowingOpaqueStructsInternal(type1, type2); |
| 160 } |
| 161 @catch (NSException *e) |
| 162 { |
| 163 /* Probably a bitfield or something that NSGetSizeAndAlignment chokes on
, oh well */ |
| 164 return NO; |
| 165 } |
| 166 } |
| 167 |
| 168 |
| 169 #pragma mark Creating classes |
| 170 |
| 171 Class OCMCreateSubclass(Class class, void *ref) |
| 172 { |
| 173 const char *className = [[NSString stringWithFormat:@"%@-%p-%u", NSStringFro
mClass(class), ref, arc4random()] UTF8String]; |
| 174 Class subclass = objc_allocateClassPair(class, className, 0); |
| 175 objc_registerClassPair(subclass); |
| 176 return subclass; |
| 177 } |
| 178 |
| 179 |
| 180 #pragma mark Directly manipulating the isa pointer (look away) |
| 181 |
| 182 void OCMSetIsa(id object, Class class) |
| 183 { |
| 184 *((Class *)object) = class; |
| 185 } |
| 186 |
| 187 Class OCMGetIsa(id object) |
| 188 { |
| 189 return *((Class *)object); |
| 190 } |
| 191 |
| 192 |
| 193 #pragma mark Alias for renaming real methods |
| 194 |
| 195 static NSString *const OCMRealMethodAliasPrefix = @"ocmock_replaced_"; |
| 196 static const char *const OCMRealMethodAliasPrefixCString = "ocmock_replaced_"; |
| 197 |
| 198 BOOL OCMIsAliasSelector(SEL selector) |
| 199 { |
| 200 return [NSStringFromSelector(selector) hasPrefix:OCMRealMethodAliasPrefix]; |
| 201 } |
| 202 |
| 203 SEL OCMAliasForOriginalSelector(SEL selector) |
| 204 { |
| 205 char aliasName[2048]; |
| 206 const char *originalName = sel_getName(selector); |
| 207 strlcpy(aliasName, OCMRealMethodAliasPrefixCString, sizeof(aliasName)); |
| 208 strlcat(aliasName, originalName, sizeof(aliasName)); |
| 209 return sel_registerName(aliasName); |
| 210 } |
| 211 |
| 212 SEL OCMOriginalSelectorForAlias(SEL selector) |
| 213 { |
| 214 if(!OCMIsAliasSelector(selector)) |
| 215 [NSException raise:NSInvalidArgumentException format:@"Not an alias sele
ctor; found %@", NSStringFromSelector(selector)]; |
| 216 NSString *string = NSStringFromSelector(selector); |
| 217 return NSSelectorFromString([string substringFromIndex:[OCMRealMethodAliasPr
efix length]]); |
| 218 } |
| 219 |
| 220 |
| 221 #pragma mark Wrappers around associative references |
| 222 |
| 223 static NSString *const OCMClassMethodMockObjectKey = @"OCMClassMethodMockObjectK
ey"; |
| 224 |
| 225 void OCMSetAssociatedMockForClass(OCClassMockObject *mock, Class aClass) |
| 226 { |
| 227 if((mock != nil) && (objc_getAssociatedObject(aClass, OCMClassMethodMockObje
ctKey) != nil)) |
| 228 [NSException raise:NSInternalInconsistencyException format:@"Another moc
k is already associated with class %@", NSStringFromClass(aClass)]; |
| 229 objc_setAssociatedObject(aClass, OCMClassMethodMockObjectKey, mock, OBJC_ASS
OCIATION_ASSIGN); |
| 230 } |
| 231 |
| 232 OCClassMockObject *OCMGetAssociatedMockForClass(Class aClass, BOOL includeSuperc
lasses) |
| 233 { |
| 234 OCClassMockObject *mock = nil; |
| 235 do |
| 236 { |
| 237 mock = objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey); |
| 238 aClass = class_getSuperclass(aClass); |
| 239 } |
| 240 while((mock == nil) && (aClass != nil) && includeSuperclasses); |
| 241 return mock; |
| 242 } |
| 243 |
| 244 static NSString *const OCMPartialMockObjectKey = @"OCMPartialMockObjectKey"; |
| 245 |
| 246 void OCMSetAssociatedMockForObject(OCClassMockObject *mock, id anObject) |
| 247 { |
| 248 if((mock != nil) && (objc_getAssociatedObject(anObject, OCMPartialMockObject
Key) != nil)) |
| 249 [NSException raise:NSInternalInconsistencyException format:@"Another moc
k is already associated with object %@", anObject]; |
| 250 objc_setAssociatedObject(anObject, OCMPartialMockObjectKey, mock, OBJC_ASSOC
IATION_ASSIGN); |
| 251 } |
| 252 |
| 253 OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject) |
| 254 { |
| 255 return objc_getAssociatedObject(anObject, OCMPartialMockObjectKey); |
| 256 } |
| 257 |
| 258 |
| 259 #pragma mark Functions related to IDE error reporting |
| 260 |
| 261 void OCMReportFailure(OCMLocation *loc, NSString *description) |
| 262 { |
| 263 id testCase = [loc testCase]; |
| 264 if((testCase != nil) && [testCase respondsToSelector:@selector(recordFailure
WithDescription:inFile:atLine:expected:)]) |
| 265 { |
| 266 [testCase recordFailureWithDescription:description inFile:[loc file] atL
ine:[loc line] expected:NO]; |
| 267 } |
| 268 else if((testCase != nil) && [testCase respondsToSelector:@selector(failWith
Exception:)]) |
| 269 { |
| 270 NSException *exception = nil; |
| 271 if([NSException instancesRespondToSelector:@selector(failureInFile:atLin
e:withDescription:)]) |
| 272 { |
| 273 exception = [NSException failureInFile:[loc file] atLine:(int)[loc l
ine] withDescription:description]; |
| 274 } |
| 275 else |
| 276 { |
| 277 NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc fil
e], (unsigned long)[loc line], description]; |
| 278 exception = [NSException exceptionWithName:@"OCMockTestFailure" reas
on:reason userInfo:nil]; |
| 279 } |
| 280 [testCase failWithException:exception]; |
| 281 } |
| 282 else if(loc != nil) |
| 283 { |
| 284 NSLog(@"%@:%lu %@", [loc file], (unsigned long)[loc line], description); |
| 285 NSString *reason = [NSString stringWithFormat:@"%@:%lu %@", [loc file],
(unsigned long)[loc line], description]; |
| 286 [[NSException exceptionWithName:@"OCMockTestFailure" reason:reason userI
nfo:nil] raise]; |
| 287 |
| 288 } |
| 289 else |
| 290 { |
| 291 NSLog(@"%@", description); |
| 292 [[NSException exceptionWithName:@"OCMockTestFailure" reason:description
userInfo:nil] raise]; |
| 293 } |
| 294 |
| 295 } |
OLD | NEW |