1use std::{fs::metadata, path::Path};
53
54#[cfg(feature = "icmp")]
55use std::net::IpAddr;
56use std::net::TcpStream;
57
58use proc_macro::TokenStream;
59use proc_macro_error2::abort_call_site;
60use proc_macro_error2::proc_macro_error;
61use syn::{parse_macro_input, ItemFn, ItemMod};
62
63#[cfg(feature = "runtime")]
64use syn::{Item, ItemStruct, ItemType};
65
66#[cfg(feature = "executable")]
67use which::which;
68
69use crate::utils::{fn_macro, is_module, lock_macro, mod_macro, sanitize_env_vars_attr};
70
71mod utils;
72
73#[proc_macro_attribute]
113#[proc_macro_error]
114pub fn env(attr: TokenStream, stream: TokenStream) -> TokenStream {
115 if is_module(&stream) {
116 mod_macro(
117 attr,
118 parse_macro_input!(stream as ItemMod),
119 check_env_condition,
120 )
121 } else {
122 fn_macro(
123 attr,
124 parse_macro_input!(stream as ItemFn),
125 check_env_condition,
126 )
127 }
128}
129
130fn check_env_condition(attr_str: String) -> (bool, String) {
131 let var_names = sanitize_env_vars_attr(&attr_str);
132
133 let mut missing_vars = vec![];
135 for name in var_names {
136 if std::env::var(name).is_err() {
137 missing_vars.push(name.to_string());
138 }
139 }
140
141 let ignore_msg = if missing_vars.is_empty() {
143 String::new()
144 } else if missing_vars.len() == 1 {
145 format!("because variable {} not found", missing_vars[0])
146 } else {
147 format!(
148 "because following variables not found:\n{}\n",
149 missing_vars.join(", ")
150 )
151 };
152
153 (missing_vars.is_empty(), ignore_msg)
154}
155
156#[cfg(not(feature = "runtime"))]
169#[proc_macro_attribute]
170#[proc_macro_error]
171pub fn runtime_env(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
172 panic!("should be used with runtime feature")
173}
174#[cfg(feature = "runtime")]
175#[proc_macro_attribute]
176#[proc_macro_error]
177pub fn runtime_env(attr: TokenStream, stream: TokenStream) -> TokenStream {
178 let attr_str = attr.to_string().replace(' ', "");
179 let var_names: Vec<&str> = attr_str.split(',').collect();
180 let ItemFn {
181 attrs,
182 vis,
183 sig,
184 block,
185 } = parse_macro_input!(stream as ItemFn);
186 let syn::Signature { ident, .. } = sig.clone();
187 let check_ident = syn::Ident::new(
188 &format!("_check_{}", ident.to_string()),
189 proc_macro2::Span::call_site(),
190 );
191 quote::quote! {
192 fn #check_ident() -> Result<(), libtest_with::Failed> {
193 let mut missing_vars = vec![];
194 #(
195 if std::env::var(#var_names).is_err() {
196 missing_vars.push(#var_names);
197 }
198 )*
199 match missing_vars.len() {
200 0 => {
201 #ident();
202 Ok(())
203 },
204 1 => Err(
205 format!("{}because variable {} not found",
206 libtest_with::RUNTIME_IGNORE_PREFIX, missing_vars[0]
207 ).into()),
208 _ => Err(
209 format!("{}because following variables not found:\n{}\n",
210 libtest_with::RUNTIME_IGNORE_PREFIX, missing_vars.join(", ")
211 ).into()),
212 }
213 }
214
215 #(#attrs)*
216 #vis #sig #block
217 }
218 .into()
219}
220
221#[proc_macro_attribute]
234#[proc_macro_error]
235pub fn no_env(attr: TokenStream, stream: TokenStream) -> TokenStream {
236 if is_module(&stream) {
237 mod_macro(
238 attr,
239 parse_macro_input!(stream as ItemMod),
240 check_no_env_condition,
241 )
242 } else {
243 fn_macro(
244 attr,
245 parse_macro_input!(stream as ItemFn),
246 check_no_env_condition,
247 )
248 }
249}
250
251fn check_no_env_condition(attr_str: String) -> (bool, String) {
252 let var_names = sanitize_env_vars_attr(&attr_str);
253
254 let mut found_vars = vec![];
256 for name in var_names {
257 if std::env::var(name).is_ok() {
258 found_vars.push(name.to_string());
259 }
260 }
261
262 let ignore_msg = if found_vars.is_empty() {
264 String::new()
265 } else if found_vars.len() == 1 {
266 format!("because variable {} was found", found_vars[0])
267 } else {
268 format!(
269 "because following variables were found:\n{}\n",
270 found_vars.join(", ")
271 )
272 };
273
274 (found_vars.is_empty(), ignore_msg)
275}
276
277#[cfg(not(feature = "runtime"))]
290#[proc_macro_attribute]
291#[proc_macro_error]
292pub fn runtime_no_env(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
293 panic!("should be used with runtime feature")
294}
295#[cfg(feature = "runtime")]
296#[proc_macro_attribute]
297#[proc_macro_error]
298pub fn runtime_no_env(attr: TokenStream, stream: TokenStream) -> TokenStream {
299 let attr_str = attr.to_string().replace(' ', "");
300 let var_names: Vec<&str> = attr_str.split(',').collect();
301 let ItemFn {
302 attrs,
303 vis,
304 sig,
305 block,
306 } = parse_macro_input!(stream as ItemFn);
307 let syn::Signature { ident, .. } = sig.clone();
308 let check_ident = syn::Ident::new(
309 &format!("_check_{}", ident.to_string()),
310 proc_macro2::Span::call_site(),
311 );
312 quote::quote! {
313 fn #check_ident() -> Result<(), libtest_with::Failed> {
314 let mut should_no_exist_vars = vec![];
315 #(
316 if std::env::var(#var_names).is_ok() {
317 should_no_exist_vars.push(#var_names);
318 }
319 )*
320 match should_no_exist_vars.len() {
321 0 => {
322 #ident();
323 Ok(())
324 },
325 1 => Err(
326 format!("{}because variable {} found",
327 libtest_with::RUNTIME_IGNORE_PREFIX, should_no_exist_vars[0]
328 ).into()),
329 _ => Err(
330 format!("{}because following variables found:\n{}\n",
331 libtest_with::RUNTIME_IGNORE_PREFIX, should_no_exist_vars.join(", ")
332 ).into()),
333 }
334 }
335
336 #(#attrs)*
337 #vis #sig #block
338 }
339 .into()
340}
341
342#[proc_macro_attribute]
370#[proc_macro_error]
371pub fn file(attr: TokenStream, stream: TokenStream) -> TokenStream {
372 if is_module(&stream) {
373 mod_macro(
374 attr,
375 parse_macro_input!(stream as ItemMod),
376 check_file_condition,
377 )
378 } else {
379 fn_macro(
380 attr,
381 parse_macro_input!(stream as ItemFn),
382 check_file_condition,
383 )
384 }
385}
386
387fn check_file_condition(attr_str: String) -> (bool, String) {
388 let files: Vec<&str> = attr_str.split(',').collect();
389 let mut missing_files = vec![];
390 for file in files.iter() {
391 if !Path::new(file.trim_matches('"')).is_file() {
392 missing_files.push(file.to_string());
393 }
394 }
395 let ignore_msg = if missing_files.len() == 1 {
396 format!("because file not found: {}", missing_files[0])
397 } else {
398 format!(
399 "because following files not found: \n{}\n",
400 missing_files.join("\n")
401 )
402 };
403 (missing_files.is_empty(), ignore_msg)
404}
405
406#[cfg(not(feature = "runtime"))]
419#[proc_macro_attribute]
420#[proc_macro_error]
421pub fn runtime_file(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
422 panic!("should be used with runtime feature")
423}
424#[cfg(feature = "runtime")]
425#[proc_macro_attribute]
426#[proc_macro_error]
427pub fn runtime_file(attr: TokenStream, stream: TokenStream) -> TokenStream {
428 let attr_str = attr.to_string().replace(' ', "");
429 let files: Vec<&str> = attr_str.split(',').collect();
430 let ItemFn {
431 attrs,
432 vis,
433 sig,
434 block,
435 } = parse_macro_input!(stream as ItemFn);
436 let syn::Signature { ident, .. } = sig.clone();
437 let check_ident = syn::Ident::new(
438 &format!("_check_{}", ident.to_string()),
439 proc_macro2::Span::call_site(),
440 );
441 quote::quote! {
442 fn #check_ident() -> Result<(), libtest_with::Failed> {
443 let mut missing_files = vec![];
444 #(
445 if !std::path::Path::new(#files.trim_matches('"')).is_file() {
446 missing_files.push(#files);
447 }
448 )*
449
450 match missing_files.len() {
451 0 => {
452 #ident();
453 Ok(())
454 },
455 1 => Err(
456 format!("{}because file not found: {}",
457 libtest_with::RUNTIME_IGNORE_PREFIX, missing_files[0]
458 ).into()),
459 _ => Err(
460 format!("{}because following files not found: \n{}\n",
461 libtest_with::RUNTIME_IGNORE_PREFIX, missing_files.join(", ")
462 ).into()),
463 }
464 }
465
466 #(#attrs)*
467 #vis #sig #block
468 }
469 .into()
470}
471
472#[proc_macro_attribute]
500#[proc_macro_error]
501pub fn path(attr: TokenStream, stream: TokenStream) -> TokenStream {
502 if is_module(&stream) {
503 mod_macro(
504 attr,
505 parse_macro_input!(stream as ItemMod),
506 check_path_condition,
507 )
508 } else {
509 fn_macro(
510 attr,
511 parse_macro_input!(stream as ItemFn),
512 check_path_condition,
513 )
514 }
515}
516
517fn check_path_condition(attr_str: String) -> (bool, String) {
518 let paths: Vec<&str> = attr_str.split(',').collect();
519 let mut missing_paths = vec![];
520 for path in paths.iter() {
521 if metadata(path.trim_matches('"')).is_err() {
522 missing_paths.push(path.to_string());
523 }
524 }
525 let ignore_msg = if missing_paths.len() == 1 {
526 format!("because path not found: {}", missing_paths[0])
527 } else {
528 format!(
529 "because following paths not found: \n{}\n",
530 missing_paths.join("\n")
531 )
532 };
533 (missing_paths.is_empty(), ignore_msg)
534}
535
536#[cfg(not(feature = "runtime"))]
549#[proc_macro_attribute]
550#[proc_macro_error]
551pub fn runtime_path(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
552 panic!("should be used with runtime feature")
553}
554#[cfg(feature = "runtime")]
555#[proc_macro_attribute]
556#[proc_macro_error]
557pub fn runtime_path(attr: TokenStream, stream: TokenStream) -> TokenStream {
558 let attr_str = attr.to_string().replace(' ', "");
559 let paths: Vec<&str> = attr_str.split(',').collect();
560 let ItemFn {
561 attrs,
562 vis,
563 sig,
564 block,
565 } = parse_macro_input!(stream as ItemFn);
566 let syn::Signature { ident, .. } = sig.clone();
567 let check_ident = syn::Ident::new(
568 &format!("_check_{}", ident.to_string()),
569 proc_macro2::Span::call_site(),
570 );
571 quote::quote! {
572 fn #check_ident() -> Result<(), libtest_with::Failed> {
573 let mut missing_paths = vec![];
574 #(
575 if std::fs::metadata(#paths.trim_matches('"')).is_err() {
576 missing_paths.push(#paths.to_string());
577 }
578 )*
579
580 match missing_paths.len() {
581 0 => {
582 #ident();
583 Ok(())
584 },
585 1 => Err(
586 format!("{}because path not found: {}",
587 libtest_with::RUNTIME_IGNORE_PREFIX, missing_paths[0]
588 ).into()),
589 _ => Err(
590 format!("{}because following paths not found: \n{}\n",
591 libtest_with::RUNTIME_IGNORE_PREFIX, missing_paths.join(", ")
592 ).into()),
593 }
594 }
595
596 #(#attrs)*
597 #vis #sig #block
598 }
599 .into()
600}
601
602#[proc_macro_attribute]
623#[proc_macro_error]
624#[cfg(feature = "http")]
625pub fn http(attr: TokenStream, stream: TokenStream) -> TokenStream {
626 if is_module(&stream) {
627 mod_macro(
628 attr,
629 parse_macro_input!(stream as ItemMod),
630 check_http_condition,
631 )
632 } else {
633 fn_macro(
634 attr,
635 parse_macro_input!(stream as ItemFn),
636 check_http_condition,
637 )
638 }
639}
640
641#[cfg(feature = "http")]
642fn check_http_condition(attr_str: String) -> (bool, String) {
643 let links: Vec<&str> = attr_str.split(',').collect();
644 let mut missing_links = vec![];
645 let client = reqwest::blocking::Client::new();
646 for link in links.iter() {
647 if client.head(&format!("http://{}", link)).send().is_err() {
648 missing_links.push(format!("http://{link:}"));
649 }
650 }
651 let ignore_msg = if missing_links.len() == 1 {
652 format!("because {} not response", missing_links[0])
653 } else {
654 format!(
655 "because following links not response: \n{}\n",
656 missing_links.join("\n")
657 )
658 };
659 (missing_links.is_empty(), ignore_msg)
660}
661
662#[cfg(not(feature = "runtime"))]
674#[proc_macro_attribute]
675#[proc_macro_error]
676pub fn runtime_http(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
677 panic!("should be used with runtime feature")
678}
679
680#[cfg(all(feature = "runtime", feature = "http"))]
681#[proc_macro_attribute]
682#[proc_macro_error]
683pub fn runtime_http(attr: TokenStream, stream: TokenStream) -> TokenStream {
684 let attr_str = attr.to_string().replace(' ', "");
685 let links: Vec<&str> = attr_str.split(',').collect();
686 let ItemFn {
687 attrs,
688 vis,
689 sig,
690 block,
691 } = parse_macro_input!(stream as ItemFn);
692 let syn::Signature { ident, .. } = sig.clone();
693 let check_ident = syn::Ident::new(
694 &format!("_check_{}", ident.to_string()),
695 proc_macro2::Span::call_site(),
696 );
697 quote::quote! {
698 fn #check_ident() -> Result<(), libtest_with::Failed> {
699
700 let mut missing_links = vec![];
701 let client = libtest_with::reqwest::blocking::Client::new();
702 #(
703 if client.head(&format!("http://{}", #links)).send().is_err() {
704 missing_links.push(format!("http://{}", #links));
705 }
706 )*
707 match missing_links.len() {
708 0 => {
709 #ident();
710 Ok(())
711 },
712 1 => Err(
713 format!("{}because {} not response",
714 libtest_with::RUNTIME_IGNORE_PREFIX, missing_links[0]
715 ).into()),
716 _ => Err(
717 format!("{}because following links not response: \n{}\n",
718 libtest_with::RUNTIME_IGNORE_PREFIX, missing_links.join(", ")
719 ).into()),
720 }
721 }
722
723 #(#attrs)*
724 #vis #sig #block
725 }
726 .into()
727}
728
729#[proc_macro_attribute]
750#[proc_macro_error]
751#[cfg(feature = "http")]
752pub fn https(attr: TokenStream, stream: TokenStream) -> TokenStream {
753 if is_module(&stream) {
754 mod_macro(
755 attr,
756 parse_macro_input!(stream as ItemMod),
757 check_https_condition,
758 )
759 } else {
760 fn_macro(
761 attr,
762 parse_macro_input!(stream as ItemFn),
763 check_https_condition,
764 )
765 }
766}
767
768#[cfg(feature = "http")]
769fn check_https_condition(attr_str: String) -> (bool, String) {
770 let links: Vec<&str> = attr_str.split(',').collect();
771 let mut missing_links = vec![];
772 let client = reqwest::blocking::Client::new();
773 for link in links.iter() {
774 if client.head(&format!("https://{}", link)).send().is_err() {
775 missing_links.push(format!("https://{link:}"));
776 }
777 }
778 let ignore_msg = if missing_links.len() == 1 {
779 format!("because {} not response", missing_links[0])
780 } else {
781 format!(
782 "because following links not response: \n{}\n",
783 missing_links.join("\n")
784 )
785 };
786 (missing_links.is_empty(), ignore_msg)
787}
788
789#[cfg(not(feature = "runtime"))]
801#[proc_macro_attribute]
802#[proc_macro_error]
803pub fn runtime_https(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
804 panic!("should be used with runtime feature")
805}
806
807#[cfg(all(feature = "runtime", feature = "http"))]
808#[proc_macro_attribute]
809#[proc_macro_error]
810pub fn runtime_https(attr: TokenStream, stream: TokenStream) -> TokenStream {
811 let attr_str = attr.to_string().replace(' ', "");
812 let links: Vec<&str> = attr_str.split(',').collect();
813 let ItemFn {
814 attrs,
815 vis,
816 sig,
817 block,
818 } = parse_macro_input!(stream as ItemFn);
819 let syn::Signature { ident, .. } = sig.clone();
820 let check_ident = syn::Ident::new(
821 &format!("_check_{}", ident.to_string()),
822 proc_macro2::Span::call_site(),
823 );
824 quote::quote! {
825 fn #check_ident() -> Result<(), libtest_with::Failed> {
826
827 let mut missing_links = vec![];
828 let client = libtest_with::reqwest::blocking::Client::new();
829 #(
830 if client.head(&format!("https://{}", #links)).send().is_err() {
831 missing_links.push(format!("https://{}", #links));
832 }
833 )*
834 match missing_links.len() {
835 0 => {
836 #ident();
837 Ok(())
838 },
839 1 => Err(
840 format!("{}because {} not response",
841 libtest_with::RUNTIME_IGNORE_PREFIX, missing_links[0]
842 ).into()),
843 _ => Err(
844 format!("{}because following links not response: \n{}\n",
845 libtest_with::RUNTIME_IGNORE_PREFIX, missing_links.join(", ")
846 ).into()),
847 }
848 }
849
850 #(#attrs)*
851 #vis #sig #block
852 }
853 .into()
854}
855
856#[proc_macro_attribute]
879#[proc_macro_error]
880#[cfg(feature = "icmp")]
881pub fn icmp(attr: TokenStream, stream: TokenStream) -> TokenStream {
882 if is_module(&stream) {
883 mod_macro(
884 attr,
885 parse_macro_input!(stream as ItemMod),
886 check_icmp_condition,
887 )
888 } else {
889 fn_macro(
890 attr,
891 parse_macro_input!(stream as ItemFn),
892 check_icmp_condition,
893 )
894 }
895}
896
897#[cfg(feature = "icmp")]
898fn check_icmp_condition(attr_str: String) -> (bool, String) {
899 let ips: Vec<&str> = attr_str.split(',').collect();
900 let mut missing_ips = vec![];
901 for ip in ips.iter() {
902 if let Ok(addr) = ip.parse::<IpAddr>() {
903 if ping::ping(addr, None, None, None, None, None).is_err() {
904 missing_ips.push(ip.to_string());
905 }
906 } else {
907 abort_call_site!("ip address malformat")
908 }
909 }
910 let ignore_msg = if missing_ips.len() == 1 {
911 format!("because ip {} not response", missing_ips[0])
912 } else {
913 format!(
914 "because following ip not response: \n{}\n",
915 missing_ips.join(", ")
916 )
917 };
918 (missing_ips.is_empty(), ignore_msg)
919}
920
921#[cfg(not(feature = "runtime"))]
935#[proc_macro_attribute]
936#[proc_macro_error]
937pub fn runtime_icmp(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
938 panic!("should be used with runtime feature")
939}
940
941#[cfg(all(feature = "runtime", feature = "icmp"))]
942#[proc_macro_attribute]
943#[proc_macro_error]
944pub fn runtime_icmp(attr: TokenStream, stream: TokenStream) -> TokenStream {
945 let attr_str = attr.to_string().replace(' ', "");
946 let ips: Vec<&str> = attr_str.split(',').collect();
947 let ItemFn {
948 attrs,
949 vis,
950 sig,
951 block,
952 } = parse_macro_input!(stream as ItemFn);
953 let syn::Signature { ident, .. } = sig.clone();
954 let check_ident = syn::Ident::new(
955 &format!("_check_{}", ident.to_string()),
956 proc_macro2::Span::call_site(),
957 );
958 quote::quote! {
959 fn #check_ident() -> Result<(), libtest_with::Failed> {
960
961 let mut missing_ips = vec![];
962 #(
963 if libtest_with::ping::ping(#ips.parse().expect("ip address is invalid"), None, None, None, None, None).is_err() {
964 missing_ips.push(#ips);
965 }
966 )*
967 match missing_ips.len() {
968 0 => {
969 #ident();
970 Ok(())
971 }
972 ,
973 1 => Err(
974 format!("{}because {} not response",
975 libtest_with::RUNTIME_IGNORE_PREFIX, missing_ips[0]
976 ).into()),
977 _ => Err(
978 format!("{}because following ips not response: \n{}\n",
979 libtest_with::RUNTIME_IGNORE_PREFIX, missing_ips.join(", ")
980 ).into()),
981 }
982 }
983
984 #(#attrs)*
985 #vis #sig #block
986 }
987 .into()
988}
989
990#[proc_macro_attribute]
1012#[proc_macro_error]
1013pub fn tcp(attr: TokenStream, stream: TokenStream) -> TokenStream {
1014 if is_module(&stream) {
1015 mod_macro(
1016 attr,
1017 parse_macro_input!(stream as ItemMod),
1018 check_tcp_condition,
1019 )
1020 } else {
1021 fn_macro(
1022 attr,
1023 parse_macro_input!(stream as ItemFn),
1024 check_tcp_condition,
1025 )
1026 }
1027}
1028
1029fn check_tcp_condition(attr_str: String) -> (bool, String) {
1030 let sockets: Vec<&str> = attr_str.split(',').collect();
1031 let mut missing_sockets = vec![];
1032 for socket in sockets.iter() {
1033 if TcpStream::connect(socket).is_err() {
1034 missing_sockets.push(socket.to_string());
1035 }
1036 }
1037 let ignore_msg = if missing_sockets.len() == 1 {
1038 format!("because fail to connect socket {}", missing_sockets[0])
1039 } else {
1040 format!(
1041 "because follow sockets can not connect\n{}\n",
1042 missing_sockets.join(", ")
1043 )
1044 };
1045 (missing_sockets.is_empty(), ignore_msg)
1046}
1047
1048#[cfg(not(feature = "runtime"))]
1061#[proc_macro_attribute]
1062#[proc_macro_error]
1063pub fn runtime_tcp(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1064 panic!("should be used with runtime feature")
1065}
1066
1067#[cfg(feature = "runtime")]
1068#[proc_macro_attribute]
1069#[proc_macro_error]
1070pub fn runtime_tcp(attr: TokenStream, stream: TokenStream) -> TokenStream {
1071 let attr_str = attr.to_string().replace(' ', "");
1072 let sockets: Vec<&str> = attr_str.split(',').collect();
1073 let ItemFn {
1074 attrs,
1075 vis,
1076 sig,
1077 block,
1078 } = parse_macro_input!(stream as ItemFn);
1079 let syn::Signature { ident, .. } = sig.clone();
1080 let check_ident = syn::Ident::new(
1081 &format!("_check_{}", ident.to_string()),
1082 proc_macro2::Span::call_site(),
1083 );
1084 quote::quote! {
1085 fn #check_ident() -> Result<(), libtest_with::Failed> {
1086
1087 let mut missing_sockets = vec![];
1088 #(
1089 if std::net::TcpStream::connect(#sockets).is_err() {
1090 missing_sockets.push(#sockets);
1091 }
1092 )*
1093 match missing_sockets.len() {
1094 0 => {
1095 #ident();
1096 Ok(())
1097 },
1098 1 => Err(
1099 format!("{}because {} not response",
1100 libtest_with::RUNTIME_IGNORE_PREFIX, missing_sockets[0]
1101 ).into()),
1102 _ => Err(
1103 format!("{}because following sockets not response: \n{}\n",
1104 libtest_with::RUNTIME_IGNORE_PREFIX, missing_sockets.join(", ")
1105 ).into()),
1106 }
1107 }
1108
1109 #(#attrs)*
1110 #vis #sig #block
1111 }
1112 .into()
1113}
1114
1115#[proc_macro_attribute]
1130#[proc_macro_error]
1131#[cfg(all(feature = "user", not(target_os = "windows")))]
1132pub fn root(attr: TokenStream, stream: TokenStream) -> TokenStream {
1133 if is_module(&stream) {
1134 mod_macro(
1135 attr,
1136 parse_macro_input!(stream as ItemMod),
1137 check_root_condition,
1138 )
1139 } else {
1140 fn_macro(
1141 attr,
1142 parse_macro_input!(stream as ItemFn),
1143 check_root_condition,
1144 )
1145 }
1146}
1147
1148#[cfg(all(feature = "user", not(target_os = "windows")))]
1149fn check_root_condition(_attr_str: String) -> (bool, String) {
1150 let current_user_id = uzers::get_current_uid();
1151 (
1152 current_user_id == 0,
1153 "because this case should run with root".into(),
1154 )
1155}
1156
1157#[cfg(not(feature = "runtime"))]
1170#[proc_macro_attribute]
1171#[proc_macro_error]
1172pub fn runtime_root(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1173 panic!("should be used with runtime feature")
1174}
1175#[cfg(all(feature = "runtime", feature = "user", not(target_os = "windows")))]
1176#[proc_macro_attribute]
1177#[proc_macro_error]
1178pub fn runtime_root(_attr: TokenStream, stream: TokenStream) -> TokenStream {
1179 let ItemFn {
1180 attrs,
1181 vis,
1182 sig,
1183 block,
1184 } = parse_macro_input!(stream as ItemFn);
1185 let syn::Signature { ident, .. } = sig.clone();
1186 let check_ident = syn::Ident::new(
1187 &format!("_check_{}", ident.to_string()),
1188 proc_macro2::Span::call_site(),
1189 );
1190 quote::quote! {
1191 fn #check_ident() -> Result<(), libtest_with::Failed> {
1192 if 0 == libtest_with::uzers::get_current_uid() {
1193 #ident();
1194 Ok(())
1195 } else {
1196 Err(format!("{}because this case should run with root", libtest_with::RUNTIME_IGNORE_PREFIX).into())
1197 }
1198 }
1199
1200 #(#attrs)*
1201 #vis #sig #block
1202 }
1203 .into()
1204}
1205
1206#[proc_macro_attribute]
1221#[proc_macro_error]
1222#[cfg(all(feature = "user", not(target_os = "windows")))]
1223pub fn group(attr: TokenStream, stream: TokenStream) -> TokenStream {
1224 if is_module(&stream) {
1225 mod_macro(
1226 attr,
1227 parse_macro_input!(stream as ItemMod),
1228 check_group_condition,
1229 )
1230 } else {
1231 fn_macro(
1232 attr,
1233 parse_macro_input!(stream as ItemFn),
1234 check_group_condition,
1235 )
1236 }
1237}
1238
1239#[cfg(feature = "user")]
1240#[cfg(all(feature = "user", not(target_os = "windows")))]
1241fn check_group_condition(group_name: String) -> (bool, String) {
1242 let current_user_id = uzers::get_current_uid();
1243
1244 let in_group = match uzers::get_user_by_uid(current_user_id) {
1245 Some(user) => {
1246 let mut in_group = false;
1247 for group in user.groups().expect("user not found") {
1248 if in_group {
1249 break;
1250 }
1251 in_group |= group.name().to_string_lossy() == group_name;
1252 }
1253 in_group
1254 }
1255 None => false,
1256 };
1257 (
1258 in_group,
1259 format!("because this case should run user in group {}", group_name),
1260 )
1261}
1262
1263#[cfg(not(feature = "runtime"))]
1276#[proc_macro_attribute]
1277#[proc_macro_error]
1278pub fn runtime_group(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1279 panic!("should be used with runtime feature")
1280}
1281#[cfg(all(feature = "runtime", feature = "user", not(target_os = "windows")))]
1282#[proc_macro_attribute]
1283#[proc_macro_error]
1284pub fn runtime_group(attr: TokenStream, stream: TokenStream) -> TokenStream {
1285 let group_name = attr.to_string().replace(' ', "");
1286 let ItemFn {
1287 attrs,
1288 vis,
1289 sig,
1290 block,
1291 } = parse_macro_input!(stream as ItemFn);
1292 let syn::Signature { ident, .. } = sig.clone();
1293 let check_ident = syn::Ident::new(
1294 &format!("_check_{}", ident.to_string()),
1295 proc_macro2::Span::call_site(),
1296 );
1297
1298 quote::quote! {
1299
1300 fn #check_ident() -> Result<(), libtest_with::Failed> {
1301 let current_user_id = libtest_with::uzers::get_current_uid();
1302 let in_group = match libtest_with::uzers::get_user_by_uid(current_user_id) {
1303 Some(user) => {
1304 let mut in_group = false;
1305 for group in user.groups().expect("user not found") {
1306 if in_group {
1307 break;
1308 }
1309 in_group |= group.name().to_string_lossy() == #group_name;
1310 }
1311 in_group
1312 }
1313 None => false,
1314 };
1315
1316 if in_group {
1317 #ident();
1318 Ok(())
1319 } else {
1320 Err(format!("{}because this case should run user in group {}",
1321 libtest_with::RUNTIME_IGNORE_PREFIX, #group_name).into())
1322 }
1323 }
1324
1325 #(#attrs)*
1326 #vis #sig #block
1327 }
1328 .into()
1329}
1330
1331#[proc_macro_attribute]
1346#[proc_macro_error]
1347#[cfg(all(feature = "user", not(target_os = "windows")))]
1348pub fn user(attr: TokenStream, stream: TokenStream) -> TokenStream {
1349 if is_module(&stream) {
1350 mod_macro(
1351 attr,
1352 parse_macro_input!(stream as ItemMod),
1353 check_user_condition,
1354 )
1355 } else {
1356 fn_macro(
1357 attr,
1358 parse_macro_input!(stream as ItemFn),
1359 check_user_condition,
1360 )
1361 }
1362}
1363
1364#[cfg(feature = "user")]
1365#[cfg(all(feature = "user", not(target_os = "windows")))]
1366fn check_user_condition(user_name: String) -> (bool, String) {
1367 let is_user = match uzers::get_current_username() {
1368 Some(uname) => uname.to_string_lossy() == user_name,
1369 None => false,
1370 };
1371 (
1372 is_user,
1373 format!("because this case should run with user {}", user_name),
1374 )
1375}
1376
1377#[cfg(not(feature = "runtime"))]
1390#[proc_macro_attribute]
1391#[proc_macro_error]
1392pub fn runtime_user(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1393 panic!("should be used with runtime feature")
1394}
1395#[cfg(all(feature = "runtime", feature = "user", not(target_os = "windows")))]
1396#[proc_macro_attribute]
1397#[proc_macro_error]
1398pub fn runtime_user(attr: TokenStream, stream: TokenStream) -> TokenStream {
1399 let user_name = attr.to_string().replace(' ', "");
1400 let ItemFn {
1401 attrs,
1402 vis,
1403 sig,
1404 block,
1405 } = parse_macro_input!(stream as ItemFn);
1406 let syn::Signature { ident, .. } = sig.clone();
1407 let check_ident = syn::Ident::new(
1408 &format!("_check_{}", ident.to_string()),
1409 proc_macro2::Span::call_site(),
1410 );
1411
1412 quote::quote! {
1413
1414 fn #check_ident() -> Result<(), libtest_with::Failed> {
1415 let is_user = match libtest_with::uzers::get_current_username() {
1416 Some(uname) => uname.to_string_lossy() == #user_name,
1417 None => false,
1418 };
1419
1420 if is_user {
1421 #ident();
1422 Ok(())
1423 } else {
1424 Err(format!("{}because this case should run with user {}",
1425 libtest_with::RUNTIME_IGNORE_PREFIX, #user_name).into())
1426 }
1427 }
1428
1429 #(#attrs)*
1430 #vis #sig #block
1431 }
1432 .into()
1433}
1434
1435#[proc_macro_attribute]
1450#[proc_macro_error]
1451#[cfg(feature = "resource")]
1452pub fn mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
1453 if is_module(&stream) {
1454 mod_macro(
1455 attr,
1456 parse_macro_input!(stream as ItemMod),
1457 check_mem_condition,
1458 )
1459 } else {
1460 fn_macro(
1461 attr,
1462 parse_macro_input!(stream as ItemFn),
1463 check_mem_condition,
1464 )
1465 }
1466}
1467
1468#[cfg(feature = "resource")]
1469fn check_mem_condition(mem_size_str: String) -> (bool, String) {
1470 let sys = sysinfo::System::new_with_specifics(
1471 sysinfo::RefreshKind::nothing()
1472 .with_memory(sysinfo::MemoryRefreshKind::nothing().with_swap()),
1473 );
1474 let mem_size = match byte_unit::Byte::parse_str(format!("{} B", sys.total_memory()), false) {
1475 Ok(b) => b,
1476 Err(_) => abort_call_site!("memory size description is not correct"),
1477 };
1478 let mem_size_limitation = match byte_unit::Byte::parse_str(&mem_size_str, true) {
1479 Ok(b) => b,
1480 Err(_) => abort_call_site!("system memory size can not get"),
1481 };
1482 (
1483 mem_size >= mem_size_limitation,
1484 format!("because the memory less than {}", mem_size_str),
1485 )
1486}
1487
1488#[cfg(not(feature = "runtime"))]
1501#[proc_macro_attribute]
1502#[proc_macro_error]
1503pub fn runtime_mem(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1504 panic!("should be used with runtime feature")
1505}
1506#[cfg(all(feature = "runtime", feature = "resource"))]
1507#[proc_macro_attribute]
1508#[proc_macro_error]
1509pub fn runtime_mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
1510 let mem_limitation_str = attr.to_string().replace(' ', "");
1511 if byte_unit::Byte::parse_str(&mem_limitation_str, true).is_err() {
1512 abort_call_site!("memory size description is not correct")
1513 }
1514
1515 let ItemFn {
1516 attrs,
1517 vis,
1518 sig,
1519 block,
1520 } = parse_macro_input!(stream as ItemFn);
1521 let syn::Signature { ident, .. } = sig.clone();
1522 let check_ident = syn::Ident::new(
1523 &format!("_check_{}", ident.to_string()),
1524 proc_macro2::Span::call_site(),
1525 );
1526
1527 quote::quote! {
1528 fn #check_ident() -> Result<(), libtest_with::Failed> {
1529 let sys = libtest_with::sysinfo::System::new_with_specifics(
1530 libtest_with::sysinfo::RefreshKind::nothing().with_memory(libtest_with::sysinfo::MemoryRefreshKind::nothing().with_ram()),
1531 );
1532 let mem_size = match libtest_with::byte_unit::Byte::parse_str(format!("{} B", sys.total_memory()), false) {
1533 Ok(b) => b,
1534 Err(_) => panic!("system memory size can not get"),
1535 };
1536 let mem_size_limitation = libtest_with::byte_unit::Byte::parse_str(#mem_limitation_str, true).expect("mem limitation should correct");
1537 if mem_size >= mem_size_limitation {
1538 #ident();
1539 Ok(())
1540 } else {
1541 Err(format!("{}because the memory less than {}",
1542 libtest_with::RUNTIME_IGNORE_PREFIX, #mem_limitation_str).into())
1543 }
1544 }
1545
1546 #(#attrs)*
1547 #vis #sig #block
1548
1549 }
1550 .into()
1551}
1552
1553#[cfg(not(feature = "runtime"))]
1566#[proc_macro_attribute]
1567#[proc_macro_error]
1568pub fn runtime_free_mem(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1569 panic!("should be used with runtime feature")
1570}
1571#[cfg(all(feature = "runtime", feature = "resource"))]
1572#[proc_macro_attribute]
1573#[proc_macro_error]
1574pub fn runtime_free_mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
1575 let mem_limitation_str = attr.to_string().replace(' ', "");
1576 if byte_unit::Byte::parse_str(&mem_limitation_str, true).is_err() {
1577 abort_call_site!("memory size description is not correct")
1578 }
1579
1580 let ItemFn {
1581 attrs,
1582 vis,
1583 sig,
1584 block,
1585 } = parse_macro_input!(stream as ItemFn);
1586 let syn::Signature { ident, .. } = sig.clone();
1587 let check_ident = syn::Ident::new(
1588 &format!("_check_{}", ident.to_string()),
1589 proc_macro2::Span::call_site(),
1590 );
1591
1592 quote::quote! {
1593 fn #check_ident() -> Result<(), libtest_with::Failed> {
1594 let sys = libtest_with::sysinfo::System::new_with_specifics(
1595 libtest_with::sysinfo::RefreshKind::nothing().with_memory(libtest_with::sysinfo::MemoryRefreshKind::nothing().with_ram()),
1596 );
1597 let mem_size = match libtest_with::byte_unit::Byte::parse_str(format!("{} B", sys.free_memory()), false) {
1598 Ok(b) => b,
1599 Err(_) => panic!("system memory size can not get"),
1600 };
1601 let mem_size_limitation = libtest_with::byte_unit::Byte::parse_str(#mem_limitation_str, true).expect("mem limitation should correct");
1602 if mem_size >= mem_size_limitation {
1603 #ident();
1604 Ok(())
1605 } else {
1606 Err(format!("{}because the memory less than {}",
1607 libtest_with::RUNTIME_IGNORE_PREFIX, #mem_limitation_str).into())
1608 }
1609 }
1610
1611 #(#attrs)*
1612 #vis #sig #block
1613
1614 }
1615 .into()
1616}
1617
1618#[cfg(not(feature = "runtime"))]
1631#[proc_macro_attribute]
1632#[proc_macro_error]
1633pub fn runtime_available_mem(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1634 panic!("should be used with runtime feature")
1635}
1636#[cfg(all(feature = "runtime", feature = "resource"))]
1637#[proc_macro_attribute]
1638#[proc_macro_error]
1639pub fn runtime_available_mem(attr: TokenStream, stream: TokenStream) -> TokenStream {
1640 let mem_limitation_str = attr.to_string().replace(' ', "");
1641 if byte_unit::Byte::parse_str(&mem_limitation_str, true).is_err() {
1642 abort_call_site!("memory size description is not correct")
1643 }
1644
1645 let ItemFn {
1646 attrs,
1647 vis,
1648 sig,
1649 block,
1650 } = parse_macro_input!(stream as ItemFn);
1651 let syn::Signature { ident, .. } = sig.clone();
1652 let check_ident = syn::Ident::new(
1653 &format!("_check_{}", ident.to_string()),
1654 proc_macro2::Span::call_site(),
1655 );
1656
1657 quote::quote! {
1658 fn #check_ident() -> Result<(), libtest_with::Failed> {
1659 let sys = libtest_with::sysinfo::System::new_with_specifics(
1660 libtest_with::sysinfo::RefreshKind::nothing().with_memory(libtest_with::sysinfo::MemoryRefreshKind::nothing().with_ram()),
1661 );
1662 let mem_size = match libtest_with::byte_unit::Byte::parse_str(format!("{} B", sys.available_memory()), false) {
1663 Ok(b) => b,
1664 Err(_) => panic!("system memory size can not get"),
1665 };
1666 let mem_size_limitation = libtest_with::byte_unit::Byte::parse_str(#mem_limitation_str, true).expect("mem limitation should correct");
1667 if mem_size >= mem_size_limitation {
1668 #ident();
1669 Ok(())
1670 } else {
1671 Err(format!("{}because the memory less than {}",
1672 libtest_with::RUNTIME_IGNORE_PREFIX, #mem_limitation_str).into())
1673 }
1674 }
1675
1676 #(#attrs)*
1677 #vis #sig #block
1678
1679 }
1680 .into()
1681}
1682
1683#[proc_macro_attribute]
1698#[proc_macro_error]
1699#[cfg(feature = "resource")]
1700pub fn swap(attr: TokenStream, stream: TokenStream) -> TokenStream {
1701 if is_module(&stream) {
1702 mod_macro(
1703 attr,
1704 parse_macro_input!(stream as ItemMod),
1705 check_swap_condition,
1706 )
1707 } else {
1708 fn_macro(
1709 attr,
1710 parse_macro_input!(stream as ItemFn),
1711 check_swap_condition,
1712 )
1713 }
1714}
1715
1716#[cfg(feature = "resource")]
1717fn check_swap_condition(swap_size_str: String) -> (bool, String) {
1718 let sys = sysinfo::System::new_with_specifics(
1719 sysinfo::RefreshKind::nothing()
1720 .with_memory(sysinfo::MemoryRefreshKind::nothing().with_swap()),
1721 );
1722 let swap_size = match byte_unit::Byte::parse_str(format!("{} B", sys.total_swap()), false) {
1723 Ok(b) => b,
1724 Err(_) => abort_call_site!("Swap size description is not correct"),
1725 };
1726 let swap_size_limitation = match byte_unit::Byte::parse_str(&swap_size_str, true) {
1727 Ok(b) => b,
1728 Err(_) => abort_call_site!("Can not get system swap size"),
1729 };
1730 (
1731 swap_size >= swap_size_limitation,
1732 format!("because the swap less than {}", swap_size_str),
1733 )
1734}
1735
1736#[cfg(not(feature = "runtime"))]
1749#[proc_macro_attribute]
1750#[proc_macro_error]
1751pub fn runtime_swap(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1752 panic!("should be used with runtime feature")
1753}
1754#[cfg(all(feature = "runtime", feature = "resource"))]
1755#[proc_macro_attribute]
1756#[proc_macro_error]
1757pub fn runtime_swap(attr: TokenStream, stream: TokenStream) -> TokenStream {
1758 let swap_limitation_str = attr.to_string().replace(' ', "");
1759 if byte_unit::Byte::parse_str(&swap_limitation_str, true).is_err() {
1760 abort_call_site!("swap size description is not correct")
1761 }
1762
1763 let ItemFn {
1764 attrs,
1765 vis,
1766 sig,
1767 block,
1768 } = parse_macro_input!(stream as ItemFn);
1769 let syn::Signature { ident, .. } = sig.clone();
1770 let check_ident = syn::Ident::new(
1771 &format!("_check_{}", ident.to_string()),
1772 proc_macro2::Span::call_site(),
1773 );
1774
1775 quote::quote! {
1776 fn #check_ident() -> Result<(), libtest_with::Failed> {
1777 let sys = libtest_with::sysinfo::System::new_with_specifics(
1778 libtest_with::sysinfo::RefreshKind::nothing().with_memory(libtest_with::sysinfo::MemoryRefreshKind::nothing().with_swap()),
1779 );
1780 let swap_size = match libtest_with::byte_unit::Byte::parse_str(format!("{} B", sys.total_swap()), false) {
1781 Ok(b) => b,
1782 Err(_) => panic!("system swap size can not get"),
1783 };
1784 let swap_size_limitation = libtest_with::byte_unit::Byte::parse_str(#swap_limitation_str, true).expect("swap limitation should correct");
1785 if swap_size >= swap_size_limitation {
1786 #ident();
1787 Ok(())
1788 } else {
1789 Err(format!("{}because the swap less than {}",
1790 libtest_with::RUNTIME_IGNORE_PREFIX, #swap_limitation_str).into())
1791 }
1792 }
1793
1794 #(#attrs)*
1795 #vis #sig #block
1796
1797 }
1798 .into()
1799}
1800
1801#[cfg(not(feature = "runtime"))]
1814#[proc_macro_attribute]
1815#[proc_macro_error]
1816pub fn runtime_free_swap(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1817 panic!("should be used with runtime feature")
1818}
1819#[cfg(all(feature = "runtime", feature = "resource"))]
1820#[proc_macro_attribute]
1821#[proc_macro_error]
1822pub fn runtime_free_swap(attr: TokenStream, stream: TokenStream) -> TokenStream {
1823 let swap_limitation_str = attr.to_string().replace(' ', "");
1824 if byte_unit::Byte::parse_str(&swap_limitation_str, true).is_err() {
1825 abort_call_site!("swap size description is not correct")
1826 }
1827
1828 let ItemFn {
1829 attrs,
1830 vis,
1831 sig,
1832 block,
1833 } = parse_macro_input!(stream as ItemFn);
1834 let syn::Signature { ident, .. } = sig.clone();
1835 let check_ident = syn::Ident::new(
1836 &format!("_check_{}", ident.to_string()),
1837 proc_macro2::Span::call_site(),
1838 );
1839
1840 quote::quote! {
1841 fn #check_ident() -> Result<(), libtest_with::Failed> {
1842 let sys = libtest_with::sysinfo::System::new_with_specifics(
1843 libtest_with::sysinfo::RefreshKind::nothing().with_memory(libtest_with::sysinfo::MemoryRefreshKind::nothing().with_swap()),
1844 );
1845 let swap_size = match libtest_with::byte_unit::Byte::parse_str(format!("{} B", sys.free_swap()), false) {
1846 Ok(b) => b,
1847 Err(_) => panic!("system swap size can not get"),
1848 };
1849 let swap_size_limitation = libtest_with::byte_unit::Byte::parse_str(#swap_limitation_str, true).expect("swap limitation should correct");
1850 if swap_size >= swap_size_limitation {
1851 #ident();
1852 Ok(())
1853 } else {
1854 Err(format!("{}because the swap less than {}",
1855 libtest_with::RUNTIME_IGNORE_PREFIX, #swap_limitation_str).into())
1856 }
1857 }
1858
1859 #(#attrs)*
1860 #vis #sig #block
1861
1862 }
1863 .into()
1864}
1865
1866#[proc_macro_attribute]
1881#[proc_macro_error]
1882#[cfg(feature = "resource")]
1883pub fn cpu_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
1884 if is_module(&stream) {
1885 mod_macro(
1886 attr,
1887 parse_macro_input!(stream as ItemMod),
1888 check_cpu_core_condition,
1889 )
1890 } else {
1891 fn_macro(
1892 attr,
1893 parse_macro_input!(stream as ItemFn),
1894 check_cpu_core_condition,
1895 )
1896 }
1897}
1898
1899#[cfg(feature = "resource")]
1900fn check_cpu_core_condition(core_limitation_str: String) -> (bool, String) {
1901 (
1902 match core_limitation_str.parse::<usize>() {
1903 Ok(c) => num_cpus::get() >= c,
1904 Err(_) => abort_call_site!("core limitation is incorrect"),
1905 },
1906 format!("because the cpu core less than {}", core_limitation_str),
1907 )
1908}
1909
1910#[cfg(not(feature = "runtime"))]
1923#[proc_macro_attribute]
1924#[proc_macro_error]
1925pub fn runtime_cpu_core(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
1926 panic!("should be used with runtime feature")
1927}
1928#[cfg(all(feature = "runtime", feature = "resource"))]
1929#[proc_macro_attribute]
1930#[proc_macro_error]
1931pub fn runtime_cpu_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
1932 let attr_str = attr.to_string().replace(' ', "");
1933 let core_limitation = match attr_str.parse::<usize>() {
1934 Ok(c) => c,
1935 Err(_) => abort_call_site!("core limitation is incorrect"),
1936 };
1937
1938 let ItemFn {
1939 attrs,
1940 vis,
1941 sig,
1942 block,
1943 } = parse_macro_input!(stream as ItemFn);
1944 let syn::Signature { ident, .. } = sig.clone();
1945 let check_ident = syn::Ident::new(
1946 &format!("_check_{}", ident.to_string()),
1947 proc_macro2::Span::call_site(),
1948 );
1949
1950 quote::quote! {
1951 fn #check_ident() -> Result<(), libtest_with::Failed> {
1952 if libtest_with::num_cpus::get() >= #core_limitation {
1953 #ident();
1954 Ok(())
1955 } else {
1956 Err(format!("{}because the cpu core less than {}",
1957 libtest_with::RUNTIME_IGNORE_PREFIX, #core_limitation).into())
1958 }
1959 }
1960
1961 #(#attrs)*
1962 #vis #sig #block
1963
1964 }
1965 .into()
1966}
1967
1968#[proc_macro_attribute]
1983#[proc_macro_error]
1984#[cfg(feature = "resource")]
1985pub fn phy_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
1986 if is_module(&stream) {
1987 mod_macro(
1988 attr,
1989 parse_macro_input!(stream as ItemMod),
1990 check_cpu_core_condition,
1991 )
1992 } else {
1993 fn_macro(
1994 attr,
1995 parse_macro_input!(stream as ItemFn),
1996 check_phy_core_condition,
1997 )
1998 }
1999}
2000
2001#[cfg(feature = "resource")]
2002fn check_phy_core_condition(core_limitation_str: String) -> (bool, String) {
2003 (
2004 match core_limitation_str.parse::<usize>() {
2005 Ok(c) => num_cpus::get_physical() >= c,
2006 Err(_) => abort_call_site!("physical core limitation is incorrect"),
2007 },
2008 format!(
2009 "because the physical cpu core less than {}",
2010 core_limitation_str
2011 ),
2012 )
2013}
2014
2015#[cfg(not(feature = "runtime"))]
2028#[proc_macro_attribute]
2029#[proc_macro_error]
2030pub fn runtime_phy_cpu_core(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2031 panic!("should be used with runtime feature")
2032}
2033#[cfg(all(feature = "runtime", feature = "resource"))]
2034#[proc_macro_attribute]
2035#[proc_macro_error]
2036pub fn runtime_phy_cpu_core(attr: TokenStream, stream: TokenStream) -> TokenStream {
2037 let attr_str = attr.to_string().replace(' ', "");
2038 let core_limitation = match attr_str.parse::<usize>() {
2039 Ok(c) => c,
2040 Err(_) => abort_call_site!("physical core limitation is incorrect"),
2041 };
2042
2043 let ItemFn {
2044 attrs,
2045 vis,
2046 sig,
2047 block,
2048 } = parse_macro_input!(stream as ItemFn);
2049 let syn::Signature { ident, .. } = sig.clone();
2050 let check_ident = syn::Ident::new(
2051 &format!("_check_{}", ident.to_string()),
2052 proc_macro2::Span::call_site(),
2053 );
2054
2055 quote::quote! {
2056 fn #check_ident() -> Result<(), libtest_with::Failed> {
2057 if libtest_with::num_cpus::get_physical() >= #core_limitation {
2058 #ident();
2059 Ok(())
2060 } else {
2061 Err(format!("{}because the physical cpu core less than {}",
2062 libtest_with::RUNTIME_IGNORE_PREFIX, #core_limitation).into())
2063 }
2064 }
2065
2066 #(#attrs)*
2067 #vis #sig #block
2068
2069 }
2070 .into()
2071}
2072
2073#[proc_macro_attribute]
2114#[proc_macro_error]
2115#[cfg(feature = "executable")]
2116pub fn executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
2117 if is_module(&stream) {
2118 mod_macro(
2119 attr,
2120 parse_macro_input!(stream as ItemMod),
2121 check_executable_condition,
2122 )
2123 } else {
2124 fn_macro(
2125 attr,
2126 parse_macro_input!(stream as ItemFn),
2127 check_executable_condition,
2128 )
2129 }
2130}
2131
2132#[cfg(feature = "executable")]
2133fn check_executable_and_condition(attr_str: String) -> (bool, String) {
2134 let executables: Vec<&str> = attr_str.split(',').collect();
2135 let mut missing_executables = vec![];
2136 for exe in executables.iter() {
2137 if which(exe.trim_matches('"')).is_err() {
2138 missing_executables.push(exe.to_string());
2139 }
2140 }
2141 let ignore_msg = if missing_executables.len() == 1 {
2142 format!("because executable not found: {}", missing_executables[0])
2143 } else {
2144 format!(
2145 "because following executables not found: \n{}\n",
2146 missing_executables.join("\n")
2147 )
2148 };
2149 (missing_executables.is_empty(), ignore_msg)
2150}
2151
2152#[cfg(feature = "executable")]
2153fn check_executable_or_condition(attr_str: String) -> (bool, String) {
2154 let executables: Vec<&str> = attr_str.split("||").collect();
2155 for exe in executables.iter() {
2156 if which(exe.trim_matches('"')).is_ok() {
2157 return (true, String::new());
2158 }
2159 }
2160 (
2161 false,
2162 format!("because none of executables can be found: {}", attr_str),
2163 )
2164}
2165
2166#[cfg(feature = "executable")]
2167fn check_executable_condition(attr_str: String) -> (bool, String) {
2168 let has_and_cond = attr_str.contains(',');
2169 let has_or_cond = attr_str.contains("||");
2170 if has_and_cond && has_or_cond {
2171 abort_call_site!("',' and '||' can not be used at the same time")
2172 } else if has_or_cond {
2173 check_executable_or_condition(attr_str)
2174 } else {
2175 check_executable_and_condition(attr_str)
2176 }
2177}
2178
2179#[cfg(not(feature = "runtime"))]
2192#[proc_macro_attribute]
2193#[proc_macro_error]
2194pub fn runtime_executable(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2195 panic!("should be used with runtime feature")
2196}
2197#[cfg(all(feature = "runtime", feature = "executable"))]
2198#[proc_macro_attribute]
2199#[proc_macro_error]
2200pub fn runtime_executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
2201 let attr_str = attr.to_string().replace(' ', "");
2202 let has_and_cond = attr_str.contains(',');
2203 let has_or_cond = attr_str.contains("||");
2204 if has_and_cond && has_or_cond {
2205 abort_call_site!("',' and '||' can not be used at the same time")
2206 }
2207 let executables: Vec<&str> = if has_or_cond {
2208 attr_str.split("||").collect()
2209 } else {
2210 attr_str.split(',').collect()
2211 };
2212 let ItemFn {
2213 attrs,
2214 vis,
2215 sig,
2216 block,
2217 } = parse_macro_input!(stream as ItemFn);
2218 let syn::Signature { ident, .. } = sig.clone();
2219 let check_ident = syn::Ident::new(
2220 &format!("_check_{}", ident.to_string()),
2221 proc_macro2::Span::call_site(),
2222 );
2223
2224 if has_or_cond {
2225 quote::quote! {
2226 fn #check_ident() -> Result<(), libtest_with::Failed> {
2227 #(
2228 if libtest_with::which::which(#executables).is_ok() {
2229 #ident();
2230 return Ok(());
2231 }
2232 )*
2233 Err(format!("{}because none of executables can be found:\n{}\n",
2234 libtest_with::RUNTIME_IGNORE_PREFIX, attr_str).into())
2235 }
2236
2237 #(#attrs)*
2238 #vis #sig #block
2239
2240 }
2241 .into()
2242 } else {
2243 quote::quote! {
2244 fn #check_ident() -> Result<(), libtest_with::Failed> {
2245 let mut missing_executables = vec![];
2246 #(
2247 if libtest_with::which::which(#executables).is_err() {
2248 missing_executables.push(#executables);
2249 }
2250 )*
2251 match missing_executables.len() {
2252 0 => {
2253 #ident();
2254 Ok(())
2255 },
2256 1 => Err(
2257 format!("{}because executable {} not found",
2258 libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables[0]
2259 ).into()),
2260 _ => Err(
2261 format!("{}because following executables not found:\n{}\n",
2262 libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables.join(", ")
2263 ).into()),
2264 }
2265 }
2266
2267 #(#attrs)*
2268 #vis #sig #block
2269
2270 }
2271 .into()
2272 }
2273}
2274
2275#[cfg(not(feature = "runtime"))]
2297#[proc_macro]
2298pub fn runner(_input: TokenStream) -> TokenStream {
2299 panic!("should be used with runtime feature")
2300}
2301#[cfg(feature = "runtime")]
2302#[proc_macro]
2303pub fn runner(input: TokenStream) -> TokenStream {
2304 let input_str = input.to_string();
2305 let mod_names: Vec<syn::Ident> = input_str
2306 .split(",")
2307 .map(|s| syn::Ident::new(s.trim(), proc_macro2::Span::call_site()))
2308 .collect();
2309 quote::quote! {
2310 fn main() {
2311 let args = libtest_with::Arguments::from_args();
2312 let mut no_env_tests = Vec::new();
2313 #(
2314 match #mod_names::_runtime_tests() {
2315 (Some(env), tests) => {
2316 libtest_with::run(&args, tests).exit_if_failed();
2317 drop(env);
2318 },
2319 (None, mut tests) => no_env_tests.append(&mut tests),
2320 }
2321 )*
2322 libtest_with::run(&args, no_env_tests).exit();
2323 }
2324 }
2325 .into()
2326}
2327
2328#[cfg(not(feature = "runtime"))]
2443#[proc_macro_attribute]
2444#[proc_macro_error]
2445pub fn module(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2446 panic!("should be used with runtime feature")
2447}
2448#[cfg(feature = "runtime")]
2449#[proc_macro_attribute]
2450#[proc_macro_error]
2451pub fn module(_attr: TokenStream, stream: TokenStream) -> TokenStream {
2452 let ItemMod {
2453 attrs,
2454 vis,
2455 mod_token,
2456 ident,
2457 content,
2458 ..
2459 } = parse_macro_input!(stream as ItemMod);
2460
2461 if let Some(content) = content {
2462 let content = content.1;
2463 if crate::utils::has_test_cfg(&attrs) {
2464 abort_call_site!("should not use `#[cfg(test)]` on the mod with `#[test_with::module]`")
2465 } else {
2466 let mut test_env_type = None;
2467 let test_names: Vec<String> = content
2468 .iter()
2469 .filter_map(|c| match c {
2470 Item::Fn(ItemFn {
2471 sig: syn::Signature { ident, .. },
2472 attrs,
2473 ..
2474 }) => match crate::utils::test_with_attrs(&attrs) {
2475 (true, true, _) => abort_call_site!(
2476 "should not use #[test] for method in `#[test_with::module]`"
2477 ),
2478 (_, true, false) => abort_call_site!(
2479 "use `#[test_with::runtime_*]` for method in `#[test_with::module]`"
2480 ),
2481 (false, true, true) => Some(ident.to_string()),
2482 _ => None,
2483 },
2484 Item::Struct(ItemStruct { ident, vis, .. })
2485 | Item::Type(ItemType { ident, vis, .. }) => {
2486 if ident.to_string() == "TestEnv" {
2487 match vis {
2488 syn::Visibility::Public(_) => test_env_type = Some(ident),
2489 _ => abort_call_site!("TestEnv should be pub for testing"),
2490 }
2491 }
2492 None
2493 }
2494 _ => None,
2495 })
2496 .collect();
2497 let check_names: Vec<syn::Ident> = test_names
2498 .iter()
2499 .map(|c| {
2500 syn::Ident::new(
2501 &format!("_check_{}", c.to_string()),
2502 proc_macro2::Span::call_site(),
2503 )
2504 })
2505 .collect();
2506 if let Some(test_env_type) = test_env_type {
2507 quote::quote! {
2508 #(#attrs)*
2509 #vis #mod_token #ident {
2510 use super::*;
2511 pub fn _runtime_tests() -> (Option<#test_env_type>, Vec<libtest_with::Trial>) {
2512 use libtest_with::Trial;
2513 (
2514 Some(#test_env_type::default()),
2515 vec![
2516 #(Trial::test(#test_names, #check_names),)*
2517 ]
2518 )
2519 }
2520 #(#content)*
2521 }
2522 }
2523 .into()
2524 } else {
2525 quote::quote! {
2526 #(#attrs)*
2527 #vis #mod_token #ident {
2528 use super::*;
2529 pub fn _runtime_tests() -> (Option<()>, Vec<libtest_with::Trial>) {
2530 use libtest_with::Trial;
2531 (
2532 None,
2533 vec![
2534 #(Trial::test(#test_names, #check_names),)*
2535 ]
2536 )
2537 }
2538 #(#content)*
2539 }
2540 }
2541 .into()
2542 }
2543 }
2544 } else {
2545 abort_call_site!("should use on mod with context")
2546 }
2547}
2548
2549#[cfg(not(feature = "runtime"))]
2567#[proc_macro_attribute]
2568#[proc_macro_error]
2569pub fn runtime_ignore_if(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2570 panic!("should be used with runtime feature")
2571}
2572#[cfg(feature = "runtime")]
2573#[proc_macro_attribute]
2574#[proc_macro_error]
2575pub fn runtime_ignore_if(attr: TokenStream, stream: TokenStream) -> TokenStream {
2576 let ignore_function = syn::Ident::new(
2577 &attr.to_string().replace(' ', ""),
2578 proc_macro2::Span::call_site(),
2579 );
2580 let ItemFn {
2581 attrs,
2582 vis,
2583 sig,
2584 block,
2585 } = parse_macro_input!(stream as ItemFn);
2586 let syn::Signature { ident, .. } = sig.clone();
2587 let check_ident = syn::Ident::new(
2588 &format!("_check_{}", ident.to_string()),
2589 proc_macro2::Span::call_site(),
2590 );
2591 quote::quote! {
2592 fn #check_ident() -> Result<(), libtest_with::Failed> {
2593 if let Some(msg) = #ignore_function() {
2594 Err(format!("{}{msg}", libtest_with::RUNTIME_IGNORE_PREFIX).into())
2595 } else {
2596 #ident();
2597 Ok(())
2598 }
2599 }
2600
2601 #(#attrs)*
2602 #vis #sig #block
2603 }
2604 .into()
2605}
2606
2607#[cfg(test)]
2608mod tests {
2609 use super::{check_env_condition, check_no_env_condition};
2610
2611 mod env_macro {
2612 use super::*;
2613
2614 #[test]
2615 fn single_env_var_should_be_not_set() {
2616 let env_var = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2618
2619 let attr_str = env_var.to_string();
2621
2622 let (is_ok, ignore_msg) = check_env_condition(attr_str);
2624
2625 assert!(!is_ok);
2628 assert!(ignore_msg.contains(env_var));
2630 }
2631
2632 #[test]
2633 fn multiple_env_vars_should_not_be_set() {
2634 let env_var1 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2636 let env_var2 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2637
2638 let attr_str = format!("{}, {}", env_var1, env_var2);
2640
2641 let (is_ok, ignore_msg) = check_env_condition(attr_str);
2643
2644 assert!(!is_ok);
2647 assert!(ignore_msg.contains(env_var1));
2649 assert!(ignore_msg.contains(env_var2));
2650 }
2651
2652 #[test]
2653 fn single_env_var_should_be_set() {
2654 let env_var = "PATH";
2656
2657 let attr_str = env_var.to_string();
2659
2660 let (is_ok, ignore_msg) = check_env_condition(attr_str);
2662
2663 assert!(is_ok);
2666 assert!(!ignore_msg.contains(env_var));
2668 }
2669
2670 #[test]
2681 fn multiple_env_vars_should_be_set() {
2682 let env_var1 = "PATH";
2684 let env_var2 = "HOME";
2685
2686 let attr_str = format!("\t{},\n\t{}\n", env_var1, env_var2);
2688
2689 let (is_ok, ignore_msg) = check_env_condition(attr_str);
2691
2692 assert!(is_ok);
2695 assert!(!ignore_msg.contains(env_var1));
2697 assert!(!ignore_msg.contains(env_var2));
2698 }
2699
2700 #[test]
2703 fn multiple_env_vars_but_one_is_not_set() {
2704 let env_var1 = "PATH";
2706 let env_var2 = "HOME";
2707 let env_var3 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2708
2709 let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2711
2712 let (is_ok, ignore_msg) = check_env_condition(attr_str);
2714
2715 assert!(!is_ok);
2718 assert!(!ignore_msg.contains(env_var1));
2720 assert!(!ignore_msg.contains(env_var2));
2721 assert!(ignore_msg.contains(env_var3));
2722 }
2723
2724 #[test]
2727 fn multiple_env_vars_and_various_not_set() {
2728 let env_var1 = "PATH";
2730 let env_var2 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2731 let env_var3 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2732
2733 let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2735
2736 let (is_ok, ignore_msg) = check_env_condition(attr_str);
2738
2739 assert!(!is_ok);
2742 assert!(!ignore_msg.contains(env_var1));
2744 assert!(ignore_msg.contains(env_var2));
2745 assert!(ignore_msg.contains(env_var3));
2746 }
2747 }
2748
2749 mod no_env_macro {
2750 use super::*;
2751
2752 #[test]
2753 fn single_env_var_not_set() {
2754 let env_var = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2756
2757 let attr_str = env_var.to_string();
2759
2760 let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2762
2763 assert!(is_ok);
2766 assert!(!ignore_msg.contains(env_var));
2768 }
2769
2770 #[test]
2771 fn multiple_env_vars_not_set() {
2772 let env_var1 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2774 let env_var2 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2775
2776 let attr_str = format!("{}, {}", env_var1, env_var2);
2778
2779 let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2781
2782 assert!(is_ok);
2785 assert!(!ignore_msg.contains(env_var1));
2787 assert!(!ignore_msg.contains(env_var2));
2788 }
2789
2790 #[test]
2791 fn single_env_var_set() {
2792 let env_var = "PATH";
2794
2795 let attr_str = env_var.to_string();
2797
2798 let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2800
2801 assert!(!is_ok);
2804 assert!(ignore_msg.contains(env_var));
2806 }
2807
2808 #[test]
2819 fn multiple_env_vars_set() {
2820 let env_var1 = "PATH";
2822 let env_var2 = "HOME";
2823
2824 let attr_str = format!("\t{},\n\t{}\n", env_var1, env_var2);
2826
2827 let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2829
2830 assert!(!is_ok);
2833 assert!(ignore_msg.contains(env_var1));
2835 assert!(ignore_msg.contains(env_var2));
2836 }
2837
2838 #[test]
2841 fn multiple_env_vars_but_one_is_set() {
2842 let env_var1 = "PATH";
2844 let env_var2 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2845 let env_var3 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2846
2847 let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2849
2850 let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2852
2853 assert!(!is_ok);
2856 assert!(ignore_msg.contains(env_var1));
2858 assert!(!ignore_msg.contains(env_var2));
2859 assert!(!ignore_msg.contains(env_var3));
2860 }
2861
2862 #[test]
2865 fn multiple_env_vars_and_various_are_set() {
2866 let env_var1 = "PATH";
2868 let env_var2 = "HOME";
2869 let env_var3 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2870
2871 let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2873
2874 let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2876
2877 assert!(!is_ok);
2880 assert!(ignore_msg.contains(env_var1));
2882 assert!(ignore_msg.contains(env_var2));
2883 assert!(!ignore_msg.contains(env_var3));
2884 }
2885 }
2886}
2887
2888#[proc_macro_attribute]
2926#[proc_macro_error]
2927pub fn lock(attr: TokenStream, stream: TokenStream) -> TokenStream {
2928 if is_module(&stream) {
2929 abort_call_site!("#[test_with::lock] only works with fn")
2930 } else {
2931 lock_macro(attr, parse_macro_input!(stream as ItemFn))
2932 }
2933}
2934
2935#[cfg(feature = "timezone")]
2970#[proc_macro_attribute]
2971#[proc_macro_error]
2972pub fn timezone(attr: TokenStream, stream: TokenStream) -> TokenStream {
2973 if is_module(&stream) {
2974 mod_macro(
2975 attr,
2976 parse_macro_input!(stream as ItemMod),
2977 check_tz_condition,
2978 )
2979 } else {
2980 fn_macro(
2981 attr,
2982 parse_macro_input!(stream as ItemFn),
2983 check_tz_condition,
2984 )
2985 }
2986}
2987
2988#[cfg(feature = "timezone")]
2989fn check_timezone(attr_str: &String) -> (bool, Vec<&str>) {
2990 let mut incorrect_tzs = vec![];
2991 let mut match_tz = false;
2992 let current_tz = chrono::Local::now().offset().local_minus_utc() / 60;
2993
2994 for tz in attr_str.split(',') {
2995 let parsed_tz = match tz {
2996 "NZDT" => Ok(13 * 60),
2997 "NZST" => Ok(12 * 60),
2998 "AEDT" => Ok(11 * 60),
2999 "ACDT" => Ok(10 * 60 + 30),
3000 "AEST" => Ok(10 * 60),
3001 "ACST" => Ok(9 * 60 + 30),
3002 "KST" | "JST" => Ok(9 * 60),
3003 "HKT" | "WITA" | "AWST" => Ok(8 * 60),
3004 "PST" => abort_call_site!("PST can be GMT+8 or GMT-8, please use +8 or -8 instead"),
3005 "WIB" => Ok(7 * 60),
3006 "CST" => abort_call_site!("PST can be GMT+8 or GMT-6, please use +8 or -6 instead"),
3007 "5.5" | "+5.5" => Ok(5 * 60 + 30),
3008 "IST" => abort_call_site!(
3009 "IST can be GMT+5.5, GMT+2 or GMT+1, please use +5.5, 2 or 1 instead"
3010 ),
3011 "PKT" => Ok(5 * 60),
3012 "EAT" | "EEST" | "IDT" | "MSK" => Ok(3 * 60),
3013 "CAT" | "EET" | "CEST" | "SAST" => Ok(2 * 60),
3014 "CET" | "WAT" | "WEST" | "BST" => Ok(1 * 60),
3015 "UTC" | "GMT" | "WET" => Ok(0),
3016 "NDT" | "-2.5" => Ok(-2 * 60 - 30),
3017 "NST" | "-3.5" => Ok(-3 * 60 - 30),
3018 "ADT" => Ok(-3 * 60),
3019 "AST" | "EDT" => Ok(-4 * 60),
3020 "EST" | "CDT" => Ok(-5 * 60),
3021 "MDT" => Ok(-6 * 60),
3022 "MST" | "PDT" => Ok(-7 * 60),
3023 "AKDT" => Ok(-8 * 60),
3024 "HDT" | "AKST" => Ok(-9 * 60),
3025 "HST" => Ok(-10 * 60),
3026 _ => tz.parse::<i32>().map(|tz| tz * 60),
3027 };
3028 if let Ok(parsed_tz) = parsed_tz {
3029 match_tz |= current_tz == parsed_tz;
3030 } else {
3031 incorrect_tzs.push(tz);
3032 }
3033 }
3034 (match_tz, incorrect_tzs)
3035}
3036
3037#[cfg(feature = "timezone")]
3038fn check_tz_condition(attr_str: String) -> (bool, String) {
3039 let (match_tz, incorrect_tzs) = check_timezone(&attr_str);
3040
3041 if incorrect_tzs.len() == 1 {
3043 (
3044 false,
3045 format!("because timezone {} is incorrect", incorrect_tzs[0]),
3046 )
3047 } else if incorrect_tzs.len() > 1 {
3048 (
3049 false,
3050 format!(
3051 "because following timezones are incorrect:\n{}\n",
3052 incorrect_tzs.join(", ")
3053 ),
3054 )
3055 } else if match_tz {
3056 (true, String::new())
3057 } else {
3058 (
3059 false,
3060 format!(
3061 "because the test case not run in following timezone:\n{}\n",
3062 attr_str
3063 ),
3064 )
3065 }
3066}
3067
3068#[cfg(not(feature = "runtime"))]
3081#[proc_macro_attribute]
3082#[proc_macro_error]
3083pub fn runtime_timezone(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
3084 panic!("should be used with runtime feature")
3085}
3086
3087#[cfg(all(feature = "runtime", feature = "timezone"))]
3088#[proc_macro_attribute]
3089#[proc_macro_error]
3090pub fn runtime_timezone(attr: TokenStream, stream: TokenStream) -> TokenStream {
3091 let attr_str = attr.to_string();
3092 let ItemFn {
3093 attrs,
3094 vis,
3095 sig,
3096 block,
3097 } = parse_macro_input!(stream as ItemFn);
3098 let syn::Signature { ident, .. } = sig.clone();
3099 let check_ident = syn::Ident::new(
3100 &format!("_check_{}", ident.to_string()),
3101 proc_macro2::Span::call_site(),
3102 );
3103 quote::quote! {
3104 fn #check_ident() -> Result<(), libtest_with::Failed> {
3105
3106 let mut incorrect_tzs = vec![];
3107 let mut match_tz = false;
3108 let current_tz = libtest_with::chrono::Local::now().offset().local_minus_utc() / 60;
3109 for tz in #attr_str.split(',') {
3110 if let Ok(parsed_tz) = tz.parse::<i32>() {
3111 match_tz |= current_tz == parsed_tz;
3112 } else {
3113 incorrect_tzs.push(tz);
3114 }
3115 }
3116
3117 if match_tz && incorrect_tzs.is_empty() {
3118 #ident();
3119 Ok(())
3120 } else if incorrect_tzs.len() == 1 {
3121 Err(
3122 format!("{}because timezone {} is incorrect",
3123 libtest_with::RUNTIME_IGNORE_PREFIX, incorrect_tzs[0]
3124 ).into())
3125 } else if incorrect_tzs.len() > 1 {
3126 Err(
3127 format!("{}because following timezones are incorrect:\n{:?}\n",
3128 libtest_with::RUNTIME_IGNORE_PREFIX, incorrect_tzs
3129 ).into())
3130 } else {
3131 Err(
3132 format!(
3133 "{}because the test case not run in following timezone:\n{}\n",
3134 libtest_with::RUNTIME_IGNORE_PREFIX,
3135 #attr_str
3136 ).into())
3137 }
3138 }
3139
3140 #(#attrs)*
3141 #vis #sig #block
3142 }
3143 .into()
3144}