101
101
//! # use pyo3::prelude::*;
102
102
//! #
103
103
//! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
104
- //! # pyo3_asyncio::testing::test_structs!();
105
- //! #
106
- //! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
107
104
//! # #[pyo3_asyncio::async_std::main]
108
105
//! # 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
112
107
//! # }
113
108
//! # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))]
114
109
//! # fn main() {}
143
138
//! # use pyo3::prelude::*;
144
139
//! #
145
140
//! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))]
146
- //! # pyo3_asyncio::testing::test_structs!();
147
- //! #
148
- //! # #[cfg(all(feature = "tokio-runtime", feature = "attributes"))]
149
141
//! # #[pyo3_asyncio::tokio::main]
150
142
//! # 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
154
144
//! # }
155
145
//! # #[cfg(not(all(feature = "tokio-runtime", feature = "attributes")))]
156
146
//! # fn main() {}
157
147
//! ```
158
148
//!
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
168
150
//!
169
151
//! Unfortunately, as we mentioned at the beginning, these utilities will only run in integration
170
152
//! tests and doc tests. Running lib tests are out of the question since we need control over the
171
153
//! main function. You can however perform compilation checks for lib tests. This is unfortunately
172
154
//! much more useful in doc tests than it is for lib tests, but the option is there if you want it.
173
155
//!
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
- //!
180
156
//! `my-crate/src/lib.rs`
181
157
//! ```
182
158
//! # #[cfg(all(
183
159
//! # any(feature = "async-std-runtime", feature = "tokio-runtime"),
184
160
//! # feature = "attributes"
185
161
//! # ))]
186
- //! pyo3_asyncio::testing::test_structs!();
187
- //!
188
- //! # #[cfg(all(
189
- //! # any(feature = "async-std-runtime", feature = "tokio-runtime"),
190
- //! # feature = "attributes"
191
- //! # ))]
192
162
//! mod tests {
193
163
//! use pyo3::prelude::*;
194
164
//!
218
188
//! # fn main() {}
219
189
//! ```
220
190
//!
221
- //! #### Expanding `test_main!()` for Doc Tests
191
+ //! ### Expanding `test_main!()` for Doc Tests
222
192
//!
223
193
//! This is probably a pretty niche topic, and there's really no reason you would _need_ to do this
224
194
//! since usually you'd probably just want to use the `#[main]` attributes for your doc tests like we
225
195
//! mentioned at the beginning of this page. But since we had to do it for _this particular module_
226
196
//! of the docs, it's probably worth mentioning as a footnote.
227
197
//!
228
198
//! 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:
236
201
//!
237
202
//! The following `test_main!()` macro:
238
203
//!
247
212
//! use pyo3::prelude::*;
248
213
//!
249
214
//! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
250
- //! pyo3_asyncio::testing::test_structs!();
251
- //!
252
- //! # #[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
253
215
//! #[pyo3_asyncio::async_std::main]
254
216
//! 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
257
218
//! }
258
219
//! # #[cfg(not(all(feature = "async-std-runtime", feature = "attributes")))]
259
220
//! # fn main() {}
260
221
//! ```
261
222
262
- use std:: { future:: Future , pin:: Pin } ;
263
-
264
223
use clap:: { App , Arg } ;
265
224
use futures:: stream:: { self , StreamExt } ;
266
225
use pyo3:: prelude:: * ;
@@ -270,16 +229,6 @@ use pyo3::prelude::*;
270
229
#[ cfg( feature = "attributes" ) ]
271
230
pub use pyo3_asyncio_macros:: test_main;
272
231
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
-
283
232
/// Args that should be provided to the test program
284
233
///
285
234
/// These args are meant to mirror the default test harness's args.
@@ -339,25 +288,39 @@ pub fn parse_args(suite_name: &str) -> Args {
339
288
}
340
289
}
341
290
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 ,
351
303
}
352
304
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
+
353
316
/// 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 < ( ) > {
355
318
stream:: iter ( tests)
356
319
. for_each_concurrent ( Some ( 4 ) , |test| {
357
320
let mut ignore = false ;
358
321
359
322
if let Some ( filter) = args. filter . as_ref ( ) {
360
- if !test. name ( ) . contains ( filter) {
323
+ if !test. name . contains ( filter) {
361
324
ignore = true ;
362
325
}
363
326
}
@@ -366,7 +329,7 @@ pub async fn test_harness(tests: Vec<impl Test + 'static>, args: Args) -> PyResu
366
329
if !ignore {
367
330
test. task ( ) . await . unwrap ( ) ;
368
331
369
- println ! ( "test {} ... ok" , test. name( ) ) ;
332
+ println ! ( "test {} ... ok" , test. name) ;
370
333
}
371
334
}
372
335
} )
@@ -375,6 +338,32 @@ pub async fn test_harness(tests: Vec<impl Test + 'static>, args: Args) -> PyResu
375
338
Ok ( ( ) )
376
339
}
377
340
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
+
378
367
#[ cfg( test) ]
379
368
#[ cfg( all(
380
369
feature = "testing" ,
0 commit comments