-
Notifications
You must be signed in to change notification settings - Fork 958
Improve test coverage for src/call.rs #5951
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Added tests for `PyCallArgs` impls `()`, `&Bound<'py, PyTuple>`, `Py<PyTuple>`, `&Py<PyTuple>`, and `Borrowed<'_, 'py, PyTuple>`, and for `call_method_positional` default impl and error paths in `src/call.rs`. |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Fixed `check_call!` test macros in `src/call.rs` that ignored their `$args` parameter, causing all five `PyCallArgs` dispatch arms to test only the `Bound<'py, PyTuple>` path. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -245,11 +245,8 @@ mod tests { | |
|
|
||
| macro_rules! check_call { | ||
| ($args:expr, $kwargs:expr) => { | ||
| let (a, k): (Py<PyTuple>, Py<PyDict>) = f | ||
| .call(args.clone(), Some(kwargs)) | ||
| .unwrap() | ||
| .extract() | ||
| .unwrap(); | ||
| let (a, k): (Py<PyTuple>, Py<PyDict>) = | ||
| f.call($args, Some($kwargs)).unwrap().extract().unwrap(); | ||
| assert!(a.is(&args)); | ||
| assert!(k.is(kwargs)); | ||
| }; | ||
|
|
@@ -287,7 +284,7 @@ mod tests { | |
| macro_rules! check_call { | ||
| ($args:expr, $kwargs:expr) => { | ||
|
Comment on lines
284
to
285
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like |
||
| let (a, k): (Py<PyTuple>, Py<PyNone>) = | ||
| f.call1(args.clone()).unwrap().extract().unwrap(); | ||
| f.call1($args).unwrap().extract().unwrap(); | ||
| assert!(a.is(&args)); | ||
| assert!(k.is_none(py)); | ||
| }; | ||
|
|
@@ -309,4 +306,86 @@ mod tests { | |
| check_call!(args.as_borrowed(), kwargs); | ||
| }) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_call_unit_args() { | ||
| // Exercises ()::call and ()::call_positional — both routes through | ||
| // into_pyobject_or_pyerr to produce an empty PyTuple. | ||
| use crate::types::PyDict; | ||
| use crate::{ | ||
|
Comment on lines
+310
to
+315
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test possibly reveals something interesting - |
||
| types::{IntoPyDict, PyAnyMethods, PyTupleMethods}, | ||
| wrap_pyfunction, Py, Python, | ||
| }; | ||
|
|
||
| Python::attach(|py| { | ||
| let f = wrap_pyfunction!(args_kwargs, py).unwrap(); | ||
| let kwargs = &[("x", 1i32)].into_py_dict(py).unwrap(); | ||
|
|
||
| // ()::call — empty positional args dispatched via args.call(…) branch | ||
| let (a, _k): (Py<PyTuple>, Option<Py<PyDict>>) = | ||
| f.call((), Some(kwargs)).unwrap().extract().unwrap(); | ||
| assert_eq!(a.bind(py).len(), 0); | ||
|
|
||
| // ()::call_positional — empty positional args dispatched via call1 → args.call_positional(…) | ||
| let (a, _k): (Py<PyTuple>, Option<Py<PyDict>>) = | ||
| f.call1(()).unwrap().extract().unwrap(); | ||
| assert_eq!(a.bind(py).len(), 0); | ||
| }) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_call_method_positional_default() { | ||
| // Exercises the default call_method_positional impl on the PyCallArgs trait | ||
| // (lines 68–76 of call.rs): getattr + call1. Reached via call_method1. | ||
| use crate::{ | ||
| exceptions::PyAttributeError, | ||
| types::{PyAnyMethods, PyList, PyListMethods, PyTuple}, | ||
| Python, | ||
| }; | ||
|
|
||
| Python::attach(|py| { | ||
| let list = PyList::new(py, [1i32, 2, 3]).unwrap(); | ||
| let args = PyTuple::new(py, [4i32]).unwrap(); | ||
|
|
||
| // Happy path: getattr("append") succeeds, call1 appends the element. | ||
| list.call_method1("append", args).unwrap(); | ||
| assert_eq!(list.len(), 4); | ||
| // Verify the value is the integer 4, not a wrapped tuple. | ||
| assert_eq!(list.get_item(3).unwrap().extract::<i32>().unwrap(), 4i32); | ||
|
|
||
| // Error path: getattr fails → call_method_positional returns Err. | ||
| let err = list | ||
| .call_method1("nonexistent_method_xyz", PyTuple::empty(py)) | ||
| .unwrap_err(); | ||
| assert!(err.is_instance_of::<PyAttributeError>(py)); | ||
| }) | ||
| } | ||
|
Comment on lines
+336
to
+362
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This reads like AI slop test which is heavily coupled to the current implementation (e.g. referencing implementation function names, line numbers). If there is merit in having such a test, I would rather it be rewritten to cover the possible error pathways presented by the API, not from the implementation. |
||
|
|
||
| #[test] | ||
| fn test_call_error_paths() { | ||
| // Exercises the NULL-return error paths in Borrowed::call and | ||
| // Borrowed::call_positional when PyObject_Call fails. | ||
|
Comment on lines
+364
to
+367
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar comment as above RE ai slop. |
||
| use crate::{ | ||
| exceptions::PyTypeError, | ||
| types::{IntoPyDict, PyAnyMethods, PyDict}, | ||
| Python, | ||
| }; | ||
|
|
||
| Python::attach(|py| { | ||
| // A PyDict is not callable — invoking it raises TypeError. | ||
| let not_callable = PyDict::new(py).into_any(); | ||
| let args = PyTuple::empty(py); | ||
| let kwargs = &[("x", 1i32)].into_py_dict(py).unwrap(); | ||
|
|
||
| // Borrowed::call error path: kwargs Some → args.call(…) branch → | ||
| // PyObject_Call(function=dict, …) returns NULL. | ||
| let err = not_callable.call(args.clone(), Some(kwargs)).unwrap_err(); | ||
| assert!(err.is_instance_of::<PyTypeError>(py)); | ||
|
|
||
| // Borrowed::call_positional error path: kwargs None → args.call_positional(…) branch → | ||
| // PyObject_Call(…, kwargs=null_ptr) returns NULL. | ||
| let err = not_callable.call(args, None).unwrap_err(); | ||
| assert!(err.is_instance_of::<PyTypeError>(py)); | ||
| }) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not interesting to users if we add test coverage, can delete this file (I added label in CI).