diff --git a/glib-macros/src/lib.rs b/glib-macros/src/lib.rs index da9a8b7b6314..b206851e5ff6 100644 --- a/glib-macros/src/lib.rs +++ b/glib-macros/src/lib.rs @@ -11,6 +11,7 @@ mod flags_attribute; mod object_impl_attributes; mod properties; mod shared_boxed_derive; +mod test; mod value_delegate_derive; mod variant_derive; @@ -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::test] +/// async fn my_async_test() { +/// // Test code that runs asynchronously +/// } +/// ``` +#[proc_macro_attribute] +pub fn test(args: TokenStream, item: TokenStream) -> TokenStream { + test::test(args, item) +} diff --git a/glib-macros/src/test.rs b/glib-macros/src/test.rs new file mode 100644 index 000000000000..606550ce8af1 --- /dev/null +++ b/glib-macros/src/test.rs @@ -0,0 +1,45 @@ +use proc_macro::TokenStream; +use quote::ToTokens; + +pub(crate) fn 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::default(); + 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/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; diff --git a/glib/tests/log.rs b/glib/tests/log.rs index a53cad91b389..e22820be4fd6 100644 --- a/glib/tests/log.rs +++ b/glib/tests/log.rs @@ -1,6 +1,10 @@ use std::sync::{Arc, Mutex}; -use glib::*; +use glib::{ + clone, g_critical, g_debug, g_info, g_message, g_warning, log_remove_handler, + log_set_default_handler, log_set_fatal_mask, log_set_handler, log_unset_default_handler, + LogLevel, LogLevels, +}; #[derive(Default)] struct Counters { diff --git a/glib/tests/print.rs b/glib/tests/print.rs index 5c1ca493054e..04b55be54c70 100644 --- a/glib/tests/print.rs +++ b/glib/tests/print.rs @@ -1,6 +1,9 @@ use std::sync::{Arc, Mutex}; -use glib::*; +use glib::{ + clone, g_print, g_printerr, set_print_handler, set_printerr_handler, unset_print_handler, + unset_printerr_handler, +}; // Funny thing: we can't put those two tests in two different functions, otherwise they might // conflict with the results of the other one (or it would be mandatory to run the tests on only