Mercurial > public > mercurial-scm > hg
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 } |