forked from FLEXTool/FLEX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FLEXTests.m
170 lines (139 loc) · 5.67 KB
/
FLEXTests.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//
// FLEXTests.m
// FLEXTests
//
// Created by Tanner Bennett on 8/27/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "NSObject+FLEX_Reflection.h"
#import "FLEXObjcInternal.h"
#import "FLEXHeapEnumerator.h"
#import "FLEXObjectRef.h"
#import "NSArray+FLEX.h"
#import "FLEXPropertyAttributes.h"
#import "FLEXProperty.h"
#import "FLEXUtility.h"
#import "FLEXRuntimeUtility.h"
#import "FLEXMethod.h"
#import "FLEXIvar.h"
#import "FLEXNewRootClass.h"
@interface Subclass : NSObject {
@public
NSUInteger *_indexes;
}
@end
@implementation Subclass @end
@interface NeverCreated : Subclass {
NSInteger a, b, c;
}
@end
@implementation NeverCreated @end
@interface FLEXTests : XCTestCase
@property (nonatomic, setter=setMyFoo:) id foo;
@end
@implementation FLEXTests
- (void)testRuntimeAdditions {
XCTAssertEqual(1, NSObject.flex_classHierarchy.count);
XCTAssertEqual(4, FLEXTests.flex_classHierarchy.count);
XCTAssertEqual(FLEXTests.flex_classHierarchy.firstObject, [self class]);
XCTAssertEqual(FLEXTests.flex_classHierarchy.lastObject, [NSObject class]);
}
- (void)testAssumptionsAboutClasses {
Class cls = [self class];
Class meta = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
Class rootMeta = object_getClass(meta);
// Subsequent `class` calls yield self
XCTAssertEqual(cls, [cls class]);
XCTAssertEqual(meta, [meta class]);
// A class's superclass is NOT a metaclass
XCTAssertFalse(class_isMetaClass([cls superclass]));
// A metaclass's superclass IS a metaclass
XCTAssertTrue(class_isMetaClass([meta superclass]));
// Subsequent object_getClass calls yield metaclass
XCTAssertEqual(object_getClass(cls), meta);
XCTAssertEqual(object_getClass(object_getClass(meta)), rootMeta);
// Superclass of a root class is nil
XCTAssertNil(NSObject.superclass);
}
- (void)testAssumptionsAboutMessageSending {
// "instances respond to selector" works with metaclasses targeting class objects
Class meta = object_getClass(NSBundle.class);
XCTAssertTrue([meta instancesRespondToSelector:@selector(mainBundle)]);
XCTAssertFalse([meta respondsToSelector:@selector(mainBundle)]);
}
- (void)testAssumptionsAboutRuntimeMethodFunctions {
Class cls = [NSBundle class];
Class meta = object_getClass(cls);
Method bundleID = class_getInstanceMethod(cls, @selector(bundleIdentifier));
Method mainBundle = class_getClassMethod(cls, @selector(mainBundle));
// Preconditions...
XCTAssert(class_isMetaClass(meta));
XCTAssert(bundleID != nil);
XCTAssert(mainBundle != nil);
// Metaclasses cannot find instance methods
XCTAssertEqual(nil, class_getInstanceMethod(meta, @selector(bundleIdentifier)));
// Metaclasses can find class methods as both class methods and instance methods,
// and the methods found by metaclasses are equal regardless of which function is used
XCTAssertEqual(mainBundle, class_getClassMethod(meta, @selector(mainBundle)));
// Metaclasses can find class methods as instance methods
XCTAssertEqual(mainBundle, class_getInstanceMethod(meta, @selector(mainBundle)));
}
- (void)testAbilitiesOfKVC {
[self setValue:@5 forKey:@"foo"];
XCTAssertEqualObjects(self.foo, @5);
}
- (void)testCPPTypeEncoding {
const char *type = "{basic_string<char, std::__1::char_traits<char>, "
"std::__1::allocator<char> >={__compressed_pair<std::__1::basic_string<char, "
"std::__1::char_traits<char>, std::__1::allocator<char> >::__rep, "
"std::__1::allocator<char> >={__rep}}}";
XCTAssertThrows(NSGetSizeAndAlignment(type, nil, nil));
}
- (void)testGetClassProperties {
NSArray *props = NSBundle.flex_allClassProperties;
props = [props flex_filtered:^BOOL(FLEXProperty *obj, NSUInteger idx) {
return [obj.name isEqualToString:@"mainBundle"];
}];
XCTAssert(props.count == 1);
}
- (void)testIvarUnboxing {
NSUInteger array[4] = { 0xaa, 0xbb, 0xcc, 0x00 };
Subclass *obj = [Subclass new];
obj->_indexes = array;
FLEXIvar *ivar = [Subclass flex_ivarNamed:@"_indexes"];
NSValue *arrayValue = [ivar getPotentiallyUnboxedValue:obj];
NSUInteger *pointerValue = arrayValue.pointerValue;
XCTAssert(pointerValue != nil);
XCTAssertEqual(pointerValue, (NSUInteger *)&array);
XCTAssertEqual(pointerValue[0], 0xaa);
}
- (void)testSafeRespondsToSelector {
XCTAssertFalse([FLEXRuntimeUtility
safeObject:[NSObject class] respondsToSelector:@selector(testSafeRespondsToSelector)
]);
Class root = NSClassFromString(@"FLEXNewRootClass");
XCTAssertTrue([FLEXRuntimeUtility safeObject:root respondsToSelector:@selector(theOnlyMethod)]);
XCTAssertFalse([FLEXRuntimeUtility safeObject:root respondsToSelector:@selector(class)]);
}
- (void)testSafeGetClassName {
id instance = [NSClassFromString(@"FLEXNewRootClass") alloc];
NSString *className = [FLEXRuntimeUtility safeClassNameForObject:instance];
XCTAssertEqualObjects(@"FLEXNewRootClass", className);
}
- (void)testInvalidObjectFinding {
// Create something that looks like an objc object
uintptr_t *pointer = (uintptr_t *)calloc(1, sizeof(uintptr_t));
object_setClass((__bridge id)(void *)pointer, [NeverCreated class]);
// Find that one object and assert that it is the object we just created
NSArray<FLEXObjectRef *> *refs = [FLEXHeapEnumerator
instancesOfClassWithName:@"NeverCreated" retained:NO
];
XCTAssertEqual(refs.count, 1);
XCTAssertEqual((__bridge void *)refs.firstObject.object, pointer);
// Find that the object isn't really an object
XCTAssertFalse(FLEXPointerIsValidObjcObject(pointer));
free(pointer);
}
@end