Skip to content

Commit 08c70e2

Browse files
committed
gio/cancellable-future: Add tests verifying the behavior with GioFuture
Since CancellableFuture can be used group and manage async operations that support cancellation, add tests to ensure that this is the case
1 parent ca7e8f4 commit 08c70e2

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

gio/src/cancellable_future.rs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,81 @@ mod tests {
161161
.unwrap()
162162
}
163163

164+
fn async_begin<P: FnOnce(Result<(), glib::Error>) + Send + 'static>(
165+
duration: Duration,
166+
must_be_cancelled_on_begin: bool,
167+
must_be_cancelled_after_sleep: bool,
168+
cancellable: &Cancellable,
169+
callback: P,
170+
) {
171+
// We do not use std::thread here since we want to simulate what C code normally does.
172+
// Also not using spawn_blocking() directly, since we want to have the full control
173+
// for the test case.
174+
let callback = Box::new(callback);
175+
let task = unsafe {
176+
crate::Task::<bool>::new(None::<&glib::Binding>, Some(cancellable), move |t, _| {
177+
let cancellable = t.cancellable().unwrap();
178+
let ret = t.propagate();
179+
println!(
180+
"Task callback, returning {:?} - cancelled {}",
181+
ret,
182+
cancellable.is_cancelled()
183+
);
184+
assert_eq!(cancellable.is_cancelled(), must_be_cancelled_after_sleep);
185+
match ret {
186+
Err(e) => callback(Err(e)),
187+
Ok(_) => callback(Ok(())),
188+
};
189+
})
190+
};
191+
192+
task.run_in_thread(move |task, _: Option<&glib::Binding>, cancellable| {
193+
let cancellable = cancellable.unwrap();
194+
let func = || {
195+
println!(
196+
"Task thread started, cancelled {} - want {}",
197+
cancellable.is_cancelled(),
198+
must_be_cancelled_on_begin
199+
);
200+
assert_eq!(cancellable.is_cancelled(), must_be_cancelled_on_begin);
201+
std::thread::sleep(duration);
202+
assert_eq!(cancellable.is_cancelled(), must_be_cancelled_after_sleep);
203+
println!(
204+
"Task thread done, cancelled {} - want {}",
205+
cancellable.is_cancelled(),
206+
must_be_cancelled_after_sleep
207+
)
208+
};
209+
210+
if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(func)) {
211+
std::panic::resume_unwind(e);
212+
}
213+
214+
unsafe {
215+
task.return_result(match cancellable.set_error_if_cancelled() {
216+
Err(e) => Err(e),
217+
Ok(_) => Ok(true),
218+
});
219+
}
220+
});
221+
}
222+
223+
fn async_future(
224+
duration: Duration,
225+
must_be_cancelled_on_begin: bool,
226+
must_be_cancelled_after_sleep: bool,
227+
) -> std::pin::Pin<Box<dyn Future<Output = Result<(), glib::Error>> + 'static>> {
228+
Box::pin(crate::GioFuture::new(&(), move |_, cancellable, send| {
229+
async_begin(
230+
duration,
231+
must_be_cancelled_on_begin,
232+
must_be_cancelled_after_sleep,
233+
cancellable,
234+
move |res| send.resolve(res),
235+
);
236+
}))
237+
}
238+
164239
#[test]
165240
fn cancellable_future_ok() {
166241
let ctx = glib::MainContext::new();
@@ -234,4 +309,108 @@ mod tests {
234309
assert!(matches!(r1, Err(Cancelled)));
235310
assert!(matches!(r2, ()));
236311
}
312+
313+
#[test]
314+
fn cancellable_future_immediate_cancel_with_gio_future() {
315+
let ctx = glib::MainContext::new();
316+
let c = Cancellable::new();
317+
318+
async fn async_chain() -> Result<(), glib::Error> {
319+
async_future(Duration::from_millis(250), true, true).await?;
320+
async_future(Duration::from_hours(99999), true, true).await
321+
}
322+
323+
c.cancel();
324+
325+
let result = ctx
326+
.block_on(ctx.spawn_local({
327+
CancellableFuture::new(
328+
futures_util::future::join5(
329+
async_chain(),
330+
async_chain(),
331+
async_chain(),
332+
async_chain(),
333+
CancellableFuture::new(async_chain(), Cancellable::new()),
334+
),
335+
c.clone(),
336+
)
337+
}))
338+
.expect("futures must be executed");
339+
340+
assert!(matches!(result, Err(Cancelled)));
341+
}
342+
343+
#[test]
344+
fn cancellable_future_delayed_cancel_with_gio_future() {
345+
let ctx = glib::MainContext::new();
346+
let c = Cancellable::new();
347+
348+
async fn async_chain() -> Result<(), glib::Error> {
349+
async_future(Duration::from_millis(250), false, true).await?;
350+
async_future(Duration::from_hours(99999), true, true).await
351+
}
352+
353+
let (result, _, _) = ctx
354+
.block_on(ctx.spawn_local({
355+
futures_util::future::join3(
356+
CancellableFuture::new(
357+
futures_util::future::join5(
358+
async_chain(),
359+
async_chain(),
360+
async_chain(),
361+
async_chain(),
362+
CancellableFuture::new(async_chain(), Cancellable::new()),
363+
),
364+
c.clone(),
365+
),
366+
cancel_after_sleep_in_thread(Duration::from_millis(100), c.clone()),
367+
// Let's wait a bit more to ensure that more events are processed
368+
// by the loop. Not required, but it simulates a more real
369+
// scenario.
370+
glib::timeout_future(Duration::from_millis(350)),
371+
)
372+
}))
373+
.expect("futures must be executed");
374+
375+
assert!(matches!(result, Err(Cancelled)));
376+
}
377+
378+
#[test]
379+
fn cancellable_future_late_cancel_with_gio_future() {
380+
let ctx = glib::MainContext::new();
381+
let c = Cancellable::new();
382+
383+
async fn async_chain() -> Result<(), glib::Error> {
384+
async_future(Duration::from_millis(100), false, false).await?;
385+
async_future(Duration::from_millis(100), false, false).await
386+
}
387+
388+
let results = ctx
389+
.block_on(ctx.spawn_local(async move {
390+
let ret = CancellableFuture::new(
391+
futures_util::future::join5(
392+
async_chain(),
393+
async_chain(),
394+
async_chain(),
395+
async_chain(),
396+
CancellableFuture::new(async_chain(), Cancellable::new()),
397+
),
398+
c.clone(),
399+
)
400+
.await;
401+
402+
c.cancel();
403+
ret
404+
}))
405+
.expect("futures must be executed");
406+
407+
assert!(results.is_ok());
408+
409+
let r1 = results.unwrap();
410+
assert!(matches!(r1.0, Ok(())));
411+
assert!(matches!(r1.1, Ok(())));
412+
assert!(matches!(r1.2, Ok(())));
413+
assert!(matches!(r1.3, Ok(())));
414+
assert!(matches!(r1.4.unwrap(), Ok(())));
415+
}
237416
}

0 commit comments

Comments
 (0)