Mercurial > public > mercurial-scm > hg
changeset 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 | be765f6797cc |
children | d85514a88706 |
files | rust/pyo3-sharedref/tests/test_sharedref.rs |
diffstat | 1 files changed, 255 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/pyo3-sharedref/tests/test_sharedref.rs Sat Dec 14 18:21:56 2024 +0100 @@ -0,0 +1,255 @@ +use pyo3::prelude::*; +use pyo3_sharedref::*; + +#[pyclass] +struct Owner { + string: PySharedRefCell<String>, +} + +#[pymethods] +impl Owner { + #[new] + fn new(s: String) -> Self { + Self { + string: PySharedRefCell::new(s), + } + } +} + +fn with_setup( + test: impl FnOnce(Python<'_>, &Bound<'_, Owner>) -> PyResult<()>, +) -> PyResult<()> { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let owner = Bound::new(py, Owner::new("new".to_owned()))?; + test(py, &owner) + }) +} + +/// "leak" in the sense of `UnsafePyLeaked` the `string` data field, +/// taking care of all the boilerplate +fn leak_string(owner: &Bound<'_, Owner>) -> UnsafePyLeaked<&'static String> { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + shared_ref.leak_immutable() +} + +fn try_leak_string( + owner: &Bound<'_, Owner>, +) -> Result<UnsafePyLeaked<&'static String>, TryLeakError> { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + shared_ref.try_leak_immutable() +} + +/// Mutate the `string` field of `owner` as would be done from Python code +/// +/// This is to simulate normal mutation of the owner object from +/// the Python interpreter. This could be replaced by methods of [`Owner`] +/// (wih closure replaced by a small fixed operations) +/// and perhaps will, once we are done converting the original tests +/// from rust-cpython +fn mutate_string<'py>( + owner: &'py Bound<'py, Owner>, + f: impl FnOnce(&mut String), +) -> () { + let cell = &owner.borrow_mut().string; + let shared_ref = unsafe { cell.borrow(owner) }; + f(&mut shared_ref.borrow_mut()); +} + +#[test] +fn test_leaked_borrow() -> PyResult<()> { + with_setup(|py, owner| { + let leaked = leak_string(owner); + let leaked_ref = unsafe { leaked.try_borrow(py) }.unwrap(); + assert_eq!(*leaked_ref, "new"); + Ok(()) + }) +} + +#[test] +fn test_leaked_borrow_mut() -> PyResult<()> { + with_setup(|py, owner| { + let leaked = leak_string(owner); + let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + let mut leaked_ref = + unsafe { leaked_iter.try_borrow_mut(py) }.unwrap(); + assert_eq!(leaked_ref.next(), Some('n')); + assert_eq!(leaked_ref.next(), Some('e')); + assert_eq!(leaked_ref.next(), Some('w')); + assert_eq!(leaked_ref.next(), None); + Ok(()) + }) +} + +#[test] +fn test_leaked_borrow_after_mut() -> PyResult<()> { + with_setup(|py, owner| { + let leaked = leak_string(owner); + mutate_string(owner, String::clear); + assert!(unsafe { leaked.try_borrow(py) }.is_err()); + Ok(()) + }) +} + +#[test] +fn test_leaked_borrow_mut_after_mut() -> PyResult<()> { + with_setup(|py, owner| { + let leaked = leak_string(owner); + let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + + mutate_string(owner, String::clear); + assert!(unsafe { leaked_iter.try_borrow_mut(py) }.is_err()); + Ok(()) + }) +} + +#[test] +#[should_panic(expected = "map() over invalidated leaked reference")] +fn test_leaked_map_after_mut() { + with_setup(|py, owner| { + let leaked = leak_string(owner); + mutate_string(owner, String::clear); + let _leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + Ok(()) + }) + .expect("should already have panicked") +} + +/// run `try_borrow_mut` on the `string` field and assert it is not an error +/// +/// Simply returning the `Result` is not possible, because that is +/// returning a reference to data owned by the function +fn assert_try_borrow_string_mut_ok(owner: &Bound<'_, Owner>) { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + assert!(shared_ref.try_borrow_mut().is_ok()); +} + +fn assert_try_borrow_string_mut_err(owner: &Bound<'_, Owner>) { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + assert!(shared_ref.try_borrow_mut().is_err()); +} + +fn assert_try_borrow_string_err(owner: &Bound<'_, Owner>) { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + assert!(shared_ref.try_borrow().is_err()); +} + +#[test] +fn test_try_borrow_mut_while_leaked_ref() -> PyResult<()> { + with_setup(|py, owner| { + assert_try_borrow_string_mut_ok(owner); + let leaked = leak_string(owner); + { + let _leaked_ref = unsafe { leaked.try_borrow(py) }.unwrap(); + assert_try_borrow_string_mut_err(owner); + { + let _leaked_ref2 = unsafe { leaked.try_borrow(py) }.unwrap(); + assert_try_borrow_string_mut_err(owner); + } + assert_try_borrow_string_mut_err(owner); + } + assert_try_borrow_string_mut_ok(owner); + Ok(()) + }) +} + +#[test] +fn test_try_borrow_mut_while_leaked_ref_mut() -> PyResult<()> { + with_setup(|py, owner| { + assert_try_borrow_string_mut_ok(owner); + let leaked = leak_string(owner); + let mut leaked_iter = unsafe { leaked.map(py, |s| s.chars()) }; + { + let _leaked_ref = + unsafe { leaked_iter.try_borrow_mut(py) }.unwrap(); + assert_try_borrow_string_mut_err(owner); + } + assert_try_borrow_string_mut_ok(owner); + Ok(()) + }) +} + +#[test] +fn test_try_leak_while_borrow_mut() -> PyResult<()> { + with_setup(|_py, owner| { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + let _mut_ref = shared_ref.borrow_mut(); + + assert!(try_leak_string(owner).is_err()); + Ok(()) + }) +} + +#[test] +#[should_panic(expected = "already mutably borrowed")] +fn test_leak_while_borrow_mut() { + with_setup(|_py, owner| { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + let _mut_ref = shared_ref.borrow_mut(); + + leak_string(owner); + Ok(()) + }) + .expect("should already have panicked") +} + +#[test] +fn test_try_borrow_mut_while_borrow() -> PyResult<()> { + with_setup(|_py, owner| { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + let _ref = shared_ref.borrow(); + + assert_try_borrow_string_mut_err(owner); + Ok(()) + }) +} + +#[test] +#[should_panic(expected = "already borrowed")] +fn test_borrow_mut_while_borrow() { + with_setup(|_py, owner| { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + let _ref = shared_ref.borrow(); + + let shared_ref2 = unsafe { cell.borrow(owner) }; + let _mut_ref = shared_ref2.borrow_mut(); + Ok(()) + }) + .expect("should already have panicked") +} + +#[test] +fn test_try_borrow_while_borrow_mut() -> PyResult<()> { + with_setup(|_py, owner| { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + let _mut_ref = shared_ref.borrow_mut(); + + assert_try_borrow_string_err(owner); + Ok(()) + }) +} + +#[test] +#[should_panic(expected = "already mutably borrowed")] +fn test_borrow_while_borrow_mut() { + with_setup(|_py, owner| { + let cell = &owner.borrow().string; + let shared_ref = unsafe { cell.borrow(owner) }; + let _mut_ref = shared_ref.borrow_mut(); + + let shared_ref2 = unsafe { cell.borrow(owner) }; + let _ref = shared_ref2.borrow(); + Ok(()) + }) + .expect("should already have panicked") +}