use proc_macro2::TokenStream;
use quote::quote;
use syn::{ItemFn, parse2};
pub(crate) fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
if !attr.is_empty() {
return Err(syn::Error::new_spanned(
attr,
"#[obs::test] does not accept arguments",
));
}
let func: ItemFn = parse2(item)?;
let attrs = &func.attrs;
let vis = &func.vis;
let sig = &func.sig;
let body = &func.block;
let is_async = sig.asyncness.is_some();
let user_attrs: Vec<_> = attrs.iter().filter(|a| !attr_is_test(a)).cloned().collect();
let expanded = if is_async {
quote! {
#[::tokio::test]
#(#user_attrs)*
#vis #sig {
let (__obs_observer, __obs_handle, __obs_guard) =
::obs_core::test::install_thread_handle();
let __obs_result = ::obs_core::with_observer_task(
__obs_observer,
async move #body,
).await;
drop(__obs_guard);
let _ = __obs_handle;
__obs_result
}
}
} else {
quote! {
#[::core::prelude::v1::test]
#(#user_attrs)*
#vis #sig {
let (__obs_observer, __obs_handle, __obs_guard) =
::obs_core::test::install_thread_handle();
let __obs_result = ::obs_core::with_test_observer(
__obs_observer,
|| #body,
);
drop(__obs_guard);
let _ = __obs_handle;
__obs_result
}
}
};
Ok(expanded)
}
fn attr_is_test(attr: &syn::Attribute) -> bool {
let path = attr.path();
path.is_ident("test")
|| path
.segments
.last()
.is_some_and(|s| s.ident == "test" && path.segments.len() <= 2)
}