use cpython::{py_fn, NoArgs, ObjectProtocol, PyDict, PyNone, PyResult, Python}; use std::sync::atomic; use std::sync::atomic::Ordering::Relaxed; #[test] fn no_args() { static CALL_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0); fn f(_py: Python) -> PyResult { Ok(CALL_COUNT.fetch_add(1, Relaxed)) } let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(py, f()); assert_eq!(CALL_COUNT.load(Relaxed), 0); assert_eq!( obj.call(py, NoArgs, None) .unwrap() .extract::(py) .unwrap(), 0 ); assert_eq!( obj.call(py, NoArgs, None) .unwrap() .extract::(py) .unwrap(), 1 ); assert_eq!(CALL_COUNT.load(Relaxed), 2); assert!(obj.call(py, (1,), None).is_err()); assert_eq!(CALL_COUNT.load(Relaxed), 2); assert_eq!( obj.call(py, NoArgs, Some(&PyDict::new(py))) .unwrap() .extract::(py) .unwrap(), 2 ); assert_eq!(CALL_COUNT.load(Relaxed), 3); let dict = PyDict::new(py); dict.set_item(py, "param", 42).unwrap(); assert!(obj.call(py, NoArgs, Some(&dict)).is_err()); assert_eq!(CALL_COUNT.load(Relaxed), 3); } #[test] fn one_arg() { fn f(_py: Python, i: usize) -> PyResult { Ok(i * 2) } let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(py, f(i: usize)); assert!(obj.call(py, NoArgs, None).is_err()); assert_eq!( obj.call(py, (1,), None) .unwrap() .extract::(py) .unwrap(), 2 ); assert!(obj.call(py, (1, 2), None).is_err()); let dict = PyDict::new(py); dict.set_item(py, "i", 42).unwrap(); assert_eq!( obj.call(py, NoArgs, Some(&dict)) .unwrap() .extract::(py) .unwrap(), 84 ); assert!(obj.call(py, (1,), Some(&dict)).is_err()); dict.set_item(py, "j", 10).unwrap(); assert!(obj.call(py, NoArgs, Some(&dict)).is_err()); } #[test] fn trailing_comma() { fn f(_py: Python, i: usize, j: usize) -> PyResult { Ok(i + j) } let gil = Python::acquire_gil(); let py = gil.python(); // Define a function where the parameters are on separate // lines with trailing commas. let obj = py_fn!( py, f( first_parameter_with_long_name: usize, second_parameter_with_long_name: usize, ) ); assert_eq!( obj.call(py, (1, 1), None) .unwrap() .extract::(py) .unwrap(), 2 ); } #[test] fn inline_two_args() { let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(py, f(a: i32, b: i32) -> PyResult { drop(py); // avoid unused variable warning Ok(a * b) }); assert!(obj.call(py, NoArgs, None).is_err()); assert_eq!( obj.call(py, (6, 7), None) .unwrap() .extract::(py) .unwrap(), 42 ); } #[test] fn opt_args() { let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(py, f(a: Option<&str>, b: &str, c: Option<&str> = None) -> PyResult { drop(py); Ok(format!("a: {:?} b: {:?} c: {:?}", a, b, c)) }); assert_eq!( obj.call(py, (PyNone, "string"), None) .unwrap() .extract::(py) .unwrap(), r#"a: None b: "string" c: None"#, ); assert_eq!( obj.call(py, ("double", "string", PyNone), None) .unwrap() .extract::(py) .unwrap(), r#"a: Some("double") b: "string" c: None"#, ); assert_eq!( obj.call(py, ("triple", "string", "args"), None) .unwrap() .extract::(py) .unwrap(), r#"a: Some("triple") b: "string" c: Some("args")"#, ); } #[test] fn none_return() { static CALL_COUNT: atomic::AtomicUsize = atomic::AtomicUsize::new(0); fn f(_py: Python) -> PyResult { CALL_COUNT.fetch_add(1, Relaxed); Ok(PyNone) } let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(py, f()); assert_eq!(CALL_COUNT.load(Relaxed), 0); assert_eq!( obj.call(py, NoArgs, None) .unwrap() .extract::(py) .unwrap(), PyNone, ); assert_eq!(CALL_COUNT.load(Relaxed), 1); } /// When Python calls a Rust function, an unhandled Rust panic is turned into /// a Python `SystemError` exception. The exception’s value is a string that /// contains the panic’s payload, if that payload was a string. #[test] fn panicking() { fn f(_py: Python) -> PyResult { panic!("panicking because {}", "reasons") } let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(py, f()); assert_eq!( obj.call(py, NoArgs, None) .unwrap_err() // Expect an exception .instance(py) .str(py) .unwrap() .to_string_lossy(py), "Rust panic: panicking because reasons" ); } /* TODO: reimplement flexible sig support #[test] fn flexible_sig() { fn f(py: Python, args: &PyTuple, kwargs: &PyDict) -> PyResult { Ok(args.len(py) + 100 * kwargs.map_or(0, |kwargs| kwargs.len(py))) } let gil = Python::acquire_gil(); let py = gil.python(); let obj = py_fn!(f(*args, **kwargs)).to_py_object(py); assert_eq!(obj.call(py, NoArgs, None).unwrap().extract::(py).unwrap(), 0); assert_eq!(obj.call(py, (1,), None).unwrap().extract::(py).unwrap(), 1); assert_eq!(obj.call(py, (1,2), None).unwrap().extract::(py).unwrap(), 2); let dict = PyDict::new(py); dict.set_item(py, "i", 42).unwrap(); assert_eq!(obj.call(py, NoArgs, Some(&dict)).unwrap().extract::(py).unwrap(), 100); assert_eq!(obj.call(py, (1,2), Some(&dict)).unwrap().extract::(py).unwrap(), 102); dict.set_item(py, "j", 10).unwrap(); assert_eq!(obj.call(py, (1,2,3), Some(&dict)).unwrap().extract::(py).unwrap(), 203); } */