Increasing test coverage (#2462)

* cov: src/buffer.rs - add tests for debug and element from format

* cov: src/buffer.rs - add some fortran-specific calls in test_array_buffer

* fix issues in MSRV

* cov: src/types/function.rs - directly call PyCFunction::new and PyCFunction::new_with_keywords

* docs: clarify docs of PyCFunction::new and PyCFunction::new_with_keywords

* revert added rust-version for MSRV in Cargo.toml

* cov: src/types/slice.rs - simple tests for PySliceIndices::new

* fix for multi-platform

* Update src/types/function.rs

Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com>

* cov: src/buffer.rs - a better PyBuffer Debug test

Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
This commit is contained in:
Ashley Anderson 2022-06-23 16:30:22 -04:00 committed by GitHub
parent 3a3a080c7d
commit bde5102eb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 326 additions and 3 deletions

View File

@ -56,7 +56,7 @@ impl<T> Debug for PyBuffer<T> {
}
/// Represents the type of a Python buffer element.
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ElementType {
/// A signed integer type and its width in bytes.
SignedInteger { bytes: usize },
@ -685,6 +685,160 @@ mod tests {
use crate::ffi;
use crate::Python;
#[test]
fn test_debug() {
Python::with_gil(|py| {
let bytes = py.eval("b'abcde'", None, None).unwrap();
let buffer: PyBuffer<u8> = PyBuffer::get(bytes).unwrap();
let expected = format!(
concat!(
"PyBuffer {{ buf: {:?}, obj: {:?}, ",
"len: 5, itemsize: 1, readonly: 1, ",
"ndim: 1, format: {:?}, shape: {:?}, ",
"strides: {:?}, suboffsets: {:?}, internal: {:?} }}",
),
buffer.0.buf,
buffer.0.obj,
buffer.0.format,
buffer.0.shape,
buffer.0.strides,
buffer.0.suboffsets,
buffer.0.internal
);
let debug_repr = format!("{:?}", buffer);
assert_eq!(debug_repr, expected);
});
}
#[test]
fn test_element_type_from_format() {
use super::ElementType;
use super::ElementType::*;
use std::ffi::CStr;
use std::mem::size_of;
use std::os::raw;
for (cstr, expected) in &[
// @ prefix goes to native_element_type_from_type_char
(
"@b\0",
SignedInteger {
bytes: size_of::<raw::c_schar>(),
},
),
(
"@c\0",
UnsignedInteger {
bytes: size_of::<raw::c_char>(),
},
),
(
"@b\0",
SignedInteger {
bytes: size_of::<raw::c_schar>(),
},
),
(
"@B\0",
UnsignedInteger {
bytes: size_of::<raw::c_uchar>(),
},
),
("@?\0", Bool),
(
"@h\0",
SignedInteger {
bytes: size_of::<raw::c_short>(),
},
),
(
"@H\0",
UnsignedInteger {
bytes: size_of::<raw::c_ushort>(),
},
),
(
"@i\0",
SignedInteger {
bytes: size_of::<raw::c_int>(),
},
),
(
"@I\0",
UnsignedInteger {
bytes: size_of::<raw::c_uint>(),
},
),
(
"@l\0",
SignedInteger {
bytes: size_of::<raw::c_long>(),
},
),
(
"@L\0",
UnsignedInteger {
bytes: size_of::<raw::c_ulong>(),
},
),
(
"@q\0",
SignedInteger {
bytes: size_of::<raw::c_longlong>(),
},
),
(
"@Q\0",
UnsignedInteger {
bytes: size_of::<raw::c_ulonglong>(),
},
),
(
"@n\0",
SignedInteger {
bytes: size_of::<libc::ssize_t>(),
},
),
(
"@N\0",
UnsignedInteger {
bytes: size_of::<libc::size_t>(),
},
),
("@e\0", Float { bytes: 2 }),
("@f\0", Float { bytes: 4 }),
("@d\0", Float { bytes: 8 }),
("@z\0", Unknown),
// = prefix goes to standard_element_type_from_type_char
("=b\0", SignedInteger { bytes: 1 }),
("=c\0", UnsignedInteger { bytes: 1 }),
("=B\0", UnsignedInteger { bytes: 1 }),
("=?\0", Bool),
("=h\0", SignedInteger { bytes: 2 }),
("=H\0", UnsignedInteger { bytes: 2 }),
("=l\0", SignedInteger { bytes: 4 }),
("=l\0", SignedInteger { bytes: 4 }),
("=I\0", UnsignedInteger { bytes: 4 }),
("=L\0", UnsignedInteger { bytes: 4 }),
("=q\0", SignedInteger { bytes: 8 }),
("=Q\0", UnsignedInteger { bytes: 8 }),
("=e\0", Float { bytes: 2 }),
("=f\0", Float { bytes: 4 }),
("=d\0", Float { bytes: 8 }),
("=z\0", Unknown),
("=0\0", Unknown),
// unknown prefix -> Unknown
(":b\0", Unknown),
] {
assert_eq!(
ElementType::from_format(CStr::from_bytes_with_nul(cstr.as_bytes()).unwrap()),
*expected,
"element from format &Cstr: {:?}",
cstr,
);
}
}
#[test]
fn test_compatible_size() {
// for the cast in PyBuffer::shape()
@ -714,6 +868,8 @@ mod tests {
assert_eq!(unsafe { *(buffer.get_ptr(&[1]) as *mut u8) }, b'b');
assert!(buffer.as_mut_slice(py).is_none());
assert!(buffer.copy_to_slice(py, &mut [0u8]).is_err());
let mut arr = [0; 5];
buffer.copy_to_slice(py, &mut arr).unwrap();
@ -738,6 +894,11 @@ mod tests {
assert_eq!(buffer.format().to_str().unwrap(), "f");
assert_eq!(buffer.shape(), [4]);
// array creates a 1D contiguious buffer, so it's both C and F contiguous. This would
// be more interesting if we can come up with a 2D buffer but I think it would need a
// third-party lib or a custom class.
// C-contiguous fns
let slice = buffer.as_slice(py).unwrap();
assert_eq!(slice.len(), 4);
assert_eq!(slice[0].get(), 1.0);
@ -755,6 +916,25 @@ mod tests {
assert_eq!(slice[2].get(), 12.0);
assert_eq!(buffer.to_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
// F-contiguous fns
let buffer = PyBuffer::get(array).unwrap();
let slice = buffer.as_fortran_slice(py).unwrap();
assert_eq!(slice.len(), 4);
assert_eq!(slice[1].get(), 11.0);
let mut_slice = buffer.as_fortran_mut_slice(py).unwrap();
assert_eq!(mut_slice.len(), 4);
assert_eq!(mut_slice[2].get(), 12.0);
mut_slice[3].set(2.75);
assert_eq!(slice[3].get(), 2.75);
buffer
.copy_from_fortran_slice(py, &[10.0f32, 11.0, 12.0, 13.0])
.unwrap();
assert_eq!(slice[2].get(), 12.0);
assert_eq!(buffer.to_fortran_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
});
}
}

View File

@ -57,7 +57,7 @@ where
}
impl PyCFunction {
/// Create a new built-in function with keywords.
/// Create a new built-in function with keywords (*args and/or **kwargs).
pub fn new_with_keywords<'a>(
fun: ffi::PyCFunctionWithKeywords,
name: &'static str,
@ -74,7 +74,7 @@ impl PyCFunction {
)
}
/// Create a new built-in function without keywords.
/// Create a new built-in function which takes no arguments.
pub fn new<'a>(
fun: ffi::PyCFunction,
name: &'static str,

View File

@ -19,6 +19,7 @@ pyobject_native_type!(
);
/// Represents Python `slice` indices.
#[derive(Debug, Eq, PartialEq)]
pub struct PySliceIndices {
pub start: isize,
pub stop: isize,
@ -88,3 +89,63 @@ impl ToPyObject for PySliceIndices {
PySlice::new(py, self.start, self.stop, self.step).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_py_slice_indices_new() {
let start = 0;
let stop = 0;
let step = 0;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
let start = 0;
let stop = 100;
let step = 10;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
let start = 0;
let stop = -10;
let step = -1;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
let start = 0;
let stop = -10;
let step = 20;
assert_eq!(
PySliceIndices::new(start, stop, step),
PySliceIndices {
start,
stop,
step,
slicelength: 0
}
);
}
}

View File

@ -273,6 +273,88 @@ fn extract_traceback(py: Python<'_>, mut error: PyErr) -> String {
error_msg
}
#[test]
fn test_pycfunction_new() {
use pyo3::ffi;
let gil = Python::acquire_gil();
let py = gil.python();
unsafe extern "C" fn c_fn(
_self: *mut ffi::PyObject,
_args: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
ffi::PyLong_FromLong(4200)
}
let py_fn = PyCFunction::new(
c_fn,
"py_fn",
"py_fn for test (this is the docstring)",
py.into(),
)
.unwrap();
py_assert!(py, py_fn, "py_fn() == 4200");
py_assert!(
py,
py_fn,
"py_fn.__doc__ == 'py_fn for test (this is the docstring)'"
);
}
#[test]
fn test_pycfunction_new_with_keywords() {
use pyo3::ffi;
use std::ffi::CString;
use std::os::raw::{c_char, c_long};
use std::ptr;
let gil = Python::acquire_gil();
let py = gil.python();
unsafe extern "C" fn c_fn(
_self: *mut ffi::PyObject,
args: *mut ffi::PyObject,
kwds: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
let mut foo: c_long = 0;
let mut bar: c_long = 0;
let foo_ptr: *mut c_long = &mut foo;
let bar_ptr: *mut c_long = &mut bar;
let foo_name = CString::new("foo").unwrap();
let foo_name_raw: *mut c_char = foo_name.into_raw();
let kw_bar_name = CString::new("kw_bar").unwrap();
let kw_bar_name_raw: *mut c_char = kw_bar_name.into_raw();
let mut arglist = vec![foo_name_raw, kw_bar_name_raw, ptr::null_mut()];
let arglist_ptr: *mut *mut c_char = arglist.as_mut_ptr();
let arg_pattern: *const c_char = CString::new("l|l").unwrap().into_raw();
ffi::PyArg_ParseTupleAndKeywords(args, kwds, arg_pattern, arglist_ptr, foo_ptr, bar_ptr);
ffi::PyLong_FromLong(foo * bar)
}
let py_fn = PyCFunction::new_with_keywords(
c_fn,
"py_fn",
"py_fn for test (this is the docstring)",
py.into(),
)
.unwrap();
py_assert!(py, py_fn, "py_fn(42, kw_bar=100) == 4200");
py_assert!(py, py_fn, "py_fn(foo=42, kw_bar=100) == 4200");
py_assert!(
py,
py_fn,
"py_fn.__doc__ == 'py_fn for test (this is the docstring)'"
);
}
#[test]
fn test_closure() {
let gil = Python::acquire_gil();