Skip to content

Commit 83abceb

Browse files
author
Andrew J Westlake
committed
Applied some feedback for the inventory crate usage, removed test_structs! and test_main_body! macros
1 parent b4ddd55 commit 83abceb

File tree

4 files changed

+65
-181
lines changed

4 files changed

+65
-181
lines changed

Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ required-features = ["tokio-runtime", "testing"]
8282
[dependencies]
8383
clap = { version = "2.33", optional = true }
8484
futures = "0.3"
85+
inventory = "0.1"
8586
lazy_static = "1.4"
8687
once_cell = "1.5"
8788
pyo3 = "0.13"
@@ -95,7 +96,4 @@ optional = true
9596
[dependencies.tokio]
9697
version = "1.0"
9798
features = ["full"]
98-
optional = true
99-
100-
[dev-dependencies]
101-
inventory = "0.1"
99+
optional = true

pyo3-asyncio-macros/src/lib.rs

Lines changed: 4 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ pub fn async_std_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
170170
let result = quote! {
171171
#fn_impl
172172

173-
inventory::submit!(crate::Test {
173+
inventory::submit!(pyo3_asyncio::testing::Test {
174174
name: format!("{}::{}", std::module_path!(), stringify!(#name)),
175175
test_fn: &#name
176176
});
@@ -248,7 +248,7 @@ pub fn tokio_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
248248
let result = quote! {
249249
#fn_impl
250250

251-
inventory::submit!(crate::Test {
251+
inventory::submit!(pyo3_asyncio::testing::Test {
252252
name: format!("{}::{}", std::module_path!(), stringify!(#name)),
253253
test_fn: &#name
254254
});
@@ -299,99 +299,9 @@ impl Parse for TestMainArgs {
299299
}
300300
}
301301

302-
/// Provides the custom `Test` structure for the `pyo3-asyncio` `#[test]` attributes
303-
///
304-
/// This macro _must_ be expanded at the root of the test crate. Its main purpose is to manage the
305-
/// boilerplate for the `inventory` crate.
306-
///
307-
/// The following `test_main!()` macro:
308-
///
309-
/// ```ignore
310-
/// pyo3_asyncio::testing::test_main!(#[pyo3_asyncio::async_std::main], "Example Test Suite");
311-
/// ```
312-
///
313-
/// Is equivalent to this expansion:
314-
///
315-
/// ```ignore
316-
/// use pyo3::prelude::*;
317-
///
318-
/// pyo3_asyncio::testing::test_structs!();
319-
///
320-
/// #[pyo3_asyncio::async_std::main]
321-
/// async fn main() -> PyResult<()> {
322-
/// pyo3_asyncio::testing::test_main_body!("Example Test Suite");
323-
/// Ok(())
324-
/// }
325-
/// ```
326-
#[cfg(not(test))]
327-
#[proc_macro]
328-
pub fn test_structs(_args: TokenStream) -> TokenStream {
329-
let result = quote! {
330-
#[derive(Clone)]
331-
pub(crate) struct Test {
332-
pub name: String,
333-
pub test_fn: &'static (dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>> + Send + Sync),
334-
}
335-
336-
impl pyo3_asyncio::testing::Test for Test {
337-
fn name(&self) -> &str {
338-
self.name.as_str()
339-
}
340-
341-
fn task(&self) -> std::pin::Pin<Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>> {
342-
(self.test_fn)()
343-
}
344-
}
345-
346-
inventory::collect!(Test);
347-
};
348-
result.into()
349-
}
350-
351-
/// Expands the `pyo3-asyncio` test harness call within the `main` fn.
352-
///
353-
/// This macro collects the test structures from the `inventory` boilerplate and forwards them to
354-
/// the test harness.
355-
///
356-
/// The following `test_main!()` macro:
357-
///
358-
/// ```ignore
359-
/// pyo3_asyncio::testing::test_main!(#[pyo3_asyncio::async_std::main], "Example Test Suite");
360-
/// ```
361-
///
362-
/// Is equivalent to this expansion:
363-
///
364-
/// ```ignore
365-
/// use pyo3::prelude::*;
366-
///
367-
/// pyo3_asyncio::testing::test_structs!();
368-
///
369-
/// #[pyo3_asyncio::async_std::main]
370-
/// async fn main() -> PyResult<()> {
371-
/// pyo3_asyncio::testing::test_main_body!("Example Test Suite");
372-
/// Ok(())
373-
/// }
374-
/// ```
375-
#[cfg(not(test))]
376-
#[proc_macro]
377-
pub fn test_main_body(args: TokenStream) -> TokenStream {
378-
let suite_name = syn::parse_macro_input!(args as syn::LitStr);
379-
380-
let result = quote! {
381-
let args = pyo3_asyncio::testing::parse_args(#suite_name);
382-
383-
pyo3_asyncio::testing::test_harness(
384-
inventory::iter::<crate::Test>().map(|test| test.clone()).collect(), args
385-
)
386-
.await?;
387-
};
388-
result.into()
389-
}
390-
391302
/// The standard `pyo3-asyncio` test harness `main` fn boilerplate.
392303
///
393-
/// This macro combines the `test_structs!` and `test_main_body!` macros to provide the full
394-
/// boilerplate for the `pyo3-asyncio` test harness in one line.
304+
/// This macro provides the full boilerplate for the `pyo3-asyncio` test harness in one line.
395305
///
396306
/// # Examples
397307
///
@@ -412,12 +322,9 @@ pub fn test_main(args: TokenStream) -> TokenStream {
412322
let TestMainArgs { attrs, suite_name } = syn::parse_macro_input!(args as TestMainArgs);
413323

414324
let result = quote! {
415-
pyo3_asyncio::testing::test_structs!();
416-
417325
#(#attrs)*
418326
async fn main() -> pyo3::PyResult<()> {
419-
pyo3_asyncio::testing::test_main_body!(#suite_name);
420-
Ok(())
327+
pyo3_asyncio::testing::main(#suite_name).await
421328
}
422329
};
423330
result.into()

src/lib.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -400,13 +400,3 @@ fn dump_err(py: Python<'_>) -> impl FnOnce(PyErr) + '_ {
400400
e.print_and_set_sys_last_vars(py);
401401
}
402402
}
403-
404-
/// Alias crate as pyo3_asyncio for test_structs! macro expansion
405-
#[cfg(test)]
406-
#[cfg(all(feature = "testing", feature = "attributes"))]
407-
use crate as pyo3_asyncio;
408-
409-
// Expand test structs in crate root to allow lib tests to be compile-checked (but not ran)
410-
#[cfg(test)]
411-
#[cfg(all(feature = "testing", feature = "attributes"))]
412-
testing::test_structs!();

src/testing.rs

Lines changed: 59 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,9 @@
101101
//! # use pyo3::prelude::*;
102102
//! #
103103
//! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
104-
//! # pyo3_asyncio::testing::test_structs!();
105-
//! #
106-
//! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
107104
//! # #[pyo3_asyncio::async_std::main]
108105
//! # async fn main() -> pyo3::PyResult<()> {
109-
//! # pyo3_asyncio::testing::test_main_body!("Example Test Suite");
110-
//! #
111-
//! # Ok(())
106+
//! # pyo3_asyncio::testing::main("Example Test Suite").await
112107
//! # }
113108
//! # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))]
114109
//! # fn main() {}
@@ -143,52 +138,27 @@
143138
//! # use pyo3::prelude::*;
144139
//! #
145140
//! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))]
146-
//! # pyo3_asyncio::testing::test_structs!();
147-
//! #
148-
//! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))]
149141
//! # #[pyo3_asyncio::tokio::main]
150142
//! # async fn main() -> PyResult<()> {
151-
//! # pyo3_asyncio::testing::test_main_body!("Example Test Suite");
152-
//! #
153-
//! # Ok(())
143+
//! # pyo3_asyncio::testing::main("Example Test Suite").await
154144
//! # }
155145
//! # #[cfg(not(all(feature = "tokio-runtime", feature = "attributes")))]
156146
//! # fn main() {}
157147
//! ```
158148
//!
159-
//! ### Caveats
160-
//!
161-
//! The `test_main!()` macro _must_ be placed in the crate root. The `inventory` crate places
162-
//! restrictions on the structures used by the `#[test]` attributes that force us to create a custom
163-
//! `Test` structure in the crate root. If `test_main!()` is not expanded in the crate root, then
164-
//! the resolution of `crate::Test` will fail in the proc macro expansion and the test will not
165-
//! compile.
166-
//!
167-
//! #### Lib Tests
149+
//! ### Lib Tests
168150
//!
169151
//! Unfortunately, as we mentioned at the beginning, these utilities will only run in integration
170152
//! tests and doc tests. Running lib tests are out of the question since we need control over the
171153
//! main function. You can however perform compilation checks for lib tests. This is unfortunately
172154
//! much more useful in doc tests than it is for lib tests, but the option is there if you want it.
173155
//!
174-
//! #### Allowing Compilation Checks in Lib Tests
175-
//!
176-
//! In order to allow the `#[test]` attributes to expand, we need to expand the `test_structs!()`
177-
//! macro in the crate root. After that, `pyo3-asyncio` tests can be defined anywhere. Again, these
178-
//! will not run, but they will be compile-checked during testing.
179-
//!
180156
//! `my-crate/src/lib.rs`
181157
//! ```
182158
//! # #[cfg(all(
183159
//! # any(feature = "async-std-runtime", feature = "tokio-runtime"),
184160
//! # feature = "attributes"
185161
//! # ))]
186-
//! pyo3_asyncio::testing::test_structs!();
187-
//!
188-
//! # #[cfg(all(
189-
//! # any(feature = "async-std-runtime", feature = "tokio-runtime"),
190-
//! # feature = "attributes"
191-
//! # ))]
192162
//! mod tests {
193163
//! use pyo3::prelude::*;
194164
//!
@@ -218,21 +188,16 @@
218188
//! # fn main() {}
219189
//! ```
220190
//!
221-
//! #### Expanding `test_main!()` for Doc Tests
191+
//! ### Expanding `test_main!()` for Doc Tests
222192
//!
223193
//! This is probably a pretty niche topic, and there's really no reason you would _need_ to do this
224194
//! since usually you'd probably just want to use the `#[main]` attributes for your doc tests like we
225195
//! mentioned at the beginning of this page. But since we had to do it for _this particular module_
226196
//! of the docs, it's probably worth mentioning as a footnote.
227197
//!
228198
//! For some reason, doc tests don't interpret the `test_main!()` macro as providing `fn main()` and
229-
//! will wrap the test body in another `fn main()`. For technical reasons listed in the
230-
//! [Caveats](#caveats) section above, this is problematic because the `Test` structure that is
231-
//! expanded by the `test_main!` macro will not be inside the crate root anymore.
232-
//!
233-
//! To get around this, we can instead expand `test_main!()` into its components for the doc test:
234-
//! * [`test_structs!()`](crate::testing::test_structs)
235-
//! * [`test_main_body!(suite_name: &'static str)`](crate::testing::test_main_body)
199+
//! will wrap the test body in another `fn main()`. To get around this, we can instead expand
200+
//! `test_main!()` into its components for the doc test:
236201
//!
237202
//! The following `test_main!()` macro:
238203
//!
@@ -247,20 +212,14 @@
247212
//! use pyo3::prelude::*;
248213
//!
249214
//! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
250-
//! pyo3_asyncio::testing::test_structs!();
251-
//!
252-
//! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
253215
//! #[pyo3_asyncio::async_std::main]
254216
//! async fn main() -> PyResult<()> {
255-
//! pyo3_asyncio::testing::test_main_body!("Example Test Suite");
256-
//! Ok(())
217+
//! pyo3_asyncio::testing::main("Example Test Suite").await
257218
//! }
258219
//! # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))]
259220
//! # fn main() {}
260221
//! ```
261222
262-
use std::{future::Future, pin::Pin};
263-
264223
use clap::{App, Arg};
265224
use futures::stream::{self, StreamExt};
266225
use pyo3::prelude::*;
@@ -270,16 +229,6 @@ use pyo3::prelude::*;
270229
#[cfg(feature = "attributes")]
271230
pub use pyo3_asyncio_macros::test_main;
272231

273-
/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>attributes</code></span>
274-
/// Provides the `Test` structure and the `inventory` boilerplate for the `pyo3-asyncio` test harness
275-
#[cfg(feature = "attributes")]
276-
pub use pyo3_asyncio_macros::test_structs;
277-
278-
/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>attributes</code></span>
279-
/// Expands the `pyo3-asyncio` test harness call within the `main` fn
280-
#[cfg(feature = "attributes")]
281-
pub use pyo3_asyncio_macros::test_main_body;
282-
283232
/// Args that should be provided to the test program
284233
///
285234
/// These args are meant to mirror the default test harness's args.
@@ -339,25 +288,39 @@ pub fn parse_args(suite_name: &str) -> Args {
339288
}
340289
}
341290

342-
/// Abstract Test Trait
343-
///
344-
/// This trait works in tandem with the pyo3-asyncio-macros to generate test objects that work with
345-
/// the pyo3-asyncio test harness.
346-
pub trait Test: Send {
347-
/// Get the name of the test
348-
fn name(&self) -> &str;
349-
/// Instantiate the task that runs the test
350-
fn task(&self) -> Pin<Box<dyn Future<Output = PyResult<()>> + Send>>;
291+
type TestFn = dyn Fn() -> std::pin::Pin<
292+
Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>,
293+
> + Send
294+
+ Sync;
295+
296+
/// The structure used by the `#[test]` macros to provide a test to the `pyo3-asyncio` test harness.
297+
#[derive(Clone)]
298+
pub struct Test {
299+
/// The fully qualified name of the test
300+
pub name: String,
301+
/// The function used to create the task that runs the test.
302+
pub test_fn: &'static TestFn,
351303
}
352304

305+
impl Test {
306+
/// Create the task that runs the test
307+
pub fn task(
308+
&self,
309+
) -> std::pin::Pin<Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>> {
310+
(self.test_fn)()
311+
}
312+
}
313+
314+
inventory::collect!(Test);
315+
353316
/// Run a sequence of tests while applying any necessary filtering from the `Args`
354-
pub async fn test_harness(tests: Vec<impl Test + 'static>, args: Args) -> PyResult<()> {
317+
pub async fn test_harness(tests: Vec<Test>, args: Args) -> PyResult<()> {
355318
stream::iter(tests)
356319
.for_each_concurrent(Some(4), |test| {
357320
let mut ignore = false;
358321

359322
if let Some(filter) = args.filter.as_ref() {
360-
if !test.name().contains(filter) {
323+
if !test.name.contains(filter) {
361324
ignore = true;
362325
}
363326
}
@@ -366,7 +329,7 @@ pub async fn test_harness(tests: Vec<impl Test + 'static>, args: Args) -> PyResu
366329
if !ignore {
367330
test.task().await.unwrap();
368331

369-
println!("test {} ... ok", test.name());
332+
println!("test {} ... ok", test.name);
370333
}
371334
}
372335
})
@@ -375,6 +338,32 @@ pub async fn test_harness(tests: Vec<impl Test + 'static>, args: Args) -> PyResu
375338
Ok(())
376339
}
377340

341+
/// Parses test arguments and passes the tests to the `pyo3-asyncio` test harness
342+
///
343+
/// This function collects the test structures from the `inventory` boilerplate and forwards them to
344+
/// the test harness.
345+
///
346+
/// # Examples
347+
///
348+
/// ```
349+
/// use pyo3::prelude::*;
350+
///
351+
/// # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
352+
/// #[pyo3_asyncio::async_std::main]
353+
/// async fn main() -> PyResult<()> {
354+
/// pyo3_asyncio::testing::main("Example Test Suite").await
355+
/// }
356+
/// ```
357+
pub async fn main(suite_name: &str) -> PyResult<()> {
358+
let args = parse_args(suite_name);
359+
360+
test_harness(
361+
inventory::iter::<Test>().map(|test| test.clone()).collect(),
362+
args,
363+
)
364+
.await
365+
}
366+
378367
#[cfg(test)]
379368
#[cfg(all(
380369
feature = "testing",

0 commit comments

Comments
 (0)