1extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use proc_macro2::{Ident, Span, TokenStream as Tokens};
8use proc_macro_crate::{crate_name, FoundCrate};
9use quote::quote;
10use syn::parse::Parser;
11use syn::punctuated::Punctuated;
12use syn::{parse_macro_input, parse_quote, ItemFn, Meta, ReturnType, Token};
13
14#[proc_macro_attribute]
67pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
68 let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
69 let args = match parser.parse2(attr.into()) {
70 Ok(args) => args,
71 Err(e) => panic!("{}", e),
72 };
73
74 let input = parse_macro_input!(item as ItemFn);
75
76 let args = args.into_iter().collect::<Vec<_>>();
77
78 let inner_test = match args.as_slice() {
79 [] => parse_quote! { ::core::prelude::v1::test },
80 [Meta::Path(path)] => quote! {#path},
81 [Meta::List(list)] => {
82 let path = &list.path;
83 let args = &list.tokens;
84 quote! { #path(#args) }
85 }
86 _ => {
87 panic!("unsupported attributes supplied: {}", quote! { args })
88 }
89 };
90
91 expand_wrapper(inner_test, &input)
92}
93
94fn expand_logging_init() -> Tokens {
96 let found_crate = crate_name("wick-logger").expect("wick-logger needs to be added in `Cargo.toml`");
97
98 match found_crate {
99 FoundCrate::Itself => quote! {
100 let logging_options = crate::LoggingOptionsBuilder::default()
101 .app_name("test")
102 .otlp_endpoint(std::env::var("OTLP_ENDPOINT").ok())
103 .levels(crate::LogFilters::with_level(crate::LogLevel::Trace))
104 .build()
105 .unwrap();
106 let __guard = crate::init_test(&logging_options);
107 },
108 FoundCrate::Name(name) => {
109 let ident = Ident::new(&name, Span::call_site());
110
111 quote! {
112 let logging_options = #ident::LoggingOptionsBuilder::default()
113 .app_name("test")
114 .otlp_endpoint(std::env::var("OTLP_ENDPOINT").ok())
115 .levels(#ident::LogFilters::with_level(#ident::LogLevel::Trace))
116 .build()
117 .unwrap();
118 let __guard = #ident::init_test(&logging_options);
119 }
120 }
121 }
122}
123
124fn expand_wrapper(inner_test: Tokens, wrappee: &ItemFn) -> TokenStream {
126 let attrs = &wrappee.attrs;
127 let async_ = &wrappee.sig.asyncness;
128 let await_ = if async_.is_some() {
129 quote! {.instrument(span).await}
130 } else {
131 quote! {}
132 };
133 let enter_ = if async_.is_some() {
134 quote! {use tracing::Instrument;}
135 } else {
136 quote! {let _guard = span.enter();}
137 };
138 let exit_ = if async_.is_some() {
139 quote! {
140 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
142 }
143 } else {
144 quote! {
145 drop(_guard);
146 }
147 };
148 let body = &wrappee.block;
149 let test_name = &wrappee.sig.ident;
150
151 let ret = match &wrappee.sig.output {
154 ReturnType::Default => quote! {},
155 ReturnType::Type(_, type_) => quote! {-> #type_},
156 };
157
158 let logging_init = expand_logging_init();
159
160 let result = quote! {
161 #[#inner_test]
162 #(#attrs)*
163 #async_ fn #test_name() #ret {
164 #async_ fn test_impl() #ret {
165 #body
166 }
167 #logging_init
168 let span = tracing::info_span!(stringify!(#test_name));
169 #enter_
170 let result = test_impl()#await_;
171 if let Err(e) = &result {
172 tracing::error!(error = ?e, "test failed");
173 }
174 #exit_
175 if let Some(guard) = __guard { guard.teardown() } ;
176 result
177 }
178 };
179
180 result.into()
181}