tracing_test_macro/
lib.rs1extern crate proc_macro;
10
11use std::sync::{Mutex, OnceLock};
12
13use proc_macro::TokenStream;
14use quote::{quote, ToTokens};
15use syn::{parse, ItemFn, Stmt};
16
17fn registered_scopes() -> &'static Mutex<Vec<String>> {
25 static REGISTERED_SCOPES: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
26 REGISTERED_SCOPES.get_or_init(|| Mutex::new(vec![]))
27}
28
29fn get_free_scope(mut test_fn_name: String) -> String {
32 let mut vec = registered_scopes().lock().unwrap();
33 let mut counter = 1;
34 let len = test_fn_name.len();
35 while vec.contains(&test_fn_name) {
36 counter += 1;
37 test_fn_name.replace_range(len.., &counter.to_string());
38 }
39 vec.push(test_fn_name.clone());
40 test_fn_name
41}
42
43#[proc_macro_attribute]
52pub fn traced_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
53 let mut function: ItemFn = parse(item).expect("Could not parse ItemFn");
55
56 let scope = get_free_scope(function.sig.ident.to_string());
58
59 let no_env_filter = cfg!(feature = "no-env-filter");
65
66 let init = parse::<Stmt>(
68 quote! {
69 tracing_test::internal::INITIALIZED.call_once(|| {
70 let env_filter = if #no_env_filter {
71 "trace".to_string()
72 } else {
73 let crate_name = module_path!()
74 .split(":")
75 .next()
76 .expect("Could not find crate name in module path")
77 .to_string();
78 format!("{}=trace", crate_name)
79 };
80 let mock_writer = tracing_test::internal::MockWriter::new(&tracing_test::internal::global_buf());
81 let subscriber = tracing_test::internal::get_subscriber(mock_writer, &env_filter);
82 tracing::dispatcher::set_global_default(subscriber)
83 .expect("Could not set global tracing subscriber");
84 });
85 }
86 .into(),
87 )
88 .expect("Could not parse quoted statement init");
89 let span = parse::<Stmt>(
90 quote! {
91 let span = tracing::info_span!(#scope);
92 }
93 .into(),
94 )
95 .expect("Could not parse quoted statement span");
96 let enter = parse::<Stmt>(
97 quote! {
98 let _enter = span.enter();
99 }
100 .into(),
101 )
102 .expect("Could not parse quoted statement enter");
103 let logs_contain_fn = parse::<Stmt>(
104 quote! {
105 fn logs_contain(val: &str) -> bool {
106 tracing_test::internal::logs_with_scope_contain(#scope, val)
107 }
108
109 }
110 .into(),
111 )
112 .expect("Could not parse quoted statement logs_contain_fn");
113 let logs_assert_fn = parse::<Stmt>(
114 quote! {
115 fn logs_assert(f: impl Fn(&[&str]) -> std::result::Result<(), String>) {
119 match tracing_test::internal::logs_assert(#scope, f) {
120 Ok(()) => {},
121 Err(msg) => panic!("The logs_assert function returned an error: {}", msg),
122 };
123 }
124 }
125 .into(),
126 )
127 .expect("Could not parse quoted statement logs_assert_fn");
128
129 function.block.stmts.insert(0, init);
131 function.block.stmts.insert(1, span);
132 function.block.stmts.insert(2, enter);
133 function.block.stmts.insert(3, logs_contain_fn);
134 function.block.stmts.insert(4, logs_assert_fn);
135
136 TokenStream::from(function.to_token_stream())
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_get_free_scope() {
146 let initial = get_free_scope("test_fn_name".to_string());
147 assert_eq!(initial, "test_fn_name");
148
149 let second = get_free_scope("test_fn_name".to_string());
150 assert_eq!(second, "test_fn_name2");
151 let third = get_free_scope("test_fn_name".to_string());
152 assert_eq!(third, "test_fn_name3");
153
154 let fourth = get_free_scope("test_fn_name4".to_string());
156 assert_eq!(fourth, "test_fn_name4");
157
158 let fifth = get_free_scope("test_fn_name5".to_string());
159 assert_eq!(fifth, "test_fn_name5");
160 }
161}