diff --git a/glib-macros/src/async_test.rs b/glib-macros/src/async_test.rs new file mode 100644 index 000000000000..b55e84f4048b --- /dev/null +++ b/glib-macros/src/async_test.rs @@ -0,0 +1,47 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use proc_macro::TokenStream; +use quote::ToTokens; + +pub(crate) fn async_test(_args: TokenStream, mut item: TokenStream) -> TokenStream { + let mut item_fn: syn::ItemFn = match syn::parse(item.clone()) { + Ok(it) => it, + Err(e) => { + item.extend(TokenStream::from(e.into_compile_error())); + return item; + } + }; + + if item_fn.sig.asyncness.is_none() { + item.extend(TokenStream::from( + syn::Error::new_spanned( + item_fn.sig.ident, + "The 'async' keyword is missing from the test function declaration", + ) + .into_compile_error(), + )); + return item; + } + + item_fn.sig.asyncness = None; + + let gen_attr = quote::quote! { + #[::core::prelude::v1::test] + }; + + let body = &item_fn.block; + + item_fn.block = syn::parse2(quote::quote! { + { + let main_ctx = glib::MainContext::new(); + main_ctx.block_on(async #body) + } + }) + .expect("Body parsing failure"); + + let mut tokens = TokenStream::new(); + tokens.extend(TokenStream::from(gen_attr.to_token_stream())); + tokens.extend(TokenStream::from(item_fn.into_token_stream())); + + tokens +} diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index da9a8b7b6314..8f66dc855fd5 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -1,5 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. +mod async_test; mod boxed_derive; mod clone; mod closure; @@ -1567,3 +1568,22 @@ pub fn derive_value_delegate(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as value_delegate_derive::ValueDelegateInput); value_delegate_derive::impl_value_delegate(input).unwrap() } + +/// An attribute macro for writing asynchronous test functions. +/// +/// This macro is designed to wrap an asynchronous test function and ensure that +/// it runs within a `glib::MainContext`. It helps in writing async tests that +/// require the use of an event loop for the asynchronous execution. +/// +/// # Example +/// +/// ``` +/// #[glib::async_test] +/// async fn my_async_test() { +/// // Test code that runs asynchronously +/// } +/// ``` +#[proc_macro_attribute] +pub fn async_test(args: TokenStream, item: TokenStream) -> TokenStream { + async_test::async_test(args, item) +} diff --git a/glib/src/lib.rs b/glib/src/lib.rs index 556e102d9f59..87d974024155 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -30,7 +30,7 @@ pub use bitflags; pub use glib_macros::cstr_bytes; pub use glib_macros::{ clone, closure, closure_local, derived_properties, flags, object_interface, object_subclass, - Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant, + test, Boxed, Downgrade, Enum, ErrorDomain, Properties, SharedBoxed, ValueDelegate, Variant, }; pub use glib_sys as ffi; pub use gobject_sys as gobject_ffi;