GULSwizzlingCacheTest.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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 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 cacheCurrentIMP:originalIMP
  48. forNewIMP:intermediateIMP
  49. forClass:swizzledClass
  50. withSelector:swizzledSelector];
  51. IMP returnedOriginalIMPWhenSwizzledOnce =
  52. [GULSwizzlingCache 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 cacheCurrentIMP:intermediateIMP
  60. forNewIMP:intermediateIMP2
  61. forClass:swizzledClass
  62. withSelector:swizzledSelector];
  63. IMP returnedOriginalIMPWhenSwizzledTwice =
  64. [GULSwizzlingCache 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 cacheCurrentIMP:intermediateIMP
  71. forNewIMP:newIMP
  72. forClass:swizzledClass
  73. withSelector:swizzledSelector];
  74. IMP returnedOriginalIMPWhenSwizzledThrice = [GULSwizzlingCache originalIMPOfCurrentIMP:newIMP];
  75. // Pointer equality to make sure they're the same IMP.
  76. XCTAssertEqual(returnedOriginalIMPWhenSwizzledThrice, originalIMP);
  77. }
  78. - (void)testGettingCachedIMPForClassAndSelector {
  79. Class swizzledClass = [NSObject class];
  80. SEL swizzledSelector = @selector(description);
  81. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  82. // Any valid IMP is OK for test.
  83. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  84. // Pointer inequality to make sure the IMPs are different.
  85. XCTAssertNotEqual(originalIMP, newIMP);
  86. [GULSwizzlingCache cacheCurrentIMP:originalIMP
  87. forNewIMP:newIMP
  88. forClass:swizzledClass
  89. withSelector:swizzledSelector];
  90. IMP returnedOriginalIMP = [[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  91. withSelector:swizzledSelector];
  92. // Pointer equality to make sure they're the same IMP.
  93. XCTAssertEqual(returnedOriginalIMP, originalIMP);
  94. }
  95. - (void)testGettingCachedIMPForClassAndSelectorWhenLastImpWasPutThereByUs {
  96. Class swizzledClass = [NSObject class];
  97. SEL swizzledSelector = @selector(description);
  98. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  99. // Any valid IMP is OK for test.
  100. IMP intermediateIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  101. // Pointer inequality to make sure the IMPs are different.
  102. XCTAssertNotEqual(originalIMP, intermediateIMP);
  103. [GULSwizzlingCache cacheCurrentIMP:originalIMP
  104. forNewIMP:intermediateIMP
  105. forClass:swizzledClass
  106. withSelector:swizzledSelector];
  107. // Any valid IMP is OK for test.
  108. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(mutableCopy));
  109. // Pointer inequality to make sure the IMPs are different.
  110. XCTAssertNotEqual(intermediateIMP, newIMP);
  111. [GULSwizzlingCache cacheCurrentIMP:intermediateIMP
  112. forNewIMP:newIMP
  113. forClass:swizzledClass
  114. withSelector:swizzledSelector];
  115. IMP returnedOriginalIMP = [[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  116. withSelector:swizzledSelector];
  117. // Pointer equality to make sure they're the same IMP.
  118. XCTAssertEqual(returnedOriginalIMP, originalIMP);
  119. }
  120. - (void)testClearingCacheActuallyClearsTheCache {
  121. Class swizzledClass = [NSObject class];
  122. SEL swizzledSelector = @selector(description);
  123. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  124. // Any valid IMP is OK for test.
  125. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  126. XCTAssertNotEqual(originalIMP, newIMP);
  127. [GULSwizzlingCache cacheCurrentIMP:originalIMP
  128. forNewIMP:newIMP
  129. forClass:swizzledClass
  130. withSelector:swizzledSelector];
  131. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  132. withSelector:swizzledSelector] != NULL);
  133. XCTAssertEqual([GULSwizzlingCache originalIMPOfCurrentIMP:newIMP], originalIMP,
  134. @"New to original IMP cache was not correctly poplated.");
  135. [[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:newIMP
  136. selector:swizzledSelector
  137. aClass:swizzledClass];
  138. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  139. withSelector:swizzledSelector] == NULL);
  140. XCTAssertEqual([GULSwizzlingCache originalIMPOfCurrentIMP:newIMP], newIMP,
  141. @"New to original IMP cache was not cleared.");
  142. }
  143. - (void)testClearingCacheForOneIMPDoesNotImpactOtherIMPs {
  144. Class swizzledClass = [NSObject class];
  145. SEL swizzledSelector = @selector(description);
  146. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  147. // Any valid IMP is OK for test.
  148. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  149. XCTAssertNotEqual(originalIMP, newIMP);
  150. [GULSwizzlingCache cacheCurrentIMP:originalIMP
  151. forNewIMP:newIMP
  152. forClass:swizzledClass
  153. withSelector:swizzledSelector];
  154. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  155. withSelector:swizzledSelector] != NULL);
  156. XCTAssertEqual([GULSwizzlingCache originalIMPOfCurrentIMP:newIMP], originalIMP,
  157. @"New to original IMP cache was not correctly populated.");
  158. Class swizzledClass2 = [NSString class];
  159. SEL swizzledSelector2 = @selector(stringWithFormat:);
  160. IMP originalIMP2 = class_getMethodImplementation(swizzledClass2, swizzledSelector2);
  161. // Any valid IMP is OK for test.
  162. IMP newIMP2 = class_getMethodImplementation(swizzledClass2, @selector(stringByAppendingString:));
  163. XCTAssertNotEqual(originalIMP2, newIMP2);
  164. [GULSwizzlingCache cacheCurrentIMP:originalIMP2
  165. forNewIMP:newIMP2
  166. forClass:swizzledClass2
  167. withSelector:swizzledSelector2];
  168. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass2
  169. withSelector:swizzledSelector2] != NULL);
  170. [[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:newIMP
  171. selector:swizzledSelector
  172. aClass:swizzledClass];
  173. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
  174. withSelector:swizzledSelector] == NULL);
  175. XCTAssertEqual([GULSwizzlingCache originalIMPOfCurrentIMP:newIMP], newIMP,
  176. @"New to original IMP cache was not cleared.");
  177. XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass2
  178. withSelector:swizzledSelector2] != NULL);
  179. XCTAssertEqual([GULSwizzlingCache originalIMPOfCurrentIMP:newIMP2], originalIMP2,
  180. @"New to original IMP cache was cleared when it shouldn't have.");
  181. }
  182. - (void)testDeallocatingSwizzlingCacheWithoutClearingItDoesntCrash {
  183. GULSwizzlingCache *cache = [[GULSwizzlingCache alloc] init];
  184. Class swizzledClass = [NSObject class];
  185. SEL swizzledSelector = @selector(description);
  186. IMP originalIMP = class_getMethodImplementation(swizzledClass, swizzledSelector);
  187. // Any valid IMP is OK for test.
  188. IMP newIMP = class_getMethodImplementation(swizzledClass, @selector(copy));
  189. [cache cacheCurrentIMP:originalIMP
  190. forNewIMP:newIMP
  191. forClass:swizzledClass
  192. withSelector:swizzledSelector];
  193. __weak GULSwizzlingCache *weakCache = cache;
  194. cache = nil;
  195. // If it reaches this point, deallocation succeded and it didn't crash.
  196. XCTAssertNil(weakCache);
  197. }
  198. - (void)testUnderlyingStoresAreDeallocatedWhenCacheIsDeallocated {
  199. GULSwizzlingCache *cache = [[GULSwizzlingCache alloc] init];
  200. __weak NSMutableDictionary *originalImps = (__bridge NSMutableDictionary *)cache.originalImps;
  201. __weak NSMutableDictionary *newToOriginalIMPs =
  202. (__bridge NSMutableDictionary *)cache.newToOriginalImps;
  203. XCTAssertNotNil(originalImps);
  204. XCTAssertNotNil(newToOriginalIMPs);
  205. cache = nil;
  206. XCTAssertNil(originalImps);
  207. XCTAssertNil(newToOriginalIMPs);
  208. }
  209. - (void)testCFMutableDictionaryRetainsAndReleasesClassSELPairCorrectly {
  210. GULSwizzlingCache *cache = [[GULSwizzlingCache alloc] init];
  211. Class testClass = [NSObject class];
  212. SEL testSelector = @selector(description);
  213. IMP testIMP = class_getMethodImplementation(testClass, testSelector);
  214. CFMutableDictionaryRef originalImps = cache.originalImps;
  215. const void *classSELCArray[2] = {(__bridge void *)(testClass), testSelector};
  216. CFArrayRef classSELPair = CFArrayCreate(kCFAllocatorDefault, classSELCArray,
  217. 2, // Size.
  218. NULL); // Elements are pointers so this is NULL.
  219. __weak NSArray *classSELPairNSArray = (__bridge NSArray *)classSELPair;
  220. CFDictionaryAddValue(originalImps, classSELPair, testIMP);
  221. CFRelease(classSELPair);
  222. XCTAssertNotNil(classSELPairNSArray);
  223. CFDictionaryRemoveValue(originalImps, classSELPair);
  224. XCTAssertNil(classSELPairNSArray);
  225. }
  226. @end