GULRuntimeClassSnapshotTests.m 10 KB

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