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:
parent
3a3a080c7d
commit
bde5102eb8
182
src/buffer.rs
182
src/buffer.rs
|
@ -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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue