Skip to content
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

Add tests for the generated documentation #53

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,7 @@ jobs:
override: true
- uses: taiki-e/install-action@v1
with:
tool: [email protected],cargo-make
# cargo-expand does not have stable output, so changing the version,
# while fine, will produce test case noise
tool: [email protected],cargo-make,[email protected]
- run: cargo make test
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ indexmap = { version = "2.0.0", optional = true }

[dev-dependencies]
thiserror = "1.0"
macrotest = "1.0.13"
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ impl<T> PyWrapperMut for T where T: PyWrapper + AsMut<Self::Inner> {}
/// }
///
/// create_init_submodule! {
/// /// Initialize this module and all its submodules
/// classes: [ PyCoolString ],
/// errors: [ IOError ],
/// funcs: [ do_nothing ],
Expand All @@ -212,12 +213,14 @@ impl<T> PyWrapperMut for T where T: PyWrapper + AsMut<Self::Inner> {}
#[macro_export]
macro_rules! create_init_submodule {
(
$(#[$meta:meta])*
$(classes: [ $($class: ty),+ ],)?
$(consts: [ $($const: ident),+ ],)?
$(errors: [ $($error: ty),+ ],)?
$(funcs: [ $($func: path),+ ],)?
$(submodules: [ $($mod_name: literal: $init_submod: path),+ ],)?
) => {
$(#[$meta])*
pub(crate) fn init_submodule(_name: &str, _py: $crate::pyo3::Python, m: &$crate::pyo3::types::PyModule) -> $crate::pyo3::PyResult<()> {
$($(
m.add_class::<$class>()?;
Expand Down
10 changes: 10 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ macro_rules! impl_compare {
($name: ident) => {
#[$crate::pyo3::pymethods]
impl $name {
/// Implements all the Python comparison operators in terms of the
/// Rust [`PartialOrd`](std::cmp::PartialOrd) instance.
#![allow(clippy::use_self)]
pub fn __richcmp__(&self, object: &Self, cmp: $crate::pyo3::basic::CompareOp) -> bool {
let result = ::std::cmp::PartialOrd::partial_cmp(self, object);
Expand Down Expand Up @@ -59,6 +61,8 @@ macro_rules! impl_hash {
($name: ident) => {
#[$crate::pyo3::pymethods]
impl $name {
/// Implements `__hash__` for Python in terms of the Rust
/// [`Hash`](std::hash::Hash) instance.
pub fn __hash__(&self) -> i64 {
let mut hasher = ::std::collections::hash_map::DefaultHasher::new();
::std::hash::Hash::hash($crate::PyWrapper::as_inner(self), &mut hasher);
Expand All @@ -73,6 +77,8 @@ macro_rules! impl_hash {
#[macro_export]
macro_rules! impl_repr {
($name: ident) => {
/// Implements `__repr__` for Python in terms of the Rust
/// [`Debug`](std::fmt::Debug) instance.
#[$crate::pyo3::pymethods]
impl $name {
pub fn __repr__(&self) -> String {
Expand All @@ -86,6 +92,8 @@ macro_rules! impl_repr {
#[macro_export]
macro_rules! impl_str {
($name: ident) => {
/// Implements `__str__` for Python in terms of the Rust
/// [`Display`](std::fmt::Display) instance.
#[$crate::pyo3::pymethods]
impl $name {
pub fn __str__(&self) -> String {
Expand Down Expand Up @@ -124,6 +132,8 @@ macro_rules! impl_parse {
($name: ident) => {
#[$crate::pyo3::pymethods]
impl $name {
/// Implements a static `parse` method for Python in terms of the
/// Rust [`FromStr`](std::str::FromStr) instance.
#[staticmethod]
pub fn parse(input: &str) -> $crate::pyo3::PyResult<Self> {
<Self as std::str::FromStr>::from_str(input)
Expand Down
63 changes: 62 additions & 1 deletion src/wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,13 @@ macro_rules! py_wrap_struct {
impl $name {
#![allow(clippy::use_self)]

#[doc = concat!(
r"Create a new [`",
stringify!($name),
r"`] from Python arguments; corresponds to `",
$($py_class, r".",)?
r"__new__()` in Python"
)]
#[new]
pub fn new(py: $crate::pyo3::Python, input: $crate::pyo3::Py<$crate::pyo3::PyAny>) -> $crate::pyo3::PyResult<Self> {
use $crate::pyo3::FromPyObject;
Expand Down Expand Up @@ -579,6 +586,15 @@ macro_rules! py_wrap_union_enum {
$crate::paste::paste! {
#[$crate::pyo3::pymethods]
impl $name {
#[doc = concat!(
r"The Python wrapper for [`",
stringify!($rs_enum),
r"::",
stringify!($variant),
r"`], creating a [`",
stringify!($name),
r"`] and taking a Python argument."
)]
#[staticmethod]
pub fn [< from_ $variant_name >](py: $crate::pyo3::Python, inner: $crate::private_ultimate_type!($($convert),+)) -> $crate::pyo3::PyResult<Self> {
let inner = &inner;
Expand All @@ -593,6 +609,10 @@ macro_rules! py_wrap_union_enum {
$crate::paste::paste! {
#[$crate::pyo3::pymethods]
impl $name {
#[doc = concat!(
r"Create a new [`", stringify!($name), r"`] wrapping a ",
r"[`", stringify!($rs_enum), r"::", stringify!($variant), "`]."
)]
#[staticmethod]
pub fn [< new_ $variant_name >]() -> Self {
Self::from($rs_enum::$variant)
Expand Down Expand Up @@ -632,6 +652,13 @@ macro_rules! py_wrap_union_enum {
$crate::paste::paste! {
#[$crate::pyo3::pymethods]
impl $name {
#[doc = concat!(
r"Create a new [`",
stringify!($name),
r"`] from a Python argument; corresponds to `",
$($py_class, r".",)?
r"__new__()` in Python"
)]
#[new]
pub fn new(py: $crate::pyo3::Python, input: &$crate::pyo3::PyAny) -> $crate::pyo3::PyResult<Self> {
$(
Expand All @@ -655,6 +682,15 @@ macro_rules! py_wrap_union_enum {
))
}

#[doc = concat!(
r"Directly return the Python version of the variant discriminant wrapped by this ",
r"value; ",
r"i.e., performs the match `",
stringify!($rs_inner),
r"::Variant(x) => x` for every variant constructor in [`",
stringify!($rs_inner),
r"`]"
)]
#[allow(unreachable_code, unreachable_pattern)]
pub fn inner(&self, py: $crate::pyo3::Python) -> $crate::pyo3::PyResult<$crate::pyo3::Py<$crate::pyo3::PyAny>> {
match &self.0 {
Expand All @@ -674,15 +710,31 @@ macro_rules! py_wrap_union_enum {
}

$(
#[doc = concat!(
r"Tests if this [`", stringify!($name), r"`] ",
r"wraps a [`", stringify!($rs_inner), r"::", stringify!($variant_name), "`] value"
)]
const fn [< is_ $variant_name >](&self) -> bool {
$crate::py_wrap_union_enum!(@is_variant self, $rs_inner, $variant $(($(=> $convert)+))?)
}

$(
#[doc = concat!(
r"Returns `x` if this [`", stringify!($name), r"`] ",
r"wraps a `", stringify!($rs_inner), r"::", stringify!($variant_name), "`(x); ",
r"otherwise returns (Python) `None`. On the Rust side, this corresponds to ",
r"either `Some(x)` or [`None`]."
)]
fn [< as_ $variant_name >](&self, py: $crate::pyo3::Python) -> Option<$crate::private_ultimate_type!($($convert),+)> {
self.[< to_ $variant_name >](py).ok()
}

#[doc = concat!(
r"Returns `x` if this [`", stringify!($name), r"`] ",
r"wraps a `", stringify!($rs_inner), r"::", stringify!($variant_name), "`(x); ",
r"otherwise raises a `ValueError`. On the Rust side, this corresponds to ",
r"either `Ok(x)` or `Err(...)`."
)]
fn [< to_ $variant_name >](&self, py: $crate::pyo3::Python) -> $crate::pyo3::PyResult<$crate::private_ultimate_type!($($convert),+)> {
if let $rs_inner::$variant(inner) = &self.0 {
$crate::private_intermediate_to_python!(py, &inner $(=> $convert)+)
Expand Down Expand Up @@ -721,7 +773,8 @@ macro_rules! py_wrap_union_enum {
/// ```
#[macro_export]
macro_rules! wrap_error {
($name: ident ($inner: ty)$(;)?) => {
($(#[$meta: meta])* $name: ident ($inner: ty)$(;)?) => {
$(#[$meta])*
#[derive(Debug)]
#[repr(transparent)]
pub struct $name($inner);
Expand Down Expand Up @@ -844,13 +897,21 @@ macro_rules! py_wrap_data_struct {
#[rigetti_pyo3::pyo3::pymethods]
impl $name {
$(
#[doc = concat!(
r"Get the ", stringify!($field_name), r" field from Python. ",
r"Annotated with `@property`."
)]
#[getter]
fn [< get_ $field_name >](&self, py: $crate::pyo3::Python<'_>) -> $crate::pyo3::PyResult<$crate::private_ultimate_type!($($convert),+)> {
use $crate::{PyWrapper, ToPython};
let inner = &self.as_inner().$field_name;
$crate::private_intermediate_to_python!(py, &inner $(=> $convert)+)
}

#[doc = concat!(
r"Set the ", stringify!($field_name), r" field from Python. ",
r"Annotated with `@", stringify!($field_name), r".setter`."
)]
#[setter]
fn [< set_ $field_name >](&mut self, py: $crate::pyo3::Python<'_>, from: $crate::private_ultimate_type!($($convert),+)) -> $crate::pyo3::PyResult<()> {
use $crate::{PyTryFrom, PyWrapperMut};
Expand Down
70 changes: 70 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use pyo3::{types::PyModule, PyResult, Python};

// The code being tested is in a separate module so it can be expanded (see
// [`test_macro_expansion`]) without expanding the contents of the tests
// themselves. You can also take advantage of this when manually using `cargo
// expand`, which may be useful when testing or debugging the macros defined in
// this crate. This file must be in a subdirectory (`wrapper_tests/mod.rs`
// instead of `wrapper_tests.rs`) because the generated `.expanded.rs` file
// cannot be in the root `tests/` directory or `cargo test` will attempt to
// build it as a test case as well.
mod wrapper_tests;

#[test]
fn test_enum_as_data_struct_member() {
wrapper_tests::append_to_inittab();
pyo3::prepare_freethreaded_python();
let result: PyResult<()> = Python::with_gil(|py| {
let code = r#"
from wrapper_tests import TestEnumUnaliased, TestEnumAliased, TestStruct, TestUnionEnum

struct = TestStruct()

assert struct.test_enum_unaliased == TestEnumUnaliased.One
assert struct.test_enum_aliased == TestEnumAliased.NONE

struct.test_enum_unaliased = TestEnumUnaliased.Two
struct.test_enum_aliased = TestEnumAliased.Two

assert struct.test_enum_unaliased == TestEnumUnaliased.Two
assert struct.test_enum_aliased == TestEnumAliased.Two

assert TestUnionEnum.new_unit().is_unit()
"#;
PyModule::from_code(py, code, "example.py", "example")?;

Ok(())
});

result.expect("python code should execute without issue")
}

#[test]
fn test_macro_expansion() {
// To regenerate the snapshot, run this test with the environment variable
// `MACROTEST=overwrite`, or alternatively delete the generated
// `tests/wrapper_tests/mod.expanded.rs` file and rerun this test.
macrotest::expand_args(
"tests/wrapper_tests/mod.rs",
// We have to specify a specific OS target because until PyO3 v0.22,
// PyO3 transitively depends on the
// [rust-ctor](https://crates.io/crates/ctor) crate, which generates
// different output on different OSes. Once we're doing *that*, we have
// to specify a specific Python ABI so that PyO3 doesn't get alarmed
// about cross-compilation. We pick the oldest availble option so that
// we can be flexible with which Python interpreter is available on the
// system. This is all a minor headache.
//
// In particular, this means that if you are running these tests on a
// different OS, you will need to install the specified target. The
// target is specifically chosen to be the one we use on CI, so CI does
// not need an extra `rustup target add`, but some developers will.
&[
"--target",
"x86_64-unknown-linux-gnu",
"--no-default-features",
"--features",
"pyo3/abi3-py37",
],
)
}
Loading
Loading