Change unsendable test to use Rust thread

This commit is contained in:
David Hewitt 2020-08-07 13:31:17 +01:00
parent 3809e2b3e9
commit ad76a8a5ce
1 changed files with 61 additions and 43 deletions

View File

@ -1,5 +1,6 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::types::PyType;
use pyo3::{py_run, PyClass};
mod common;
@ -166,59 +167,76 @@ fn class_with_object_field() {
#[pyclass(unsendable)]
struct UnsendableBase {
rc: std::rc::Rc<usize>,
value: std::rc::Rc<usize>,
}
#[pymethods]
impl UnsendableBase {
#[new]
fn new(value: usize) -> UnsendableBase {
Self {
value: std::rc::Rc::new(value),
}
}
#[getter]
fn value(&self) -> usize {
*self.rc.as_ref()
*self.value
}
}
#[pyclass(extends=UnsendableBase)]
struct UnsendableChild {}
#[pymethods]
impl UnsendableChild {
#[new]
fn new(value: usize) -> (UnsendableChild, UnsendableBase) {
(UnsendableChild {}, UnsendableBase::new(value))
}
}
fn test_unsendable<T: PyClass + 'static>() -> PyResult<()> {
let obj = std::thread::spawn(|| -> PyResult<_> {
Python::with_gil(|py| {
let obj: Py<T> = PyType::new::<T>(py).call1((5,))?.extract()?;
// Accessing the value inside this thread should not panic
let caught_panic =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| -> PyResult<_> {
assert_eq!(obj.as_ref(py).getattr("value")?.extract::<usize>()?, 5);
Ok(())
}))
.is_err();
assert_eq!(caught_panic, false);
Ok(obj)
})
})
.join()
.unwrap()?;
// This access must panic
Python::with_gil(|py| {
obj.borrow(py);
});
panic!("Borrowing unsendable from receiving thread did not panic.");
}
/// If a class is marked as `unsendable`, it panics when accessed by another thread.
#[test]
fn panic_unsendable() {
if option_env!("RUSTFLAGS")
.map(|s| s.contains("-Cpanic=abort"))
.unwrap_or(false)
{
return;
}
let gil = Python::acquire_gil();
let py = gil.python();
let base = || UnsendableBase {
rc: std::rc::Rc::new(0),
};
let unsendable_base = PyCell::new(py, base()).unwrap();
let unsendable_child = PyCell::new(py, (UnsendableChild {}, base())).unwrap();
let source = pyo3::indoc::indoc!(
r#"
def value():
return unsendable.value()
import concurrent.futures
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
future = executor.submit(value)
try:
result = future.result()
assert False, 'future must panic'
except BaseException as e:
assert str(e) == 'test_class_basics::UnsendableBase is unsendable, but sent to another thread!'
"#
);
let globals = PyModule::import(py, "__main__").unwrap().dict();
let test = |unsendable| {
globals.set_item("unsendable", unsendable).unwrap();
py.run(source, Some(globals), None)
.map_err(|e| e.print(py))
.unwrap();
};
test(unsendable_base.as_ref());
test(unsendable_child.as_ref());
#[should_panic(
expected = "test_class_basics::UnsendableBase is unsendable, but sent to another thread!"
)]
fn panic_unsendable_base() {
test_unsendable::<UnsendableBase>().unwrap();
}
#[test]
#[should_panic(
expected = "test_class_basics::UnsendableBase is unsendable, but sent to another thread!"
)]
fn panic_unsendable_child() {
test_unsendable::<UnsendableChild>().unwrap();
}