GULSwizzlingCacheTest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <XCTest/XCTest.h>
  15. #import <objc/runtime.h>
  16. #import <GoogleUtilities/GULSwizzlingCache.h>
  17. #import <GoogleUtilities/GULSwizzlingCache_Private.h>
  18. @interface GULSwizzlingCacheTest : XCTestCase
  19. @end
  20. @implementation GULSwizzlingCacheTest
  21. - (void)tearDown {
  22. [[GULSwizzlingCache sharedInstance] clearCache];
  23. [super tearDown];
  24. }
  25. - (void)testSharedInstanceCreatesSingleton {
  26. GULSwizzlingCache *firstCache = [GULSwizzlingCache sharedInstance];
  27. GULSwizzlingCache *secondCache = [GULSwizzlingCache sharedInstance];
  28. // Pointer equality to make sure they're the same instance.
  29. XCTAssertEqual(firstCache, secondCache);
  30. }
  31. - (void)testOriginalIMPOfCurrentIMPIsSameWhenNotPreviouslySwizzled {
  32. Class swizzledClass = [NSObject class];
  33. SEL swizzledSelector = @selector(description);
  34. IMP currentIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  35. IMP returnedOriginalIMP = [[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:currentIMP];
  36. // Pointer equality to make sure they're the same IMP.
  37. XCTAssertEqual(returnedOriginalIMP, currentIMP);
  38. }
  39. - (void)testOriginalIMPOfNewIMPIsActuallyOriginalIMPWhenPreviouslySwizzledManyTimes {
  40. Class swizzledClass = [NSObject class];
  41. SEL swizzledSelector = @selector(description);
  42. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  43. // Any valid IMP is OK for test.
  44. IMP intermediateIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  45. // Pointer inequality to make sure the IMPs are different.
  46. XCTAssertNotEqual(originalIMP, intermediateIMP);
  47. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:originalIMP
  48. forNewIMP:intermediateIMP
  49. forClass:swizzledClass
  50. withSelector:swizzledSelector];
  51. IMP returnedOriginalIMPWhenSwizzledOnce =
  52. [[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:intermediateIMP];
  53. // Pointer equality to make sure they're the same IMP.
  54. XCTAssertEqual(returnedOriginalIMPWhenSwizzledOnce, originalIMP);
  55. // Any valid IMP is OK for test.
  56. IMP intermediateIMP2 = class_getMethodImplementation(swizzledClass, @selector(init));
  57. // Pointer inequality to make sure the IMPs are different.
  58. XCTAssertNotEqual(intermediateIMP, intermediateIMP2);
  59. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:intermediateIMP
  60. forNewIMP:intermediateIMP2
  61. forClass:swizzledClass
  62. withSelector:swizzledSelector];
  63. IMP returnedOriginalIMPWhenSwizzledTwice =
  64. [[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:intermediateIMP2];
  65. // Pointer inequality to make sure the IMPs are different.
  66. XCTAssertEqual(returnedOriginalIMPWhenSwizzledTwice, originalIMP);
  67. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(mutableCopy));
  68. // Pointer inequality to make sure the IMPs are different.
  69. XCTAssertNotEqual(intermediateIMP, newIMP);
  70. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:intermediateIMP
  71. forNewIMP:newIMP
  72. forClass:swizzledClass
  73. withSelector:swizzledSelector];
  74. IMP returnedOriginalIMPWhenSwizzledThrice =
  75. [[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:newIMP];
  76. // Pointer equality to make sure they're the same IMP.
  77. XCTAssertEqual(returnedOriginalIMPWhenSwizzledThrice, originalIMP);
  78. }
  79. - (void)testGettingCachedIMPForClassAndSelector {
  80. Class swizzledClass = [NSObject class];
  81. SEL swizzledSelector = @selector(description);
  82. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  83. // Any valid IMP is OK for test.
  84. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  85. // Pointer inequality to make sure the IMPs are different.
  86. XCTAssertNotEqual(originalIMP, newIMP);
  87. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:originalIMP
  88. forNewIMP:newIMP
  89. forClass:swizzledClass
  90. withSelector:swizzledSelector];
  91. IMP returnedOriginalIMP = [[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  92. withSelector:swizzledSelector];
  93. // Pointer equality to make sure they're the same IMP.
  94. XCTAssertEqual(returnedOriginalIMP, originalIMP);
  95. }
  96. - (void)testGettingCachedIMPForClassAndSelectorWhenLastImpWasPutThereByUs {
  97. Class swizzledClass = [NSObject class];
  98. SEL swizzledSelector = @selector(description);
  99. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  100. // Any valid IMP is OK for test.
  101. IMP intermediateIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  102. // Pointer inequality to make sure the IMPs are different.
  103. XCTAssertNotEqual(originalIMP, intermediateIMP);
  104. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:originalIMP
  105. forNewIMP:intermediateIMP
  106. forClass:swizzledClass
  107. withSelector:swizzledSelector];
  108. // Any valid IMP is OK for test.
  109. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(mutableCopy));
  110. // Pointer inequality to make sure the IMPs are different.
  111. XCTAssertNotEqual(intermediateIMP, newIMP);
  112. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:intermediateIMP
  113. forNewIMP:newIMP
  114. forClass:swizzledClass
  115. withSelector:swizzledSelector];
  116. IMP returnedOriginalIMP = [[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  117. withSelector:swizzledSelector];
  118. // Pointer equality to make sure they're the same IMP.
  119. XCTAssertEqual(returnedOriginalIMP, originalIMP);
  120. }
  121. - (void)testClearingCacheActuallyClearsTheCache {
  122. Class swizzledClass = [NSObject class];
  123. SEL swizzledSelector = @selector(description);
  124. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  125. // Any valid IMP is OK for test.
  126. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  127. XCTAssertNotEqual(originalIMP, newIMP);
  128. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:originalIMP
  129. forNewIMP:newIMP
  130. forClass:swizzledClass
  131. withSelector:swizzledSelector];
  132. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  133. withSelector:swizzledSelector] != NULL);
  134. XCTAssertEqual([[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:newIMP], originalIMP,
  135. @"New to original IMP cache was not correctly poplated.");
  136. [[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:newIMP
  137. selector:swizzledSelector
  138. aClass:swizzledClass];
  139. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  140. withSelector:swizzledSelector] == NULL);
  141. XCTAssertEqual([[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:newIMP], newIMP,
  142. @"New to original IMP cache was not cleared.");
  143. }
  144. - (void)testClearingCacheForOneIMPDoesNotImpactOtherIMPs {
  145. Class swizzledClass = [NSObject class];
  146. SEL swizzledSelector = @selector(description);
  147. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  148. // Any valid IMP is OK for test.
  149. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  150. XCTAssertNotEqual(originalIMP, newIMP);
  151. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:originalIMP
  152. forNewIMP:newIMP
  153. forClass:swizzledClass
  154. withSelector:swizzledSelector];
  155. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  156. withSelector:swizzledSelector] != NULL);
  157. XCTAssertEqual([[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:newIMP], originalIMP,
  158. @"New to original IMP cache was not correctly populated.");
  159. Class swizzledClass2 = [NSString class];
  160. SEL swizzledSelector2 = @selector(stringWithFormat:);
  161. IMP originalIMP2 = class_getMethodImplementation(swizzledClass2, swizzledSelector2);
  162. // Any valid IMP is OK for test.
  163. IMP newIMP2 = class_getMethodImplementation(swizzledClass2, @selector(stringByAppendingString:));
  164. XCTAssertNotEqual(originalIMP2, newIMP2);
  165. [[GULSwizzlingCache sharedInstance] cacheCurrentIMP:originalIMP2
  166. forNewIMP:newIMP2
  167. forClass:swizzledClass2
  168. withSelector:swizzledSelector2];
  169. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass2
  170. withSelector:swizzledSelector2] != NULL);
  171. [[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:newIMP
  172. selector:swizzledSelector
  173. aClass:swizzledClass];
  174. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  175. withSelector:swizzledSelector] == NULL);
  176. XCTAssertEqual([[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:newIMP], newIMP,
  177. @"New to original IMP cache was not cleared.");
  178. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass2
  179. withSelector:swizzledSelector2] != NULL);
  180. XCTAssertEqual([[GULSwizzlingCache sharedInstance] originalIMPOfCurrentIMP:newIMP2], originalIMP2,
  181. @"New to original IMP cache was cleared when it shouldn't have.");
  182. }
  183. - (void)testDeallocatingSwizzlingCacheWithoutClearingItDoesntCrash {
  184. GULSwizzlingCache *cache = [[GULSwizzlingCache alloc] init];
  185. Class swizzledClass = [NSObject class];
  186. SEL swizzledSelector = @selector(description);
  187. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  188. // Any valid IMP is OK for test.
  189. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  190. [cache cacheCurrentIMP:originalIMP
  191. forNewIMP:newIMP
  192. forClass:swizzledClass
  193. withSelector:swizzledSelector];
  194. __weak GULSwizzlingCache *weakCache = cache;
  195. cache = nil;
  196. // If it reaches this point, deallocation succeded and it didn't crash.
  197. XCTAssertNil(weakCache);
  198. }
  199. - (void)testUnderlyingStoresAreDeallocatedWhenCacheIsDeallocated {
  200. GULSwizzlingCache *cache = [[GULSwizzlingCache alloc] init];
  201. __weak NSMutableDictionary *originalImps = (__bridge NSMutableDictionary *)cache.originalImps;
  202. __weak NSMutableDictionary *newToOriginalIMPs =
  203. (__bridge NSMutableDictionary *)cache.newToOriginalImps;
  204. XCTAssertNotNil(originalImps);
  205. XCTAssertNotNil(newToOriginalIMPs);
  206. cache = nil;
  207. XCTAssertNil(originalImps);
  208. XCTAssertNil(newToOriginalIMPs);
  209. }
  210. - (void)testCFMutableDictionaryRetainsAndReleasesClassSELPairCorrectly {
  211. GULSwizzlingCache *cache = [[GULSwizzlingCache alloc] init];
  212. Class testClass = [NSObject class];
  213. SEL testSelector = @selector(description);
  214. IMP testIMP = class_getMethodImplementation(testClass, testSelector);
  215. CFMutableDictionaryRef originalImps = cache.originalImps;
  216. const void *classSELCArray[2] = {(__bridge void *)(testClass), testSelector};
  217. CFArrayRef classSELPair = CFArrayCreate(kCFAllocatorDefault, classSELCArray,
  218. 2, // Size.
  219. NULL); // Elements are pointers so this is NULL.
  220. __weak NSArray *classSELPairNSArray = (__bridge NSArray *)classSELPair;
  221. CFDictionaryAddValue(originalImps, classSELPair, testIMP);
  222. CFRelease(classSELPair);
  223. XCTAssertNotNil(classSELPairNSArray);
  224. CFDictionaryRemoveValue(originalImps, classSELPair);
  225. XCTAssertNil(classSELPairNSArray);
  226. }
  227. @end