comparison rust/pyo3-sharedref/tests/test_sharedref.rs @ 52607:a7d2529ed6dd

rust-pyo3-sharedref: converted integration tests from rust-cpython This should bring full confidence on the conversion to PyO3. It highlights also the difference between the two bindings systems: in PyO3, the struct defined by the user is the inner Rust one. In rust-cpython, it is the wrapped one exposed to CPythob We enclose some of the boilerplate in helper functions. Perhaps we should first import the rust-cpython integration test, rework it with the same helpers, then only change the helpers.
author Georges Racinet <georges.racinet@octobus.net>
date Sat, 14 Dec 2024 18:21:56 +0100
parents
children d85514a88706
comparison
equal deleted inserted replaced
52606:be765f6797cc 52607:a7d2529ed6dd
1 use pyo3::prelude::*;
2 use pyo3_sharedref::*;
3
4 #[pyclass]
5 struct Owner {
6 string: PySharedRefCell<String>,
7 }
8
9 #[pymethods]
10 impl Owner {
11 #[new]
12 fn new(s: String) -> Self {
13 Self {
14 string: PySharedRefCell::new(s),
15 }
16 }
17 }
18
19 fn with_setup(
20 test: impl FnOnce(Python<'_>, &Bound<'_, Owner>) -> PyResult<()>,
21 ) -> PyResult<()> {
22 pyo3::prepare_freethreaded_python();
23 Python::with_gil(|py| {
24 let owner = Bound::new(py, Owner::new("new".to_owned()))?;
25 test(py, &owner)
26 })
27 }
28
29 /// "leak" in the sense of `UnsafePyLeaked` the `string` data field,
30 /// taking care of all the boilerplate
31 fn leak_string(owner: &Bound<'_, Owner>) -> UnsafePyLeaked<&'static String> {
32 let cell = &owner.borrow().string;
33 let shared_ref = unsafe { cell.borrow(owner) };
34 shared_ref.leak_immutable()
35 }
36
37 fn try_leak_string(
38 owner: &Bound<'_, Owner>,
39 ) -> Result<UnsafePyLeaked<&'static String>, TryLeakError> {
40 let cell = &owner.borrow().string;
41 let shared_ref = unsafe { cell.borrow(owner) };
42 shared_ref.try_leak_immutable()
43 }
44
45 /// Mutate the `string` field of `owner` as would be done from Python code
46 ///
47 /// This is to simulate normal mutation of the owner object from
48 /// the Python interpreter. This could be replaced by methods of [`Owner`]
49 /// (wih closure replaced by a small fixed operations)
50 /// and perhaps will, once we are done converting the original tests
51 /// from rust-cpython
52 fn mutate_string<'py>(
53 owner: &'py Bound<'py, Owner>,
54 f: impl FnOnce(&mut String),
55 ) -> () {
56 let cell = &owner.borrow_mut().string;
57 let shared_ref = unsafe { cell.borrow(owner) };
58 f(&mut shared_ref.borrow_mut());
59 }
60
61 #[test]
62 fn test_leaked_borrow() -> PyResult<()> {
63 with_setup(|py, owner| {
64 let leaked = leak_string(owner);
65 let leaked_ref = unsafe { leaked.try_borrow(py) }.unwrap();
66 assert_eq!(*leaked_ref, "new");
67 Ok(())
68 })
69 }
70
71 #[test]
72 fn test_leaked_borrow_mut() -> PyResult<()> {
73 with_setup(|py, owner| {
74 let leaked = leak_string(owner);
75 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
76 let mut leaked_ref =
77 unsafe { leaked_iter.try_borrow_mut(py) }.unwrap();
78 assert_eq!(leaked_ref.next(), Some('n'));
79 assert_eq!(leaked_ref.next(), Some('e'));
80 assert_eq!(leaked_ref.next(), Some('w'));
81 assert_eq!(leaked_ref.next(), None);
82 Ok(())
83 })
84 }
85
86 #[test]
87 fn test_leaked_borrow_after_mut() -> PyResult<()> {
88 with_setup(|py, owner| {
89 let leaked = leak_string(owner);
90 mutate_string(owner, String::clear);
91 assert!(unsafe { leaked.try_borrow(py) }.is_err());
92 Ok(())
93 })
94 }
95
96 #[test]
97 fn test_leaked_borrow_mut_after_mut() -> PyResult<()> {
98 with_setup(|py, owner| {
99 let leaked = leak_string(owner);
100 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
101
102 mutate_string(owner, String::clear);
103 assert!(unsafe { leaked_iter.try_borrow_mut(py) }.is_err());
104 Ok(())
105 })
106 }
107
108 #[test]
109 #[should_panic(expected = "map() over invalidated leaked reference")]
110 fn test_leaked_map_after_mut() {
111 with_setup(|py, owner| {
112 let leaked = leak_string(owner);
113 mutate_string(owner, String::clear);
114 let _leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
115 Ok(())
116 })
117 .expect("should already have panicked")
118 }
119
120 /// run `try_borrow_mut` on the `string` field and assert it is not an error
121 ///
122 /// Simply returning the `Result` is not possible, because that is
123 /// returning a reference to data owned by the function
124 fn assert_try_borrow_string_mut_ok(owner: &Bound<'_, Owner>) {
125 let cell = &owner.borrow().string;
126 let shared_ref = unsafe { cell.borrow(owner) };
127 assert!(shared_ref.try_borrow_mut().is_ok());
128 }
129
130 fn assert_try_borrow_string_mut_err(owner: &Bound<'_, Owner>) {
131 let cell = &owner.borrow().string;
132 let shared_ref = unsafe { cell.borrow(owner) };
133 assert!(shared_ref.try_borrow_mut().is_err());
134 }
135
136 fn assert_try_borrow_string_err(owner: &Bound<'_, Owner>) {
137 let cell = &owner.borrow().string;
138 let shared_ref = unsafe { cell.borrow(owner) };
139 assert!(shared_ref.try_borrow().is_err());
140 }
141
142 #[test]
143 fn test_try_borrow_mut_while_leaked_ref() -> PyResult<()> {
144 with_setup(|py, owner| {
145 assert_try_borrow_string_mut_ok(owner);
146 let leaked = leak_string(owner);
147 {
148 let _leaked_ref = unsafe { leaked.try_borrow(py) }.unwrap();
149 assert_try_borrow_string_mut_err(owner);
150 {
151 let _leaked_ref2 = unsafe { leaked.try_borrow(py) }.unwrap();
152 assert_try_borrow_string_mut_err(owner);
153 }
154 assert_try_borrow_string_mut_err(owner);
155 }
156 assert_try_borrow_string_mut_ok(owner);
157 Ok(())
158 })
159 }
160
161 #[test]
162 fn test_try_borrow_mut_while_leaked_ref_mut() -> PyResult<()> {
163 with_setup(|py, owner| {
164 assert_try_borrow_string_mut_ok(owner);
165 let leaked = leak_string(owner);
166 let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) };
167 {
168 let _leaked_ref =
169 unsafe { leaked_iter.try_borrow_mut(py) }.unwrap();
170 assert_try_borrow_string_mut_err(owner);
171 }
172 assert_try_borrow_string_mut_ok(owner);
173 Ok(())
174 })
175 }
176
177 #[test]
178 fn test_try_leak_while_borrow_mut() -> PyResult<()> {
179 with_setup(|_py, owner| {
180 let cell = &owner.borrow().string;
181 let shared_ref = unsafe { cell.borrow(owner) };
182 let _mut_ref = shared_ref.borrow_mut();
183
184 assert!(try_leak_string(owner).is_err());
185 Ok(())
186 })
187 }
188
189 #[test]
190 #[should_panic(expected = "already mutably borrowed")]
191 fn test_leak_while_borrow_mut() {
192 with_setup(|_py, owner| {
193 let cell = &owner.borrow().string;
194 let shared_ref = unsafe { cell.borrow(owner) };
195 let _mut_ref = shared_ref.borrow_mut();
196
197 leak_string(owner);
198 Ok(())
199 })
200 .expect("should already have panicked")
201 }
202
203 #[test]
204 fn test_try_borrow_mut_while_borrow() -> PyResult<()> {
205 with_setup(|_py, owner| {
206 let cell = &owner.borrow().string;
207 let shared_ref = unsafe { cell.borrow(owner) };
208 let _ref = shared_ref.borrow();
209
210 assert_try_borrow_string_mut_err(owner);
211 Ok(())
212 })
213 }
214
215 #[test]
216 #[should_panic(expected = "already borrowed")]
217 fn test_borrow_mut_while_borrow() {
218 with_setup(|_py, owner| {
219 let cell = &owner.borrow().string;
220 let shared_ref = unsafe { cell.borrow(owner) };
221 let _ref = shared_ref.borrow();
222
223 let shared_ref2 = unsafe { cell.borrow(owner) };
224 let _mut_ref = shared_ref2.borrow_mut();
225 Ok(())
226 })
227 .expect("should already have panicked")
228 }
229
230 #[test]
231 fn test_try_borrow_while_borrow_mut() -> PyResult<()> {
232 with_setup(|_py, owner| {
233 let cell = &owner.borrow().string;
234 let shared_ref = unsafe { cell.borrow(owner) };
235 let _mut_ref = shared_ref.borrow_mut();
236
237 assert_try_borrow_string_err(owner);
238 Ok(())
239 })
240 }
241
242 #[test]
243 #[should_panic(expected = "already mutably borrowed")]
244 fn test_borrow_while_borrow_mut() {
245 with_setup(|_py, owner| {
246 let cell = &owner.borrow().string;
247 let shared_ref = unsafe { cell.borrow(owner) };
248 let _mut_ref = shared_ref.borrow_mut();
249
250 let shared_ref2 = unsafe { cell.borrow(owner) };
251 let _ref = shared_ref2.borrow();
252 Ok(())
253 })
254 .expect("should already have panicked")
255 }