Skip to content

Commit 8bfd7e6

Browse files
committed
Add test helper RcTestObject to test that rc::Id works properly
1 parent d4586ea commit 8bfd7e6

File tree

4 files changed

+256
-41
lines changed

4 files changed

+256
-41
lines changed

objc2/src/rc/id.rs

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -606,49 +606,93 @@ impl<T: UnwindSafe + ?Sized> UnwindSafe for Id<T, Owned> {}
606606

607607
#[cfg(test)]
608608
mod tests {
609-
use super::{Id, Owned, Shared};
610-
use crate::rc::autoreleasepool;
609+
use super::*;
610+
use crate::msg_send;
611+
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
611612
use crate::runtime::Object;
612-
use crate::{class, msg_send};
613613

614-
fn retain_count(obj: &Object) -> usize {
615-
unsafe { msg_send![obj, retainCount] }
614+
#[track_caller]
615+
fn assert_retain_count(obj: &Object, expected: usize) {
616+
let retain_count: usize = unsafe { msg_send![obj, retainCount] };
617+
assert_eq!(retain_count, expected);
616618
}
617619

618620
#[test]
619-
fn test_autorelease() {
620-
let obj: Id<Object, Shared> = unsafe { Id::new(msg_send![class!(NSObject), new]).unwrap() };
621+
fn test_drop() {
622+
let mut expected = ThreadTestData::current();
623+
624+
let obj = RcTestObject::new();
625+
expected.alloc += 1;
626+
expected.init += 1;
627+
expected.assert_current();
628+
629+
drop(obj);
630+
expected.release += 1;
631+
expected.dealloc += 1;
632+
expected.assert_current();
633+
}
621634

635+
#[test]
636+
fn test_autorelease() {
637+
let obj: Id<_, Shared> = RcTestObject::new().into();
622638
let cloned = obj.clone();
639+
let mut expected = ThreadTestData::current();
623640

624641
autoreleasepool(|pool| {
625642
let _ref = obj.autorelease(pool);
626-
assert_eq!(retain_count(&*cloned), 2);
643+
expected.autorelease += 1;
644+
expected.assert_current();
645+
assert_retain_count(&cloned, 2);
627646
});
647+
expected.release += 1;
648+
expected.assert_current();
649+
assert_retain_count(&cloned, 1);
628650

629-
// make sure that the autoreleased value has been released
630-
// TODO: Investigate if this is flaky on GNUStep
631-
assert_eq!(retain_count(&*cloned), 1);
651+
autoreleasepool(|pool| {
652+
let _ref = cloned.autorelease(pool);
653+
expected.autorelease += 1;
654+
expected.assert_current();
655+
});
656+
expected.release += 1;
657+
expected.dealloc += 1;
658+
expected.assert_current();
632659
}
633660

634661
#[test]
635662
fn test_clone() {
636-
let cls = class!(NSObject);
637-
let obj: Id<Object, Owned> = unsafe {
638-
let obj: *mut Object = msg_send![cls, alloc];
639-
let obj: *mut Object = msg_send![obj, init];
640-
Id::new(obj).unwrap()
641-
};
642-
assert_eq!(retain_count(&obj), 1);
663+
let obj: Id<_, Owned> = RcTestObject::new();
664+
assert_retain_count(&obj, 1);
665+
let mut expected = ThreadTestData::current();
643666

644667
let obj: Id<_, Shared> = obj.into();
645-
assert_eq!(retain_count(&obj), 1);
668+
expected.assert_current();
669+
assert_retain_count(&obj, 1);
646670

647671
let cloned = obj.clone();
648-
assert_eq!(retain_count(&cloned), 2);
649-
assert_eq!(retain_count(&obj), 2);
672+
expected.retain += 1;
673+
expected.assert_current();
674+
assert_retain_count(&cloned, 2);
675+
assert_retain_count(&obj, 2);
650676

651677
drop(obj);
652-
assert_eq!(retain_count(&cloned), 1);
678+
expected.release += 1;
679+
expected.assert_current();
680+
assert_retain_count(&cloned, 1);
681+
682+
drop(cloned);
683+
expected.release += 1;
684+
expected.dealloc += 1;
685+
expected.assert_current();
686+
}
687+
688+
#[test]
689+
fn test_retain_autoreleased_works_as_retain() {
690+
let obj: Id<_, Shared> = RcTestObject::new().into();
691+
let mut expected = ThreadTestData::current();
692+
693+
let ptr = Id::as_ptr(&obj) as *mut RcTestObject;
694+
let _obj2: Id<_, Shared> = unsafe { Id::retain_autoreleased(ptr) }.unwrap();
695+
expected.retain += 1;
696+
expected.assert_current();
653697
}
654698
}

objc2/src/rc/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,18 @@ mod id_traits;
6262
mod ownership;
6363
mod weak_id;
6464

65+
#[cfg(test)]
66+
mod test_object;
67+
6568
pub use self::autorelease::{autoreleasepool, AutoreleasePool, AutoreleaseSafe};
6669
pub use self::id::Id;
6770
pub use self::id_traits::{DefaultId, SliceId, SliceIdMut};
6871
pub use self::ownership::{Owned, Ownership, Shared};
6972
pub use self::weak_id::WeakId;
7073

74+
#[cfg(test)]
75+
pub(crate) use self::test_object::{RcTestObject, ThreadTestData};
76+
7177
#[cfg(test)]
7278
mod tests {
7379
use core::marker::PhantomData;

objc2/src/rc/test_object.rs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
use core::cell::RefCell;
2+
use core::ops::{Deref, DerefMut};
3+
use std::sync::Once;
4+
5+
use super::{Id, Owned};
6+
use crate::declare::ClassBuilder;
7+
use crate::runtime::{Bool, Class, Object, Sel};
8+
use crate::{msg_send, msg_send_bool};
9+
use crate::{Encoding, Message, RefEncode};
10+
11+
#[derive(Debug, Clone, Default, PartialEq)]
12+
pub(crate) struct ThreadTestData {
13+
pub(crate) alloc: usize,
14+
pub(crate) dealloc: usize,
15+
pub(crate) init: usize,
16+
pub(crate) retain: usize,
17+
pub(crate) release: usize,
18+
pub(crate) autorelease: usize,
19+
pub(crate) try_retain: usize,
20+
pub(crate) try_retain_fail: usize,
21+
}
22+
23+
impl ThreadTestData {
24+
/// Get the amount of method calls performed on the current thread.
25+
pub(crate) fn current() -> ThreadTestData {
26+
TEST_DATA.with(|data| data.borrow().clone())
27+
}
28+
29+
#[track_caller]
30+
pub(crate) fn assert_current(&self) {
31+
let current = Self::current();
32+
assert_eq!(&current, self);
33+
}
34+
}
35+
36+
std::thread_local! {
37+
pub(crate) static TEST_DATA: RefCell<ThreadTestData> = RefCell::new(Default::default());
38+
}
39+
40+
/// A helper object that counts how many times various reference-counting
41+
/// primitives are called.
42+
#[repr(C)]
43+
pub(crate) struct RcTestObject {
44+
inner: Object,
45+
}
46+
47+
unsafe impl RefEncode for RcTestObject {
48+
const ENCODING_REF: Encoding<'static> = Object::ENCODING_REF;
49+
}
50+
51+
unsafe impl Message for RcTestObject {}
52+
53+
unsafe impl Send for RcTestObject {}
54+
unsafe impl Sync for RcTestObject {}
55+
56+
impl Deref for RcTestObject {
57+
type Target = Object;
58+
fn deref(&self) -> &Self::Target {
59+
&self.inner
60+
}
61+
}
62+
63+
impl DerefMut for RcTestObject {
64+
fn deref_mut(&mut self) -> &mut Self::Target {
65+
&mut self.inner
66+
}
67+
}
68+
69+
impl RcTestObject {
70+
fn class() -> &'static Class {
71+
static REGISTER_CLASS: Once = Once::new();
72+
73+
REGISTER_CLASS.call_once(|| {
74+
extern "C" fn alloc(cls: &Class, _cmd: Sel) -> *mut RcTestObject {
75+
TEST_DATA.with(|data| data.borrow_mut().alloc += 1);
76+
let superclass = class!(NSObject).metaclass();
77+
unsafe { msg_send![super(cls, superclass), alloc] }
78+
}
79+
extern "C" fn init(this: &mut RcTestObject, _cmd: Sel) -> *mut RcTestObject {
80+
TEST_DATA.with(|data| data.borrow_mut().init += 1);
81+
unsafe { msg_send![super(this, class!(NSObject)), init] }
82+
}
83+
extern "C" fn retain(this: &RcTestObject, _cmd: Sel) -> *mut RcTestObject {
84+
TEST_DATA.with(|data| data.borrow_mut().retain += 1);
85+
unsafe { msg_send![super(this, class!(NSObject)), retain] }
86+
}
87+
extern "C" fn release(this: &RcTestObject, _cmd: Sel) {
88+
TEST_DATA.with(|data| data.borrow_mut().release += 1);
89+
unsafe { msg_send![super(this, class!(NSObject)), release] }
90+
}
91+
extern "C" fn autorelease(this: &RcTestObject, _cmd: Sel) -> *mut RcTestObject {
92+
TEST_DATA.with(|data| data.borrow_mut().autorelease += 1);
93+
unsafe { msg_send![super(this, class!(NSObject)), autorelease] }
94+
}
95+
unsafe extern "C" fn dealloc(_this: *mut RcTestObject, _cmd: Sel) {
96+
TEST_DATA.with(|data| data.borrow_mut().dealloc += 1);
97+
// Don't call superclass
98+
}
99+
unsafe extern "C" fn try_retain(this: &RcTestObject, _cmd: Sel) -> Bool {
100+
TEST_DATA.with(|data| data.borrow_mut().try_retain += 1);
101+
let res = unsafe { msg_send_bool![super(this, class!(NSObject)), _tryRetain] };
102+
if !res {
103+
TEST_DATA.with(|data| data.borrow_mut().try_retain -= 1);
104+
TEST_DATA.with(|data| data.borrow_mut().try_retain_fail += 1);
105+
}
106+
Bool::from(res)
107+
}
108+
109+
let mut builder = ClassBuilder::new("RcTestObject", class!(NSObject)).unwrap();
110+
unsafe {
111+
builder.add_class_method(
112+
sel!(alloc),
113+
alloc as extern "C" fn(&Class, Sel) -> *mut RcTestObject,
114+
);
115+
builder.add_method(
116+
sel!(init),
117+
init as extern "C" fn(&mut RcTestObject, Sel) -> _,
118+
);
119+
builder.add_method(
120+
sel!(retain),
121+
retain as extern "C" fn(&RcTestObject, Sel) -> _,
122+
);
123+
builder.add_method(
124+
sel!(_tryRetain),
125+
try_retain as unsafe extern "C" fn(&RcTestObject, Sel) -> Bool,
126+
);
127+
builder.add_method(sel!(release), release as extern "C" fn(&RcTestObject, Sel));
128+
builder.add_method(
129+
sel!(autorelease),
130+
autorelease as extern "C" fn(&RcTestObject, Sel) -> _,
131+
);
132+
builder.add_method(
133+
sel!(dealloc),
134+
dealloc as unsafe extern "C" fn(*mut RcTestObject, Sel),
135+
);
136+
}
137+
138+
builder.register();
139+
});
140+
141+
class!(RcTestObject)
142+
}
143+
144+
pub(crate) fn new() -> Id<Self, Owned> {
145+
unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap()
146+
}
147+
}

objc2/src/rc/weak_id.rs

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -150,45 +150,63 @@ impl<T: Message> TryFrom<WeakId<T>> for Id<T, Shared> {
150150

151151
#[cfg(test)]
152152
mod tests {
153-
use super::WeakId;
154-
use super::{Id, Shared};
153+
use super::*;
154+
use crate::rc::{RcTestObject, ThreadTestData};
155155
use crate::runtime::Object;
156-
use crate::{class, msg_send};
157156

158157
#[test]
159158
fn test_weak() {
160-
let cls = class!(NSObject);
161-
let obj: Id<Object, Shared> = unsafe {
162-
let obj: *mut Object = msg_send![cls, alloc];
163-
let obj: *mut Object = msg_send![obj, init];
164-
Id::new(obj).unwrap()
165-
};
159+
let obj: Id<_, Shared> = RcTestObject::new().into();
160+
let mut expected = ThreadTestData::current();
166161

167162
let weak = WeakId::new(&obj);
163+
expected.assert_current();
164+
168165
let strong = weak.load().unwrap();
169-
let strong_ptr: *const Object = &*strong;
170-
let obj_ptr: *const Object = &*obj;
171-
assert_eq!(strong_ptr, obj_ptr);
172-
drop(strong);
166+
expected.try_retain += 1;
167+
expected.assert_current();
168+
assert!(ptr::eq(&*strong, &*obj));
173169

174170
drop(obj);
171+
drop(strong);
172+
expected.release += 2;
173+
expected.dealloc += 1;
174+
expected.assert_current();
175+
175176
assert!(weak.load().is_none());
177+
expected.try_retain_fail += 1;
178+
expected.assert_current();
179+
180+
drop(weak);
181+
expected.assert_current();
176182
}
177183

178184
#[test]
179185
fn test_weak_clone() {
180-
let obj: Id<Object, Shared> = unsafe { Id::new(msg_send![class!(NSObject), new]).unwrap() };
186+
let obj: Id<_, Shared> = RcTestObject::new().into();
187+
let mut expected = ThreadTestData::current();
188+
181189
let weak = WeakId::new(&obj);
190+
expected.assert_current();
182191

183192
let weak2 = weak.clone();
193+
expected.try_retain += 1;
194+
expected.release += 1;
195+
expected.assert_current();
184196

185197
let strong = weak.load().unwrap();
198+
expected.try_retain += 1;
199+
expected.assert_current();
200+
assert!(ptr::eq(&*strong, &*obj));
201+
186202
let strong2 = weak2.load().unwrap();
187-
let strong_ptr: *const Object = &*strong;
188-
let strong2_ptr: *const Object = &*strong2;
189-
let obj_ptr: *const Object = &*obj;
190-
assert_eq!(strong_ptr, obj_ptr);
191-
assert_eq!(strong2_ptr, obj_ptr);
203+
expected.try_retain += 1;
204+
expected.assert_current();
205+
assert!(ptr::eq(&*strong, &*strong2));
206+
207+
drop(weak);
208+
drop(weak2);
209+
expected.assert_current();
192210
}
193211

194212
#[test]

0 commit comments

Comments
 (0)