KWStub.m 6.8 KB

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