2022-10-24 01:50:31 +02:00
|
|
|
use proc_macro::TokenStream;
|
|
|
|
use proc_macro2::{Ident, Span};
|
|
|
|
use quote::{quote, quote_spanned, ToTokens};
|
2024-02-28 22:04:33 +01:00
|
|
|
use syn::{parse::Parser, punctuated::Punctuated, spanned::Spanned, ExprAssign, Token};
|
2022-10-24 01:50:31 +02:00
|
|
|
|
|
|
|
fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
|
|
|
|
tokens.extend(TokenStream::from(error.into_compile_error()));
|
|
|
|
tokens
|
|
|
|
}
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
const ALLOWED_ATTRIBUTES: &[&str] = &["audit", "domain_level"];
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct Flags {
|
|
|
|
audit: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_attributes(
|
|
|
|
args: &TokenStream,
|
|
|
|
input: &syn::ItemFn,
|
|
|
|
) -> Result<(proc_macro2::TokenStream, Flags), syn::Error> {
|
|
|
|
let args: Punctuated<ExprAssign, syn::token::Comma> =
|
|
|
|
match Punctuated::<ExprAssign, Token![,]>::parse_terminated.parse(args.clone()) {
|
|
|
|
Ok(it) => it,
|
|
|
|
Err(e) => return Err(e),
|
|
|
|
};
|
|
|
|
|
|
|
|
let args_are_allowed = args.pairs().all(|p| {
|
|
|
|
ALLOWED_ATTRIBUTES.to_vec().contains(
|
|
|
|
&p.value()
|
|
|
|
.left
|
|
|
|
.span()
|
|
|
|
.source_text()
|
|
|
|
.unwrap_or_default()
|
|
|
|
.as_str(),
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
if !args_are_allowed {
|
|
|
|
let msg = "Invalid test config attribute. The following are allow";
|
|
|
|
return Err(syn::Error::new_spanned(
|
|
|
|
input.sig.fn_token,
|
|
|
|
format!("{}: {}", msg, ALLOWED_ATTRIBUTES.join(", ")),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut flags = Flags::default();
|
|
|
|
let mut field_modifications = quote! {};
|
|
|
|
|
|
|
|
args.pairs().for_each(|p| {
|
|
|
|
match p
|
|
|
|
.value()
|
|
|
|
.left
|
|
|
|
.span()
|
|
|
|
.source_text()
|
|
|
|
.unwrap_or_default()
|
|
|
|
.as_str()
|
|
|
|
{
|
|
|
|
"audit" => flags.audit = true,
|
|
|
|
_ => {
|
|
|
|
let field_name = p.value().left.to_token_stream(); // here we can use to_token_stream as we know we're iterating over ExprAssigns
|
|
|
|
let field_value = p.value().right.to_token_stream();
|
|
|
|
field_modifications.extend(quote! {
|
|
|
|
#field_name: #field_value,})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let ts = quote!(crate::testkit::TestConfiguration {
|
|
|
|
#field_modifications
|
|
|
|
..crate::testkit::TestConfiguration::default()
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok((ts, flags))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn qs_test(args: TokenStream, item: TokenStream) -> TokenStream {
|
2022-10-24 01:50:31 +02:00
|
|
|
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
|
|
|
Ok(it) => it,
|
|
|
|
Err(e) => return token_stream_with_error(item, e),
|
|
|
|
};
|
|
|
|
|
2023-03-30 08:35:10 +02:00
|
|
|
if let Some(attr) = input.attrs.iter().find(|attr| attr.path().is_ident("test")) {
|
2022-10-24 01:50:31 +02:00
|
|
|
let msg = "second test attribute is supplied";
|
2022-11-11 01:23:49 +01:00
|
|
|
return token_stream_with_error(item, syn::Error::new_spanned(attr, msg));
|
2022-10-24 01:50:31 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if input.sig.asyncness.is_none() {
|
|
|
|
let msg = "the `async` keyword is missing from the function declaration";
|
|
|
|
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If type mismatch occurs, the current rustc points to the last statement.
|
|
|
|
let (last_stmt_start_span, _last_stmt_end_span) = {
|
|
|
|
let mut last_stmt = input
|
|
|
|
.block
|
|
|
|
.stmts
|
|
|
|
.last()
|
|
|
|
.map(ToTokens::into_token_stream)
|
|
|
|
.unwrap_or_default()
|
|
|
|
.into_iter();
|
|
|
|
// `Span` on stable Rust has a limitation that only points to the first
|
|
|
|
// token, not the whole tokens. We can work around this limitation by
|
|
|
|
// using the first/last span of the tokens like
|
|
|
|
// `syn::Error::new_spanned` does.
|
|
|
|
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
|
|
|
let end = last_stmt.last().map_or(start, |t| t.span());
|
|
|
|
(start, end)
|
|
|
|
};
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
// Setup the config filling the remaining fields with the default values
|
|
|
|
let (default_config_struct, _flags) = match parse_attributes(&args, &input) {
|
|
|
|
Ok(dc) => dc,
|
|
|
|
Err(e) => return token_stream_with_error(args, e),
|
|
|
|
};
|
|
|
|
|
2022-10-24 01:50:31 +02:00
|
|
|
let rt = quote_spanned! {last_stmt_start_span=>
|
|
|
|
tokio::runtime::Builder::new_current_thread()
|
|
|
|
};
|
|
|
|
|
|
|
|
let header = quote! {
|
|
|
|
#[::core::prelude::v1::test]
|
|
|
|
};
|
|
|
|
|
|
|
|
let test_fn = &input.sig.ident;
|
|
|
|
let test_driver = Ident::new(&format!("qs_{}", test_fn), input.sig.span());
|
|
|
|
|
|
|
|
// Effectively we are just injecting a real test function around this which we will
|
|
|
|
// call.
|
|
|
|
|
|
|
|
let result = quote! {
|
|
|
|
#input
|
|
|
|
|
|
|
|
#header
|
|
|
|
fn #test_driver() {
|
|
|
|
let body = async {
|
2024-02-28 22:04:33 +01:00
|
|
|
let test_config = #default_config_struct;
|
2022-10-24 01:50:31 +02:00
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
let _profiler = dhat::Profiler::new_heap();
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
let test_server = crate::testkit::setup_test(test_config).await;
|
2022-10-24 01:50:31 +02:00
|
|
|
|
|
|
|
#test_fn(&test_server).await;
|
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
drop(_profiler);
|
|
|
|
|
2022-10-24 01:50:31 +02:00
|
|
|
// Any needed teardown?
|
2023-08-29 04:26:29 +02:00
|
|
|
// Clear the cache before we verify.
|
|
|
|
assert!(test_server.clear_cache().await.is_ok());
|
2022-10-24 01:50:31 +02:00
|
|
|
// Make sure there are no errors.
|
|
|
|
let verifications = test_server.verify().await;
|
|
|
|
trace!("Verification result: {:?}", verifications);
|
|
|
|
assert!(verifications.len() == 0);
|
|
|
|
};
|
|
|
|
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
|
|
|
{
|
|
|
|
return #rt
|
|
|
|
.enable_all()
|
2023-01-25 05:14:16 +01:00
|
|
|
.build()
|
|
|
|
.expect("Failed building the Runtime")
|
|
|
|
.block_on(body);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
result.into()
|
|
|
|
}
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
pub(crate) fn qs_pair_test(args: &TokenStream, item: TokenStream) -> TokenStream {
|
2023-01-25 05:14:16 +01:00
|
|
|
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
|
|
|
Ok(it) => it,
|
|
|
|
Err(e) => return token_stream_with_error(item, e),
|
|
|
|
};
|
|
|
|
|
2023-03-30 08:35:10 +02:00
|
|
|
if let Some(attr) = input.attrs.iter().find(|attr| attr.path().is_ident("test")) {
|
2023-01-25 05:14:16 +01:00
|
|
|
let msg = "second test attribute is supplied";
|
|
|
|
return token_stream_with_error(item, syn::Error::new_spanned(attr, msg));
|
|
|
|
};
|
|
|
|
|
|
|
|
if input.sig.asyncness.is_none() {
|
|
|
|
let msg = "the `async` keyword is missing from the function declaration";
|
|
|
|
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If type mismatch occurs, the current rustc points to the last statement.
|
|
|
|
let (last_stmt_start_span, _last_stmt_end_span) = {
|
|
|
|
let mut last_stmt = input
|
|
|
|
.block
|
|
|
|
.stmts
|
|
|
|
.last()
|
|
|
|
.map(ToTokens::into_token_stream)
|
|
|
|
.unwrap_or_default()
|
|
|
|
.into_iter();
|
|
|
|
// `Span` on stable Rust has a limitation that only points to the first
|
|
|
|
// token, not the whole tokens. We can work around this limitation by
|
|
|
|
// using the first/last span of the tokens like
|
|
|
|
// `syn::Error::new_spanned` does.
|
|
|
|
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
|
|
|
let end = last_stmt.last().map_or(start, |t| t.span());
|
|
|
|
(start, end)
|
|
|
|
};
|
|
|
|
|
|
|
|
let rt = quote_spanned! {last_stmt_start_span=>
|
|
|
|
tokio::runtime::Builder::new_current_thread()
|
|
|
|
};
|
|
|
|
|
|
|
|
let header = quote! {
|
|
|
|
#[::core::prelude::v1::test]
|
|
|
|
};
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
// Setup the config filling the remaining fields with the default values
|
2024-03-06 02:33:14 +01:00
|
|
|
let (default_config_struct, _flags) = match parse_attributes(args, &input) {
|
2024-02-28 22:04:33 +01:00
|
|
|
Ok(dc) => dc,
|
|
|
|
Err(e) => return token_stream_with_error(args.clone(), e),
|
2023-01-25 05:14:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let test_fn = &input.sig.ident;
|
|
|
|
let test_driver = Ident::new(&format!("qs_{}", test_fn), input.sig.span());
|
|
|
|
|
|
|
|
// Effectively we are just injecting a real test function around this which we will
|
|
|
|
// call.
|
|
|
|
|
|
|
|
let result = quote! {
|
|
|
|
#input
|
|
|
|
|
|
|
|
#header
|
|
|
|
fn #test_driver() {
|
|
|
|
let body = async {
|
2024-02-28 22:04:33 +01:00
|
|
|
let test_config = #default_config_struct;
|
2023-01-25 05:14:16 +01:00
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
let _profiler = dhat::Profiler::new_heap();
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
let (server_a, server_b) = crate::testkit::setup_pair_test(test_config).await;
|
2023-01-25 05:14:16 +01:00
|
|
|
|
|
|
|
#test_fn(&server_a, &server_b).await;
|
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
drop(_profiler);
|
|
|
|
|
2023-01-25 05:14:16 +01:00
|
|
|
// Any needed teardown?
|
2023-08-29 04:26:29 +02:00
|
|
|
assert!(server_a.clear_cache().await.is_ok());
|
|
|
|
assert!(server_b.clear_cache().await.is_ok());
|
2023-01-25 05:14:16 +01:00
|
|
|
// Make sure there are no errors.
|
|
|
|
let verifications_a = server_a.verify().await;
|
|
|
|
let verifications_b = server_b.verify().await;
|
|
|
|
trace!("Verification result: {:?}, {:?}", verifications_a, verifications_b);
|
|
|
|
assert!(verifications_a.len() + verifications_b.len() == 0);
|
|
|
|
};
|
|
|
|
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
|
|
|
{
|
|
|
|
return #rt
|
|
|
|
.enable_all()
|
2022-10-24 01:50:31 +02:00
|
|
|
.build()
|
|
|
|
.expect("Failed building the Runtime")
|
|
|
|
.block_on(body);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
result.into()
|
|
|
|
}
|
2022-11-02 10:46:09 +01:00
|
|
|
|
2023-06-08 12:17:46 +02:00
|
|
|
pub(crate) fn idm_test(args: &TokenStream, item: TokenStream) -> TokenStream {
|
2022-11-02 10:46:09 +01:00
|
|
|
let input: syn::ItemFn = match syn::parse(item.clone()) {
|
|
|
|
Ok(it) => it,
|
|
|
|
Err(e) => return token_stream_with_error(item, e),
|
|
|
|
};
|
|
|
|
|
2023-03-30 08:35:10 +02:00
|
|
|
if let Some(attr) = input.attrs.iter().find(|attr| attr.path().is_ident("test")) {
|
2022-11-02 10:46:09 +01:00
|
|
|
let msg = "second test attribute is supplied";
|
2022-11-11 01:23:49 +01:00
|
|
|
return token_stream_with_error(item, syn::Error::new_spanned(attr, msg));
|
2022-11-02 10:46:09 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if input.sig.asyncness.is_none() {
|
|
|
|
let msg = "the `async` keyword is missing from the function declaration";
|
|
|
|
return token_stream_with_error(item, syn::Error::new_spanned(input.sig.fn_token, msg));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If type mismatch occurs, the current rustc points to the last statement.
|
|
|
|
let (last_stmt_start_span, _last_stmt_end_span) = {
|
|
|
|
let mut last_stmt = input
|
|
|
|
.block
|
|
|
|
.stmts
|
|
|
|
.last()
|
|
|
|
.map(ToTokens::into_token_stream)
|
|
|
|
.unwrap_or_default()
|
|
|
|
.into_iter();
|
|
|
|
// `Span` on stable Rust has a limitation that only points to the first
|
|
|
|
// token, not the whole tokens. We can work around this limitation by
|
|
|
|
// using the first/last span of the tokens like
|
|
|
|
// `syn::Error::new_spanned` does.
|
|
|
|
let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span());
|
|
|
|
let end = last_stmt.last().map_or(start, |t| t.span());
|
|
|
|
(start, end)
|
|
|
|
};
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
// Setup the config filling the remaining fields with the default values
|
2024-03-06 02:33:14 +01:00
|
|
|
let (default_config_struct, flags) = match parse_attributes(args, &input) {
|
2024-02-28 22:04:33 +01:00
|
|
|
Ok(dc) => dc,
|
|
|
|
Err(e) => return token_stream_with_error(args.clone(), e),
|
|
|
|
};
|
|
|
|
|
2022-11-02 10:46:09 +01:00
|
|
|
let rt = quote_spanned! {last_stmt_start_span=>
|
|
|
|
tokio::runtime::Builder::new_current_thread()
|
|
|
|
};
|
|
|
|
|
|
|
|
let header = quote! {
|
|
|
|
#[::core::prelude::v1::test]
|
|
|
|
};
|
|
|
|
|
|
|
|
let test_fn = &input.sig.ident;
|
|
|
|
let test_driver = Ident::new(&format!("idm_{}", test_fn), input.sig.span());
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
let test_fn_args = if flags.audit {
|
2023-06-08 12:17:46 +02:00
|
|
|
quote! {
|
|
|
|
&test_server, &mut idms_delayed, &mut idms_audit
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
quote! {
|
|
|
|
&test_server, &mut idms_delayed
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-02 10:46:09 +01:00
|
|
|
// Effectively we are just injecting a real test function around this which we will
|
|
|
|
// call.
|
|
|
|
|
|
|
|
let result = quote! {
|
|
|
|
#input
|
|
|
|
|
|
|
|
#header
|
|
|
|
fn #test_driver() {
|
|
|
|
let body = async {
|
2024-02-28 22:04:33 +01:00
|
|
|
let test_config = #default_config_struct;
|
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
let _profiler = dhat::Profiler::new_heap();
|
|
|
|
|
2024-02-28 22:04:33 +01:00
|
|
|
let (test_server, mut idms_delayed, mut idms_audit) = crate::testkit::setup_idm_test(test_config).await;
|
2022-11-02 10:46:09 +01:00
|
|
|
|
2023-06-08 12:17:46 +02:00
|
|
|
#test_fn(#test_fn_args).await;
|
2022-11-02 10:46:09 +01:00
|
|
|
|
2024-06-13 01:48:49 +02:00
|
|
|
#[cfg(feature = "dhat-heap")]
|
|
|
|
drop(_profiler);
|
|
|
|
|
2022-11-02 10:46:09 +01:00
|
|
|
// Any needed teardown?
|
2023-08-29 04:26:29 +02:00
|
|
|
// assert!(test_server.clear_cache().await.is_ok());
|
2022-11-02 10:46:09 +01:00
|
|
|
// Make sure there are no errors.
|
2024-08-02 01:29:46 +02:00
|
|
|
let mut idm_read_txn = test_server.proxy_read().await.unwrap();
|
2022-11-02 10:46:09 +01:00
|
|
|
let verifications = idm_read_txn.qs_read.verify();
|
|
|
|
trace!("Verification result: {:?}", verifications);
|
|
|
|
assert!(verifications.len() == 0);
|
|
|
|
|
|
|
|
idms_delayed.check_is_empty_or_panic();
|
2023-06-08 12:17:46 +02:00
|
|
|
idms_audit.check_is_empty_or_panic();
|
2022-11-02 10:46:09 +01:00
|
|
|
};
|
|
|
|
#[allow(clippy::expect_used, clippy::diverging_sub_expression)]
|
|
|
|
{
|
|
|
|
return #rt
|
|
|
|
.enable_all()
|
|
|
|
.build()
|
|
|
|
.expect("Failed building the Runtime")
|
|
|
|
.block_on(body);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
result.into()
|
|
|
|
}
|