GULRuntimeClassSnapshotTests.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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/GULRuntimeClassDiff.h>
  17. #import <GoogleUtilities/GULRuntimeClassSnapshot.h>
  18. // A variable to be used as a backing store for a dynamic class property. */
  19. static NSString *dynamicClassBacking;
  20. /** Class used for testing the detection of runtime state changes. */
  21. @interface GULRuntimeClassSnapshotTestClass : NSObject {
  22. /** An ivar to be used as the backing store for an instance property later. */
  23. NSString *dynamicPropertyIvar;
  24. }
  25. @end
  26. @implementation GULRuntimeClassSnapshotTestClass
  27. + (NSString *)description {
  28. return [super description];
  29. }
  30. - (NSString *)description {
  31. return [super description];
  32. }
  33. @end
  34. @interface GULRuntimeClassSnapshotTests : XCTestCase
  35. @end
  36. @implementation GULRuntimeClassSnapshotTests
  37. /** Tests initialization. */
  38. - (void)testInitWithClass {
  39. Class NSObjectClass = [NSObject class];
  40. GULRuntimeClassSnapshot *snapshot = [[GULRuntimeClassSnapshot alloc] initWithClass:NSObjectClass];
  41. XCTAssertNotNil(snapshot);
  42. }
  43. /** Tests the ability to snapshot without throwing. */
  44. - (void)testCapture {
  45. Class NSObjectClass = [NSObject class];
  46. GULRuntimeClassSnapshot *snapshot = [[GULRuntimeClassSnapshot alloc] initWithClass:NSObjectClass];
  47. XCTAssertNoThrow([snapshot capture]);
  48. }
  49. /** Tests that isEqual: of empty snapshots is YES. */
  50. - (void)testDiffOfNoChanges {
  51. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  52. GULRuntimeClassSnapshot *snapshot1 =
  53. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  54. [snapshot1 capture];
  55. GULRuntimeClassSnapshot *snapshot2 =
  56. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  57. [snapshot2 capture];
  58. GULRuntimeClassDiff *noChangeDiff = [[GULRuntimeClassDiff alloc] init];
  59. XCTAssertEqualObjects([snapshot1 diff:snapshot2], noChangeDiff);
  60. }
  61. /** Tests that adding a class property is detected between two snapshots. */
  62. - (void)testAddingAClassPropertyDetected {
  63. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  64. Class GULRuntimeClassSnapshotTestClassMetaClass =
  65. objc_getMetaClass([NSStringFromClass(GULRuntimeClassSnapshotTestClassClass) UTF8String]);
  66. GULRuntimeClassSnapshot *snapshot1 =
  67. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  68. [snapshot1 capture];
  69. // Reference:
  70. objc_property_attribute_t type = {"T", "@\"NSString\""};
  71. objc_property_attribute_t ownership = {"C", ""};
  72. objc_property_attribute_t backingivar = {"V", "dynamicClassBacking"};
  73. objc_property_attribute_t attributes[] = {type, ownership, backingivar};
  74. class_addProperty(GULRuntimeClassSnapshotTestClassMetaClass, "dynamicClassProperty", attributes,
  75. 3);
  76. GULRuntimeClassSnapshot *snapshot2 =
  77. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  78. [snapshot2 capture];
  79. GULRuntimeClassDiff *diff = [snapshot1 diff:snapshot2];
  80. XCTAssertEqual(diff.addedClassProperties.count, 1);
  81. XCTAssertEqualObjects(diff.addedClassProperties.anyObject, @"dynamicClassProperty");
  82. }
  83. /** Tests that adding an instance property is detected between two snapshots. */
  84. - (void)testAddingAnInstancePropertyDetected {
  85. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  86. GULRuntimeClassSnapshot *snapshot1 =
  87. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  88. [snapshot1 capture];
  89. // Reference:
  90. objc_property_attribute_t type = {"T", "@\"NSString\""};
  91. objc_property_attribute_t ownership = {"C", ""};
  92. objc_property_attribute_t backingivar = {"V", "_dynamicPropertyIvar"};
  93. objc_property_attribute_t attributes[] = {type, ownership, backingivar};
  94. class_addProperty(GULRuntimeClassSnapshotTestClassClass, "dynamicProperty", attributes, 3);
  95. GULRuntimeClassSnapshot *snapshot2 =
  96. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  97. [snapshot2 capture];
  98. GULRuntimeClassDiff *diff = [snapshot1 diff:snapshot2];
  99. XCTAssertEqual(diff.addedInstanceProperties.count, 1);
  100. XCTAssertEqualObjects(diff.addedInstanceProperties.anyObject, @"dynamicProperty");
  101. }
  102. /** Tests that adding a class selector is detected between two snapshots. */
  103. - (void)testAddingAClassMethodDetected {
  104. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  105. Class GULRuntimeClassSnapshotTestClassMetaClass =
  106. objc_getMetaClass([NSStringFromClass(GULRuntimeClassSnapshotTestClassClass) UTF8String]);
  107. GULRuntimeClassSnapshot *snapshot1 =
  108. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  109. [snapshot1 capture];
  110. SEL selector = _cmd;
  111. Method method = class_getInstanceMethod([self class], selector);
  112. IMP imp = method_getImplementation(method);
  113. const char *typeEncoding = method_getTypeEncoding(method);
  114. class_addMethod(GULRuntimeClassSnapshotTestClassMetaClass, selector, imp, typeEncoding);
  115. GULRuntimeClassSnapshot *snapshot2 =
  116. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  117. [snapshot2 capture];
  118. GULRuntimeClassDiff *simpleChangeDiff = [[GULRuntimeClassDiff alloc] init];
  119. simpleChangeDiff.aClass = GULRuntimeClassSnapshotTestClassClass;
  120. NSString *selectorString = NSStringFromSelector(selector);
  121. simpleChangeDiff.addedClassSelectors = [[NSSet alloc] initWithObjects:selectorString, nil];
  122. GULRuntimeClassDiff *snapShotDiff = [snapshot1 diff:snapshot2];
  123. XCTAssertEqualObjects(snapShotDiff, simpleChangeDiff);
  124. }
  125. /** Tests that adding an instance selector is detected between two snapshots. */
  126. - (void)testAddingAnInstanceMethodDetected {
  127. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  128. GULRuntimeClassSnapshot *snapshot1 =
  129. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  130. [snapshot1 capture];
  131. SEL selector = _cmd;
  132. Method method = class_getInstanceMethod([self class], selector);
  133. IMP imp = method_getImplementation(method);
  134. const char *typeEncoding = method_getTypeEncoding(method);
  135. class_addMethod(GULRuntimeClassSnapshotTestClassClass, selector, imp, typeEncoding);
  136. GULRuntimeClassSnapshot *snapshot2 =
  137. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  138. [snapshot2 capture];
  139. GULRuntimeClassDiff *simpleChangeDiff = [[GULRuntimeClassDiff alloc] init];
  140. simpleChangeDiff.aClass = GULRuntimeClassSnapshotTestClassClass;
  141. NSString *selectorString = NSStringFromSelector(selector);
  142. simpleChangeDiff.addedInstanceSelectors = [[NSSet alloc] initWithObjects:selectorString, nil];
  143. GULRuntimeClassDiff *snapShotDiff = [snapshot1 diff:snapshot2];
  144. XCTAssertEqualObjects(snapShotDiff, simpleChangeDiff);
  145. }
  146. /** Tests that modifying the IMP of a class selector is detected between two snapshots. */
  147. - (void)testModifiedClassImp {
  148. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  149. Class GULRuntimeClassSnapshotTestClassMetaClass =
  150. objc_getMetaClass([NSStringFromClass(GULRuntimeClassSnapshotTestClassClass) UTF8String]);
  151. GULRuntimeClassSnapshot *snapshot1 =
  152. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  153. [snapshot1 capture];
  154. SEL selector = @selector(description);
  155. Method method = class_getInstanceMethod(GULRuntimeClassSnapshotTestClassMetaClass, selector);
  156. IMP originalImp = method_getImplementation(method);
  157. IMP imp = imp_implementationWithBlock(^NSString *(id _self) {
  158. return @"fakeDescription";
  159. });
  160. IMP probableOriginalImp = method_setImplementation(method, imp);
  161. XCTAssertEqual(probableOriginalImp, originalImp);
  162. IMP imp2 = method_getImplementation(method);
  163. XCTAssertEqual(imp2, imp);
  164. GULRuntimeClassSnapshot *snapshot2 =
  165. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  166. [snapshot2 capture];
  167. GULRuntimeClassDiff *snapshotDiff = [snapshot1 diff:snapshot2];
  168. XCTAssertNotNil(snapshotDiff.modifiedImps);
  169. XCTAssertEqual(snapshotDiff.modifiedImps.count, 1);
  170. NSString *originalImpAddress = [NSString stringWithFormat:@"%p", originalImp];
  171. NSString *modifiedImp = [snapshotDiff.modifiedImps anyObject];
  172. XCTAssertTrue([modifiedImp containsString:@"+["]);
  173. XCTAssertTrue([modifiedImp containsString:originalImpAddress]);
  174. }
  175. /** Tests that modifying the IMP of an instance selector is detected between two snapshots. */
  176. - (void)testModifiedInstanceImp {
  177. Class GULRuntimeClassSnapshotTestClassClass = [GULRuntimeClassSnapshotTestClass class];
  178. GULRuntimeClassSnapshot *snapshot1 =
  179. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  180. [snapshot1 capture];
  181. SEL selector = @selector(description);
  182. Method method = class_getInstanceMethod(GULRuntimeClassSnapshotTestClassClass, selector);
  183. IMP originalImp = method_getImplementation(method);
  184. IMP imp = imp_implementationWithBlock(^NSString *(id _self) {
  185. return @"fakeDescription";
  186. });
  187. IMP probableOriginalImp = method_setImplementation(method, imp);
  188. XCTAssertEqual(probableOriginalImp, originalImp);
  189. IMP imp2 = method_getImplementation(method);
  190. XCTAssertEqual(imp2, imp);
  191. GULRuntimeClassSnapshot *snapshot2 =
  192. [[GULRuntimeClassSnapshot alloc] initWithClass:GULRuntimeClassSnapshotTestClassClass];
  193. [snapshot2 capture];
  194. GULRuntimeClassDiff *snapshotDiff = [snapshot1 diff:snapshot2];
  195. XCTAssertNotNil(snapshotDiff.modifiedImps);
  196. XCTAssertEqual(snapshotDiff.modifiedImps.count, 1);
  197. NSString *originalImpAddress = [NSString stringWithFormat:@"%p", originalImp];
  198. NSString *modifiedImp = [snapshotDiff.modifiedImps anyObject];
  199. XCTAssertTrue([modifiedImp containsString:@"-["]);
  200. XCTAssertTrue([modifiedImp containsString:originalImpAddress]);
  201. }
  202. @end