| 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 "OCMFunctions.h" |
| 19 #import "OCMLocation.h" |
| 20 #import "OCClassMockObject.h" |
| 21 #import "OCPartialMockObject.h" |
| 22 |
| 23 #pragma mark Known private API |
| 24 |
| 25 @interface NSException (OCMKnownExceptionMethods) |
| 26 + (NSException*)failureInFile:(NSString*)file |
| 27 atLine:(int)line |
| 28 withDescription:(NSString*)formatString, ...; |
| 29 @end |
| 30 |
| 31 @interface NSObject (OCMKnownTestCaseMethods) |
| 32 - (void)recordFailureWithDescription:(NSString*)description |
| 33 inFile:(NSString*)file |
| 34 atLine:(NSUInteger)line |
| 35 expected:(BOOL)expected; |
| 36 - (void)failWithException:(NSException*)exception; |
| 37 @end |
| 38 |
| 39 #pragma mark Functions related to ObjC type system |
| 40 |
| 41 BOOL OCMIsObjectType(const char* objCType) { |
| 42 objCType = OCMTypeWithoutQualifiers(objCType); |
| 43 |
| 44 if (strcmp(objCType, @encode(id)) == 0 || |
| 45 strcmp(objCType, @encode(Class)) == 0) |
| 46 return YES; |
| 47 |
| 48 // if the returnType is a typedef to an object, it has the form |
| 49 // ^{OriginClass=#} |
| 50 NSString* regexString = @"^\\^\\{(.*)=#.*\\}"; |
| 51 NSRegularExpression* regex = |
| 52 [NSRegularExpression regularExpressionWithPattern:regexString |
| 53 options:0 |
| 54 error:NULL]; |
| 55 NSString* type = |
| 56 [NSString stringWithCString:objCType encoding:NSASCIIStringEncoding]; |
| 57 if ([regex numberOfMatchesInString:type |
| 58 options:0 |
| 59 range:NSMakeRange(0, type.length)] > 0) |
| 60 return YES; |
| 61 |
| 62 // if the return type is a block we treat it like an object |
| 63 // TODO: if the runtime were to encode the block's argument and/or return |
| 64 // types, this test would not be sufficient |
| 65 if (strcmp(objCType, @encode(void (^)())) == 0) |
| 66 return YES; |
| 67 |
| 68 return NO; |
| 69 } |
| 70 |
| 71 const char* OCMTypeWithoutQualifiers(const char* objCType) { |
| 72 while (strchr("rnNoORV", objCType[0]) != NULL) |
| 73 objCType += 1; |
| 74 return objCType; |
| 75 } |
| 76 |
| 77 /* |
| 78 * Sometimes an external type is an opaque struct (which will have an @encode of |
| 79 * "{structName}" |
| 80 * or "{structName=}") but the actual method return type, or property type, will |
| 81 * know the contents |
| 82 * of the struct (so will have an objcType of say "{structName=iiSS}". This |
| 83 * function will determine |
| 84 * those are equal provided they have the same structure name, otherwise |
| 85 * everything else will be |
| 86 * compared textually. This can happen particularly for pointers to such |
| 87 * structures, which still |
| 88 * encode what is being pointed to. |
| 89 * |
| 90 * For some types some runtime functions throw exceptions, which is why we wrap |
| 91 * this in an |
| 92 * exception handler just below. |
| 93 */ |
| 94 static BOOL OCMEqualTypesAllowingOpaqueStructsInternal(const char* type1, |
| 95 const char* type2) { |
| 96 type1 = OCMTypeWithoutQualifiers(type1); |
| 97 type2 = OCMTypeWithoutQualifiers(type2); |
| 98 |
| 99 switch (type1[0]) { |
| 100 case '{': |
| 101 case '(': { |
| 102 if (type2[0] != type1[0]) |
| 103 return NO; |
| 104 char endChar = type1[0] == '{' ? '}' : ')'; |
| 105 |
| 106 const char* type1End = strchr(type1, endChar); |
| 107 const char* type2End = strchr(type2, endChar); |
| 108 const char* type1Equals = strchr(type1, '='); |
| 109 const char* type2Equals = strchr(type2, '='); |
| 110 |
| 111 /* Opaque types either don't have an equals sign (just the name and the |
| 112 * end brace), or |
| 113 * empty content after the equals sign. |
| 114 * We want that to compare the same as a type of the same name but with |
| 115 * the content. |
| 116 */ |
| 117 BOOL type1Opaque = (type1Equals == NULL || (type1End < type1Equals) || |
| 118 type1Equals[1] == endChar); |
| 119 BOOL type2Opaque = (type2Equals == NULL || (type2End < type2Equals) || |
| 120 type2Equals[1] == endChar); |
| 121 const char* type1NameEnd = |
| 122 (type1Equals == NULL || (type1End < type1Equals)) ? type1End |
| 123 : type1Equals; |
| 124 const char* type2NameEnd = |
| 125 (type2Equals == NULL || (type2End < type2Equals)) ? type2End |
| 126 : type2Equals; |
| 127 intptr_t type1NameLen = type1NameEnd - type1; |
| 128 intptr_t type2NameLen = type2NameEnd - type2; |
| 129 |
| 130 /* If the names are not equal, return NO */ |
| 131 if (type1NameLen != type2NameLen || strncmp(type1, type2, type1NameLen)) |
| 132 return NO; |
| 133 |
| 134 /* If the same name, and at least one is opaque, that is close enough. */ |
| 135 if (type1Opaque || type2Opaque) |
| 136 return YES; |
| 137 |
| 138 /* Otherwise, compare all the elements. Use NSGetSizeAndAlignment to walk |
| 139 * through the struct elements. */ |
| 140 type1 = type1Equals + 1; |
| 141 type2 = type2Equals + 1; |
| 142 while (type1[0] != endChar && type1[0] != '\0') { |
| 143 if (!OCMEqualTypesAllowingOpaqueStructs(type1, type2)) |
| 144 return NO; |
| 145 type1 = NSGetSizeAndAlignment(type1, NULL, NULL); |
| 146 type2 = NSGetSizeAndAlignment(type2, NULL, NULL); |
| 147 } |
| 148 return YES; |
| 149 } |
| 150 case '^': |
| 151 /* for a pointer, make sure the other is a pointer, then recursively |
| 152 * compare the rest */ |
| 153 if (type2[0] != type1[0]) |
| 154 return NO; |
| 155 return OCMEqualTypesAllowingOpaqueStructs(type1 + 1, type2 + 1); |
| 156 |
| 157 case '\0': |
| 158 return type2[0] == '\0'; |
| 159 |
| 160 default: { |
| 161 // Move the type pointers past the current types, then compare that region |
| 162 const char* afterType1 = NSGetSizeAndAlignment(type1, NULL, NULL); |
| 163 const char* afterType2 = NSGetSizeAndAlignment(type2, NULL, NULL); |
| 164 intptr_t type1Len = afterType1 - type1; |
| 165 intptr_t type2Len = afterType2 - type2; |
| 166 |
| 167 return (type1Len == type2Len && (strncmp(type1, type2, type1Len) == 0)); |
| 168 } |
| 169 } |
| 170 } |
| 171 |
| 172 BOOL OCMEqualTypesAllowingOpaqueStructs(const char* type1, const char* type2) { |
| 173 @try { |
| 174 return OCMEqualTypesAllowingOpaqueStructsInternal(type1, type2); |
| 175 } @catch (NSException* e) { |
| 176 /* Probably a bitfield or something that NSGetSizeAndAlignment chokes on, oh |
| 177 * well */ |
| 178 return NO; |
| 179 } |
| 180 } |
| 181 |
| 182 #pragma mark Creating classes |
| 183 |
| 184 Class OCMCreateSubclass(Class class, void* ref) { |
| 185 const char* className = |
| 186 [[NSString stringWithFormat:@"%@-%p-%u", NSStringFromClass(class), ref, |
| 187 arc4random()] UTF8String]; |
| 188 Class subclass = objc_allocateClassPair(class, className, 0); |
| 189 objc_registerClassPair(subclass); |
| 190 return subclass; |
| 191 } |
| 192 |
| 193 #pragma mark Directly manipulating the isa pointer (look away) |
| 194 |
| 195 void OCMSetIsa(id object, Class class) { |
| 196 *((Class*)object) = class; |
| 197 } |
| 198 |
| 199 Class OCMGetIsa(id object) { |
| 200 return *((Class*)object); |
| 201 } |
| 202 |
| 203 #pragma mark Alias for renaming real methods |
| 204 |
| 205 static NSString* const OCMRealMethodAliasPrefix = @"ocmock_replaced_"; |
| 206 static const char* const OCMRealMethodAliasPrefixCString = "ocmock_replaced_"; |
| 207 |
| 208 BOOL OCMIsAliasSelector(SEL selector) { |
| 209 return [NSStringFromSelector(selector) hasPrefix:OCMRealMethodAliasPrefix]; |
| 210 } |
| 211 |
| 212 SEL OCMAliasForOriginalSelector(SEL selector) { |
| 213 char aliasName[2048]; |
| 214 const char* originalName = sel_getName(selector); |
| 215 strlcpy(aliasName, OCMRealMethodAliasPrefixCString, sizeof(aliasName)); |
| 216 strlcat(aliasName, originalName, sizeof(aliasName)); |
| 217 return sel_registerName(aliasName); |
| 218 } |
| 219 |
| 220 SEL OCMOriginalSelectorForAlias(SEL selector) { |
| 221 if (!OCMIsAliasSelector(selector)) |
| 222 [NSException raise:NSInvalidArgumentException |
| 223 format:@"Not an alias selector; found %@", |
| 224 NSStringFromSelector(selector)]; |
| 225 NSString* string = NSStringFromSelector(selector); |
| 226 return NSSelectorFromString( |
| 227 [string substringFromIndex:[OCMRealMethodAliasPrefix length]]); |
| 228 } |
| 229 |
| 230 #pragma mark Wrappers around associative references |
| 231 |
| 232 static NSString* const OCMClassMethodMockObjectKey = |
| 233 @"OCMClassMethodMockObjectKey"; |
| 234 |
| 235 void OCMSetAssociatedMockForClass(OCClassMockObject* mock, Class aClass) { |
| 236 if ((mock != nil) && |
| 237 (objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey) != nil)) |
| 238 [NSException raise:NSInternalInconsistencyException |
| 239 format:@"Another mock is already associated with class %@", |
| 240 NSStringFromClass(aClass)]; |
| 241 objc_setAssociatedObject(aClass, OCMClassMethodMockObjectKey, mock, |
| 242 OBJC_ASSOCIATION_ASSIGN); |
| 243 } |
| 244 |
| 245 OCClassMockObject* OCMGetAssociatedMockForClass(Class aClass, |
| 246 BOOL includeSuperclasses) { |
| 247 OCClassMockObject* mock = nil; |
| 248 do { |
| 249 mock = objc_getAssociatedObject(aClass, OCMClassMethodMockObjectKey); |
| 250 aClass = class_getSuperclass(aClass); |
| 251 } while ((mock == nil) && (aClass != nil) && includeSuperclasses); |
| 252 return mock; |
| 253 } |
| 254 |
| 255 static NSString* const OCMPartialMockObjectKey = @"OCMPartialMockObjectKey"; |
| 256 |
| 257 void OCMSetAssociatedMockForObject(OCClassMockObject* mock, id anObject) { |
| 258 if ((mock != nil) && |
| 259 (objc_getAssociatedObject(anObject, OCMPartialMockObjectKey) != nil)) |
| 260 [NSException |
| 261 raise:NSInternalInconsistencyException |
| 262 format:@"Another mock is already associated with object %@", anObject]; |
| 263 objc_setAssociatedObject(anObject, OCMPartialMockObjectKey, mock, |
| 264 OBJC_ASSOCIATION_ASSIGN); |
| 265 } |
| 266 |
| 267 OCPartialMockObject* OCMGetAssociatedMockForObject(id anObject) { |
| 268 return objc_getAssociatedObject(anObject, OCMPartialMockObjectKey); |
| 269 } |
| 270 |
| 271 #pragma mark Functions related to IDE error reporting |
| 272 |
| 273 void OCMReportFailure(OCMLocation* loc, NSString* description) { |
| 274 id testCase = [loc testCase]; |
| 275 if ((testCase != nil) && |
| 276 [testCase respondsToSelector:@selector(recordFailureWithDescription: |
| 277 inFile: |
| 278 atLine: |
| 279 expected:)]) { |
| 280 [testCase recordFailureWithDescription:description |
| 281 inFile:[loc file] |
| 282 atLine:[loc line] |
| 283 expected:NO]; |
| 284 } else if ((testCase != nil) && |
| 285 [testCase respondsToSelector:@selector(failWithException:)]) { |
| 286 NSException* exception = nil; |
| 287 if ([NSException instancesRespondToSelector:@selector(failureInFile: |
| 288 atLine: |
| 289 withDescription:)]) { |
| 290 exception = [NSException failureInFile:[loc file] |
| 291 atLine:(int)[loc line] |
| 292 withDescription:description]; |
| 293 } else { |
| 294 NSString* reason = |
| 295 [NSString stringWithFormat:@"%@:%lu %@", [loc file], |
| 296 (unsigned long)[loc line], description]; |
| 297 exception = [NSException exceptionWithName:@"OCMockTestFailure" |
| 298 reason:reason |
| 299 userInfo:nil]; |
| 300 } |
| 301 [testCase failWithException:exception]; |
| 302 } else if (loc != nil) { |
| 303 NSLog(@"%@:%lu %@", [loc file], (unsigned long)[loc line], description); |
| 304 NSString* reason = |
| 305 [NSString stringWithFormat:@"%@:%lu %@", [loc file], |
| 306 (unsigned long)[loc line], description]; |
| 307 [[NSException exceptionWithName:@"OCMockTestFailure" |
| 308 reason:reason |
| 309 userInfo:nil] raise]; |
| 310 |
| 311 } else { |
| 312 NSLog(@"%@", description); |
| 313 [[NSException exceptionWithName:@"OCMockTestFailure" |
| 314 reason:description |
| 315 userInfo:nil] raise]; |
| 316 } |
| 317 } |
| OLD | NEW |