Skip to content

Commit f5b83fd

Browse files
committed
Test runtime introspection on a class created by Objective-C
In particular, I wanted to make sure that ivars actually work as they should
1 parent dd3c422 commit f5b83fd

File tree

5 files changed

+220
-4
lines changed

5 files changed

+220
-4
lines changed

tests/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ gnustep-2-0 = ["gnustep-1-9", "block2/gnustep-2-0", "objc2-foundation/gnustep-2-
3131
gnustep-2-1 = ["gnustep-2-0", "block2/gnustep-2-1", "objc2-foundation/gnustep-2-1"]
3232
winobjc = ["gnustep-1-8", "block2/winobjc", "objc2-foundation/winobjc"]
3333

34+
malloc = ["objc2/malloc"]
35+
3436
[dependencies]
3537
block2 = { path = "../block2", default-features = false }
3638
block-sys = { path = "../block-sys", default-features = false }

tests/build.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::env;
22

33
fn main() {
4-
println!("cargo:rerun-if-changed=extern/block_utils.c");
5-
println!("cargo:rerun-if-changed=extern/encode_utils.m");
4+
println!("cargo:rerun-if-changed=build.rs");
65

76
let mut builder = cc::Build::new();
87
builder.compiler("clang");
98
builder.file("extern/block_utils.c");
9+
println!("cargo:rerun-if-changed=extern/block_utils.c");
1010

1111
for flag in env::var("DEP_BLOCK_0_0_CC_ARGS").unwrap().split(' ') {
1212
builder.flag(flag);
@@ -17,13 +17,16 @@ fn main() {
1717
let mut builder = cc::Build::new();
1818
builder.compiler("clang");
1919
builder.file("extern/encode_utils.m");
20+
builder.file("extern/test_object.m");
21+
println!("cargo:rerun-if-changed=extern/encode_utils.m");
22+
println!("cargo:rerun-if-changed=extern/test_object.m");
2023

2124
for flag in env::var("DEP_OBJC_0_2_CC_ARGS").unwrap().split(' ') {
2225
builder.flag(flag);
2326
}
2427

28+
builder.compile("libobjc_utils.a");
29+
2530
// For assembly tests
2631
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
27-
28-
builder.compile("libencode_utils.a");
2932
}

tests/extern/test_object.m

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include <Foundation/NSObject.h>
2+
3+
@interface MyTestObject: NSObject <NSObject> {
4+
int var1;
5+
BOOL var2;
6+
}
7+
8+
+ (instancetype) getAutoreleasedInstance;
9+
+ (int) add: (int) a and: (int) b;
10+
11+
- (int) var1;
12+
- (void) addToVar1: (int) number;
13+
14+
- (BOOL) var2;
15+
@end
16+
17+
18+
@implementation MyTestObject
19+
- (id)init {
20+
self = [super init];
21+
if (self) {
22+
var1 = 42;
23+
var2 = YES;
24+
}
25+
return self;
26+
}
27+
28+
+ (instancetype) getAutoreleasedInstance {
29+
return [[MyTestObject alloc] init];
30+
}
31+
32+
+ (int) add: (int) a and: (int) b {
33+
return a + b;
34+
}
35+
36+
- (int) var1 {
37+
return var1;
38+
}
39+
40+
- (void) addToVar1: (int) number {
41+
var1 += number;
42+
}
43+
44+
- (BOOL) var2 {
45+
return var2;
46+
}
47+
@end

tests/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ extern crate std;
1010
pub mod ffi;
1111
#[cfg(test)]
1212
mod test_encode_utils;
13+
#[cfg(test)]
14+
mod test_object;
15+
1316
use crate::ffi::LargeStruct;
1417

1518
pub fn get_int_block_with(i: i32) -> RcBlock<(), i32> {

tests/src/test_object.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use core::mem::size_of;
2+
use std::os::raw::c_int;
3+
4+
use objc2::rc::{autoreleasepool, AutoreleasePool, Id, Owned};
5+
use objc2::runtime::{Bool, Class, Protocol};
6+
#[cfg(feature = "malloc")]
7+
use objc2::sel;
8+
use objc2::{class, msg_send, msg_send_bool};
9+
use objc2_foundation::NSObject;
10+
11+
#[repr(C)]
12+
struct MyTestObject {
13+
inner: NSObject,
14+
}
15+
16+
unsafe impl ::objc2::Message for MyTestObject {}
17+
18+
unsafe impl ::objc2::RefEncode for MyTestObject {
19+
const ENCODING_REF: ::objc2::Encoding<'static> = ::objc2::Encoding::Object;
20+
}
21+
22+
impl MyTestObject {
23+
fn class() -> &'static Class {
24+
class!(MyTestObject)
25+
}
26+
27+
fn new() -> Id<Self, Owned> {
28+
let cls = Self::class();
29+
unsafe { Id::new(msg_send![cls, new]).unwrap() }
30+
}
31+
32+
fn new_autoreleased<'p>(pool: &'p AutoreleasePool) -> &'p Self {
33+
let cls = Self::class();
34+
let ptr: *const Self = unsafe { msg_send![cls, getAutoreleasedInstance] };
35+
unsafe { pool.ptr_as_ref(ptr) }
36+
}
37+
38+
fn add_numbers(a: c_int, b: c_int) -> c_int {
39+
let cls = Self::class();
40+
unsafe { msg_send![cls, add: a, and: b] }
41+
}
42+
43+
fn var1(&self) -> c_int {
44+
unsafe { msg_send![self, var1] }
45+
}
46+
47+
fn var1_ivar(&self) -> &c_int {
48+
unsafe { self.inner.ivar("var1") }
49+
}
50+
51+
fn var1_ivar_mut(&mut self) -> &mut c_int {
52+
unsafe { self.inner.ivar_mut("var1") }
53+
}
54+
55+
fn add_to_ivar1(&mut self, number: c_int) {
56+
unsafe { msg_send![self, addToVar1: number] }
57+
}
58+
59+
fn var2(&self) -> bool {
60+
unsafe { msg_send_bool![self, var2] }
61+
}
62+
63+
fn var2_ivar(&self) -> &Bool {
64+
unsafe { self.inner.ivar("var2") }
65+
}
66+
67+
fn var2_ivar_mut(&mut self) -> &mut Bool {
68+
unsafe { self.inner.ivar_mut("var2") }
69+
}
70+
}
71+
72+
#[cfg(feature = "malloc")]
73+
macro_rules! assert_in {
74+
($item:expr, $lst:expr) => {{
75+
let mut found = false;
76+
for &x in $lst.iter() {
77+
if x == $item {
78+
found = true;
79+
}
80+
}
81+
assert!(
82+
found,
83+
"Did not find {} in {}",
84+
stringify!($item),
85+
stringify!($lst),
86+
);
87+
}};
88+
}
89+
90+
#[test]
91+
fn test_class() {
92+
let cls = MyTestObject::class();
93+
assert_eq!(MyTestObject::add_numbers(-3, 15), 12);
94+
95+
#[cfg(feature = "malloc")]
96+
{
97+
let classes = Class::classes();
98+
assert_eq!(classes.len(), Class::classes_count());
99+
assert_in!(cls, classes);
100+
}
101+
102+
// Test objc2::runtime functionality
103+
assert_eq!(Class::get("MyTestObject"), Some(cls));
104+
assert_ne!(cls, class!(NSObject));
105+
assert_eq!(cls.name(), "MyTestObject");
106+
assert_eq!(cls.superclass(), Some(class!(NSObject)));
107+
assert_eq!(cls.metaclass().name(), "MyTestObject");
108+
assert_ne!(cls.metaclass(), cls);
109+
assert_eq!(cls.instance_size(), {
110+
#[repr(C)]
111+
struct MyTestObjectLayout {
112+
isa: *const Class,
113+
var1: c_int,
114+
var2: Bool,
115+
}
116+
size_of::<MyTestObjectLayout>()
117+
});
118+
119+
let protocol = Protocol::get("NSObject").unwrap();
120+
assert!(cls.conforms_to(protocol));
121+
assert!(!cls.conforms_to(Protocol::get("NSCopying").unwrap()));
122+
#[cfg(feature = "malloc")]
123+
assert_in!(protocol, cls.adopted_protocols());
124+
125+
#[cfg(feature = "malloc")]
126+
{
127+
let method = cls.instance_method(sel!(addToVar1:)).unwrap();
128+
assert_in!(method, cls.instance_methods());
129+
130+
let ivar = cls.instance_variable("var1").unwrap();
131+
assert_in!(ivar, cls.instance_variables());
132+
}
133+
}
134+
135+
#[test]
136+
fn test_object() {
137+
autoreleasepool(|pool| {
138+
let _obj = MyTestObject::new_autoreleased(pool);
139+
});
140+
141+
let mut obj = MyTestObject::new();
142+
assert_eq!((*obj.inner).class(), MyTestObject::class());
143+
144+
assert_eq!(obj.var1(), 42);
145+
assert_eq!(*obj.var1_ivar(), 42);
146+
147+
obj.add_to_ivar1(3);
148+
assert_eq!(obj.var1(), 45);
149+
assert_eq!(*obj.var1_ivar(), 45);
150+
151+
*obj.var1_ivar_mut() = 100;
152+
assert_eq!(obj.var1(), 100);
153+
assert_eq!(*obj.var1_ivar(), 100);
154+
155+
assert!(obj.var2());
156+
assert!(obj.var2_ivar().is_true());
157+
158+
*obj.var2_ivar_mut() = Bool::NO;
159+
assert!(!obj.var2());
160+
assert!(obj.var2_ivar().is_false());
161+
}

0 commit comments

Comments
 (0)