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.
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")
}