1
- //! # PyO3 Asyncio Testing Utilities
2
- //!
3
- //! This module provides some utilities for parsing test arguments as well as running and filtering
4
- //! a sequence of tests.
5
- //!
6
- //! As mentioned [here](crate#pythons-event-loop), PyO3 Asyncio tests cannot use the default test
7
- //! harness since it doesn't allow Python to gain control over the main thread. Instead, we have to
8
- //! provide our own test harness in order to create integration tests.
9
- //!
10
- //! ## Creating A PyO3 Asyncio Integration Test
11
- //!
12
- //! ### Main Test File
13
- //! First, we need to create the test's main file. Although these tests are considered integration
14
- //! tests, we cannot put them in the `tests` directory since that is a special directory owned by
15
- //! Cargo. Instead, we put our tests in a `pytests` directory, although the name `pytests` is just
16
- //! a convention.
17
- //!
18
- //! `pytests/test_example.rs`
19
- //! ```no_run
20
- //! fn main() {
21
- //!
22
- //! }
23
- //! ```
24
- //!
25
- //! ### Test Manifest Entry
26
- //! Next, we need to add our test file to the Cargo manifest. Add the following section to your
27
- //! `Cargo.toml`
28
- //!
29
- //! ```toml
30
- //! [[test]]
31
- //! name = "test_example"
32
- //! path = "pytests/test_example.rs"
33
- //! harness = false
34
- //! ```
35
- //!
36
- //! At this point you should be able to run the test via `cargo test`
37
- //!
38
- //! ### Using the PyO3 Asyncio Test Harness
39
- //! Now that we've got our test registered with `cargo test`, we can start using the PyO3 Asyncio
40
- //! test harness.
41
- //!
42
- //! In your `Cargo.toml` add the testing feature to `pyo3-asyncio`:
43
- //! ```toml
44
- //! pyo3-asyncio = { version = "0.13", features = ["testing", "async-std-runtime"] }
45
- //! ```
46
- //!
47
- //! Now, in your test's main file, call [`async_std::testing::test_main`]:
48
- //!
49
- //! ```no_run
50
- //! fn main() {
51
- //! pyo3_asyncio::async_std::testing::test_main("Example Test Suite", vec![]);
52
- //! }
53
- //! ```
54
- //!
55
- //! ### Adding Tests to the PyO3 Asyncio Test Harness
56
- //!
57
- //! ```no_run
58
- //! use std::{time::Duration, thread};
59
- //!
60
- //! use pyo3_asyncio::testing::Test;
61
- //!
62
- //! fn main() {
63
- //! pyo3_asyncio::async_std::testing::test_main(
64
- //! "Example Test Suite",
65
- //! vec![
66
- //! Test::new_async(
67
- //! "test_async_sleep".into(),
68
- //! async move {
69
- //! async_std::task::sleep(Duration::from_secs(1)).await;
70
- //! Ok(())
71
- //! }
72
- //! ),
73
- //! pyo3_asyncio::async_std::testing::new_sync_test(
74
- //! "test_sync_sleep".into(),
75
- //! || {
76
- //! thread::sleep(Duration::from_secs(1));
77
- //! Ok(())
78
- //! }
79
- //! )
80
- //! ]
81
- //! );
82
- //! }
83
- //! ```
84
-
85
1
use std:: future:: Future ;
86
2
87
3
use async_std:: task;
88
4
use futures:: channel:: oneshot;
89
5
use pyo3:: prelude:: * ;
90
6
91
- use crate :: { get_event_loop, CALL_SOON , CREATE_FUTURE , EXPECT_INIT } ;
7
+ use crate :: generic:: { self , JoinError , Runtime } ;
8
+
9
+ struct AsyncStdJoinError ;
10
+
11
+ impl JoinError for AsyncStdJoinError {
12
+ fn is_panic ( & self ) -> bool {
13
+ todo ! ( )
14
+ }
15
+ }
16
+
17
+ struct AsyncStdRuntime ;
18
+
19
+ impl Runtime for AsyncStdRuntime {
20
+ type JoinError = AsyncStdJoinError ;
21
+ type JoinHandle = task:: JoinHandle < Result < ( ) , AsyncStdJoinError > > ;
22
+
23
+ fn spawn < F > ( fut : F ) -> Self :: JoinHandle
24
+ where
25
+ F : Future < Output = ( ) > + Send + ' static ,
26
+ {
27
+ task:: spawn ( async move {
28
+ fut. await ;
29
+ Ok ( ( ) )
30
+ } )
31
+ }
32
+ }
92
33
93
34
/// Run the event loop until the given Future completes
94
35
///
@@ -107,17 +48,11 @@ use crate::{get_event_loop, CALL_SOON, CREATE_FUTURE, EXPECT_INIT};
107
48
/// # use std::time::Duration;
108
49
/// #
109
50
/// # use pyo3::prelude::*;
110
- /// # use tokio::runtime::{Builder, Runtime};
111
- /// #
112
- /// # let runtime = Builder::new_current_thread()
113
- /// # .enable_all()
114
- /// # .build()
115
- /// # .expect("Couldn't build the runtime");
116
51
/// #
117
52
/// # Python::with_gil(|py| {
118
53
/// # pyo3_asyncio::with_runtime(py, || {
119
- /// pyo3_asyncio::tokio ::run_until_complete(py, &runtime , async move {
120
- /// tokio::time ::sleep(Duration::from_secs(1)).await;
54
+ /// pyo3_asyncio::async_std ::run_until_complete(py, async move {
55
+ /// async_std::task ::sleep(Duration::from_secs(1)).await;
121
56
/// Ok(())
122
57
/// })?;
123
58
/// # Ok(())
@@ -132,14 +67,7 @@ pub fn run_until_complete<F>(py: Python, fut: F) -> PyResult<()>
132
67
where
133
68
F : Future < Output = PyResult < ( ) > > + Send + ' static ,
134
69
{
135
- let coro = into_coroutine ( py, async move {
136
- fut. await ?;
137
- Ok ( Python :: with_gil ( |py| py. None ( ) ) )
138
- } ) ?;
139
-
140
- get_event_loop ( py) . call_method1 ( "run_until_complete" , ( coro, ) ) ?;
141
-
142
- Ok ( ( ) )
70
+ generic:: run_until_complete :: < AsyncStdRuntime , _ > ( py, fut)
143
71
}
144
72
145
73
#[ pyclass]
@@ -173,27 +101,6 @@ impl PyTaskCompleter {
173
101
}
174
102
}
175
103
176
- fn set_result ( py : Python , future : & PyAny , result : PyResult < PyObject > ) -> PyResult < ( ) > {
177
- match result {
178
- Ok ( val) => {
179
- let set_result = future. getattr ( "set_result" ) ?;
180
- CALL_SOON
181
- . get ( )
182
- . expect ( EXPECT_INIT )
183
- . call1 ( py, ( set_result, val) ) ?;
184
- }
185
- Err ( err) => {
186
- let set_exception = future. getattr ( "set_exception" ) ?;
187
- CALL_SOON
188
- . get ( )
189
- . expect ( EXPECT_INIT )
190
- . call1 ( py, ( set_exception, err) ) ?;
191
- }
192
- }
193
-
194
- Ok ( ( ) )
195
- }
196
-
197
104
fn dump_err ( py : Python < ' _ > ) -> impl FnOnce ( PyErr ) + ' _ {
198
105
move |e| {
199
106
// We can't display Python exceptions via std::fmt::Display,
@@ -213,25 +120,14 @@ fn dump_err(py: Python<'_>) -> impl FnOnce(PyErr) + '_ {
213
120
/// ```no_run
214
121
/// use std::time::Duration;
215
122
///
216
- /// use lazy_static::lazy_static;
217
123
/// use pyo3::prelude::*;
218
- /// use tokio::runtime::{Builder, Runtime};
219
- ///
220
- /// lazy_static! {
221
- /// static ref CURRENT_THREAD_RUNTIME: Runtime = {
222
- /// Builder::new_current_thread()
223
- /// .enable_all()
224
- /// .build()
225
- /// .expect("Couldn't build the runtime")
226
- /// };
227
- /// }
228
124
///
229
125
/// /// Awaitable sleep function
230
126
/// #[pyfunction]
231
127
/// fn sleep_for(py: Python, secs: &PyAny) -> PyResult<PyObject> {
232
128
/// let secs = secs.extract()?;
233
129
///
234
- /// pyo3_asyncio::tokio ::into_coroutine(py, &CURRENT_THREAD_RUNTIME , async move {
130
+ /// pyo3_asyncio::async_std ::into_coroutine(py, async move {
235
131
/// tokio::time::sleep(Duration::from_secs(secs)).await;
236
132
/// Python::with_gil(|py| Ok(py.None()))
237
133
/// })
@@ -241,32 +137,96 @@ pub fn into_coroutine<F>(py: Python, fut: F) -> PyResult<PyObject>
241
137
where
242
138
F : Future < Output = PyResult < PyObject > > + Send + ' static ,
243
139
{
244
- let future_rx = CREATE_FUTURE . get ( ) . expect ( EXPECT_INIT ) . call0 ( py) ?;
245
- let future_tx1 = future_rx. clone ( ) ;
246
-
247
- task:: spawn ( async move {
248
- task:: spawn ( async move {
249
- let result = fut. await ;
250
-
251
- Python :: with_gil ( move |py| {
252
- if set_result ( py, future_tx1. as_ref ( py) , result)
253
- . map_err ( dump_err ( py) )
254
- . is_err ( )
255
- {
256
-
257
- // Cancelled
258
- }
259
- } ) ;
260
- } )
261
- . await ;
262
- } ) ;
263
-
264
- Ok ( future_rx)
140
+ generic:: into_coroutine :: < AsyncStdRuntime , _ > ( py, fut)
265
141
}
266
142
267
143
/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>testing</code></span> Testing Utilities for the async-std runtime.
268
144
#[ cfg( feature = "testing" ) ]
269
145
pub mod testing {
146
+ //! # PyO3 Asyncio Testing Utilities
147
+ //!
148
+ //! This module provides some utilities for parsing test arguments as well as running and filtering
149
+ //! a sequence of tests.
150
+ //!
151
+ //! As mentioned [here](crate#pythons-event-loop), PyO3 Asyncio tests cannot use the default test
152
+ //! harness since it doesn't allow Python to gain control over the main thread. Instead, we have to
153
+ //! provide our own test harness in order to create integration tests.
154
+ //!
155
+ //! ## Creating A PyO3 Asyncio Integration Test
156
+ //!
157
+ //! ### Main Test File
158
+ //! First, we need to create the test's main file. Although these tests are considered integration
159
+ //! tests, we cannot put them in the `tests` directory since that is a special directory owned by
160
+ //! Cargo. Instead, we put our tests in a `pytests` directory, although the name `pytests` is just
161
+ //! a convention.
162
+ //!
163
+ //! `pytests/test_example.rs`
164
+ //! ```no_run
165
+ //! fn main() {
166
+ //!
167
+ //! }
168
+ //! ```
169
+ //!
170
+ //! ### Test Manifest Entry
171
+ //! Next, we need to add our test file to the Cargo manifest. Add the following section to your
172
+ //! `Cargo.toml`
173
+ //!
174
+ //! ```toml
175
+ //! [[test]]
176
+ //! name = "test_example"
177
+ //! path = "pytests/test_example.rs"
178
+ //! harness = false
179
+ //! ```
180
+ //!
181
+ //! At this point you should be able to run the test via `cargo test`
182
+ //!
183
+ //! ### Using the PyO3 Asyncio Test Harness
184
+ //! Now that we've got our test registered with `cargo test`, we can start using the PyO3 Asyncio
185
+ //! test harness.
186
+ //!
187
+ //! In your `Cargo.toml` add the testing feature to `pyo3-asyncio`:
188
+ //! ```toml
189
+ //! pyo3-asyncio = { version = "0.13", features = ["testing", "async-std-runtime"] }
190
+ //! ```
191
+ //!
192
+ //! Now, in your test's main file, call [`async_std::testing::test_main`]:
193
+ //!
194
+ //! ```no_run
195
+ //! fn main() {
196
+ //! pyo3_asyncio::async_std::testing::test_main("Example Test Suite", vec![]);
197
+ //! }
198
+ //! ```
199
+ //!
200
+ //! ### Adding Tests to the PyO3 Asyncio Test Harness
201
+ //!
202
+ //! ```no_run
203
+ //! use std::{time::Duration, thread};
204
+ //!
205
+ //! use pyo3_asyncio::testing::Test;
206
+ //!
207
+ //! fn main() {
208
+ //! pyo3_asyncio::async_std::testing::test_main(
209
+ //! "Example Test Suite",
210
+ //! vec![
211
+ //! Test::new_async(
212
+ //! "test_async_sleep".into(),
213
+ //! async move {
214
+ //! async_std::task::sleep(Duration::from_secs(1)).await;
215
+ //! Ok(())
216
+ //! }
217
+ //! ),
218
+ //! pyo3_asyncio::async_std::testing::new_sync_test(
219
+ //! "test_sync_sleep".into(),
220
+ //! || {
221
+ //! thread::sleep(Duration::from_secs(1));
222
+ //! Ok(())
223
+ //! }
224
+ //! )
225
+ //! ]
226
+ //! );
227
+ //! }
228
+ //! ```
229
+
270
230
use async_std:: task;
271
231
use pyo3:: prelude:: * ;
272
232
0 commit comments