diff --git a/src/lib.rs b/src/lib.rs index 9b7bce50..1c14aa19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -289,10 +289,10 @@ macro_rules! wrap_pymodule { /// # Example /// ``` /// use pyo3::{prelude::*, py_run, types::PyList}; -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let list = PyList::new(py, &[1, 2, 3]); -/// py_run!(py, list, "assert list == [1, 2, 3]"); +/// Python::with_gil(|py| { +/// let list = PyList::new(py, &[1, 2, 3]); +/// py_run!(py, list, "assert list == [1, 2, 3]"); +/// }); /// ``` /// /// You can use this macro to test pyfunctions or pyclasses quickly. @@ -320,15 +320,32 @@ macro_rules! wrap_pymodule { /// (self.hour, self.minute, self.second) /// } /// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let time = PyCell::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap(); -/// let time_as_tuple = (8, 43, 16); -/// py_run!(py, time time_as_tuple, r#" -/// assert time.hour == 8 -/// assert time.repl_japanese() == "8時43分16秒" -/// assert time.as_tuple() == time_as_tuple -/// "#); +/// Python::with_gil(|py| { +/// let time = PyCell::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap(); +/// let time_as_tuple = (8, 43, 16); +/// py_run!(py, time time_as_tuple, r#" +/// assert time.hour == 8 +/// assert time.repl_japanese() == "8時43分16秒" +/// assert time.as_tuple() == time_as_tuple +/// "#); +/// }); +/// ``` +/// +/// If you need to prepare the `locals` dict by yourself, you can pass it by `*locals`. +/// +/// ``` +/// # use pyo3::prelude::*; +/// #[pyclass] +/// struct MyClass {} +/// #[pymethods] +/// impl MyClass { +/// #[new] +/// fn new() -> Self { MyClass {} } +/// } +/// Python::with_gil(|py| { +/// let locals = [("C", py.get_type::())]; +/// pyo3::py_run!(py, *locals, "c = C()"); +/// }); /// ``` /// /// **Note** @@ -345,6 +362,12 @@ macro_rules! py_run { ($py:expr, $($val:ident)+, $code:expr) => {{ $crate::py_run_impl!($py, $($val)+, &$crate::unindent::unindent($code)) }}; + ($py:expr, *$dict:expr, $code:literal) => {{ + $crate::py_run_impl!($py, *$dict, $crate::indoc::indoc!($code)) + }}; + ($py:expr, *$dict:expr, $code:expr) => {{ + $crate::py_run_impl!($py, *$dict, &$crate::unindent::unindent($code)) + }}; } #[macro_export] @@ -355,8 +378,10 @@ macro_rules! py_run_impl { use $crate::types::IntoPyDict; use $crate::ToPyObject; let d = [$((stringify!($val), $val.to_object($py)),)+].into_py_dict($py); - - if let Err(e) = $py.run($code, None, Some(d)) { + $crate::py_run_impl!($py, *d, $code) + }}; + ($py:expr, *$dict:expr, $code:expr) => {{ + if let Err(e) = $py.run($code, None, Some($dict)) { e.print($py); // So when this c api function the last line called printed the error to stderr, // the output is only written into a buffer which is never flushed because we diff --git a/tests/common.rs b/tests/common.rs index c110f5d4..75a246a9 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,37 +1,43 @@ -//! Useful tips for writing tests: -//! - Tests are run in parallel; There's still a race condition in test_owned with some other test -//! - You need to use flush=True to get any output from print +//! Some common macros for tests #[macro_export] macro_rules! py_assert { - ($py:expr, $val:ident, $assertion:expr) => { - pyo3::py_run!($py, $val, concat!("assert ", $assertion)) + ($py:expr, $($val:ident)+, $assertion:literal) => { + pyo3::py_run!($py, $($val)+, concat!("assert ", $assertion)) + }; + ($py:expr, *$dict:expr, $assertion:literal) => { + pyo3::py_run!($py, *$dict, concat!("assert ", $assertion)) }; } #[macro_export] macro_rules! py_expect_exception { - ($py:expr, $val:ident, $code:expr, $err:ident) => {{ + // Case1: idents & no err_msg + ($py:expr, $($val:ident)+, $code:expr, $err:ident) => {{ use pyo3::types::IntoPyDict; - let d = [(stringify!($val), &$val)].into_py_dict($py); - - let res = $py.run($code, None, Some(d)); + let d = [$((stringify!($val), $val.to_object($py)),)+].into_py_dict($py); + py_expect_exception!($py, *d, $code, $err) + }}; + // Case2: dict & no err_msg + ($py:expr, *$dict:expr, $code:expr, $err:ident) => {{ + let res = $py.run($code, None, Some($dict)); let err = res.expect_err(&format!("Did not raise {}", stringify!($err))); if !err.matches($py, $py.get_type::()) { panic!("Expected {} but got {:?}", stringify!($err), err) } err }}; - ($py:expr, $val:ident, $code:expr, $err:ident, $err_msg:expr) => {{ - let err = py_expect_exception!($py, $val, $code, $err); - assert_eq!( - err.instance($py) - .str() - .expect("error str() failed") - .to_str() - .expect("message was not valid utf8"), - $err_msg - ); + // Case3: idents & err_msg + ($py:expr, $($val:ident)+, $code:expr, $err:ident, $err_msg:literal) => {{ + let err = py_expect_exception!($py, $($val)+, $code, $err); + // Suppose that the error message looks like 'TypeError: ~' + assert_eq!(format!("Py{}", err), concat!(stringify!($err), ": ", $err_msg)); + err + }}; + // Case4: dict & err_msg + ($py:expr, *$dict:expr, $code:expr, $err:ident, $err_msg:literal) => {{ + let err = py_expect_exception!($py, *$dict, $code, $err); + assert_eq!(format!("Py{}", err), concat!(stringify!($err), ": ", $err_msg)); err }}; }