test_with/
lib.rs

1//! `test_with` provides [macro@env], [macro@file], [macro@path], [macro@http], [macro@https],
2//! [macro@icmp], [macro@tcp], [macro@root], [macro@group], [macro@user], [macro@mem], [macro@swap],
3//! [macro@cpu_core], [macro@phy_core], [macro@executable], [macro@timezone] macros to help you run
4//! test case only with the condition is fulfilled.  If the `#[test]` is absent for the test case,
5//! `#[test_with]` will add it to the test case automatically.
6//!
7//! This crate help you easier make integrating test case and has a good cargo summary on CI server,
8//! and will not affect on your binary output when you dependent it as dev-dependency as following.
9//! ```toml
10//! [dev-dependencies]
11//! test-with = "*"
12//! ```
13//! All features will be opt-in default feature, so this crate will be easier to use, if you using
14//! a CI server with really limitation resource and want this crate as slim as possible, you can
15//! select the feature you want as following.
16//! ```toml
17//! [dev-dependencies]
18//! test-with = { version = "*", default-features = false, features = ["net"] }
19//! ```
20//!
21//! The solution to have a real runtime condition check, we need to put the test as normal function
22//! as an example, then use `cargo run --example`
23//! The `test-with` need be included as normal dependency with `runtime` feature.
24//! And also include the `libtest-with` with corresponding features in `Cargo.toml`
25//! [macro@runner] and [macro@module] are for the basic skeleton of the test runner.
26//! [macro@runtime_env], [macro@runtime_no_env], [macro@runtime_file], [macro@runtime_path],
27//! [macro@runtime_http], [macro@runtime_https], [macro@runtime_icmp], [macro@runtime_tcp],
28//! [macro@runtime_root], [macro@runtime_group], [macro@runtime_user], [macro@runtime_mem],
29//! [macro@runtime_free_mem], [macro@runtime_available_mem], [macro@runtime_swap],
30//! [macro@runtime_free_swap], [macro@runtime_available_swap], [macro@runtime_cpu_core],
31//! [macro@runtime_phy_core], [macro@runtime_executable], [macro@runtime_timezone]
32//! and [macro@runtime_ignore_if] are used to transform a normal function to a testcase.
33//!
34//! ```toml
35//! [dependencies]
36//! test-with = { version = "*", default-features = false, features = ["runtime"] }
37//! libtest-with = { version = "0.8.1-3", features = ["net", "resource", "user", "executable", "timezone"] }
38//! ```
39//!
40//! ```rust
41//! // write as example in examples/*rs
42//! test_with::runner!(env);
43//! #[test_with::module]
44//! mod env {
45//! #[test_with::runtime_env(PWD)]
46//! fn test_works() {
47//!     assert!(true);
48//!     }
49//! }
50//! ```
51
52use 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/// Run test case when the environment variable is set.
74/// ```
75/// #[cfg(test)]
76/// mod tests {
77///
78///     // PWD environment variable exists
79///     #[test_with::env(PWD)]
80///     #[test]
81///     fn test_works() {
82///         assert!(true);
83///     }
84///
85///     // NOTHING environment variable does not exist
86///     #[test_with::env(NOTHING)]
87///     #[test]
88///     fn test_ignored() {
89///         panic!("should be ignored")
90///     }
91///
92///     // NOT_SAYING environment variable does not exist
93///     #[test_with::env(PWD, NOT_SAYING)]
94///     #[test]
95///     fn test_ignored_too() {
96///         panic!("should be ignored")
97///     }
98/// }
99/// ```
100/// or run all test cases for test module when the environment variable is set.
101/// ```
102/// #[test_with::env(PWD)]
103/// #[cfg(test)]
104/// mod tests {
105///
106///     #[test]
107///     fn test_works() {
108///         assert!(true);
109///     }
110/// }
111/// ```
112#[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    // Check if the environment variables are set
134    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    // Generate ignore message
142    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/// Run test case when the example running and the environment variable is set.
157///```rust
158/// // write as example in examples/*rs
159/// test_with::runner!(env);
160/// #[test_with::module]
161/// mod env {
162/// #[test_with::runtime_env(PWD)]
163/// fn test_works() {
164///     assert!(true);
165///     }
166/// }
167///```
168#[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/// Ignore test case when the environment variable is set.
222/// ```
223/// #[cfg(test)]
224/// mod tests {
225///
226///     // The test will be ignored in GITHUB_ACTION
227///     #[test_with::no_env(GITHUB_ACTIONS)]
228///     #[test]
229///     fn test_ignore_in_github_action() {
230///         assert!(false);
231///     }
232/// }
233#[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    // Check if the environment variables are set
255    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    // Generate ignore message
263    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/// Ignore test case when the example running and the environment variable is set.
278///```rust
279/// // write as example in examples/*rs
280/// test_with::runner!(env);
281/// #[test_with::module]
282/// mod env {
283/// #[test_with::runtime_no_env(NOT_EXIST)]
284/// fn test_works() {
285///     assert!(true);
286///     }
287/// }
288///```
289#[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/// Run test case when the file exist.
343/// ```
344/// #[cfg(test)]
345/// mod tests {
346///
347///     // hostname exists
348///     #[test_with::file(/etc/hostname)]
349///     #[test]
350///     fn test_works() {
351///         assert!(true);
352///     }
353///
354///     // nothing file does not exist
355///     #[test_with::file(/etc/nothing)]
356///     #[test]
357///     fn test_ignored() {
358///         panic!("should be ignored")
359///     }
360///
361///     // hostname and hosts exist
362///     #[test_with::file(/etc/hostname, /etc/hosts)]
363///     #[test]
364///     fn test_works_too() {
365///         assert!(true);
366///     }
367/// }
368/// ```
369#[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/// Run test case when the example running and the file exist.
407///```rust
408/// // write as example in examples/*rs
409/// test_with::runner!(file);
410/// #[test_with::module]
411/// mod file {
412///     #[test_with::runtime_file(/etc/hostname)]
413///     fn test_works() {
414///         assert!(true);
415///     }
416/// }
417///```
418#[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/// Run test case when the path(file or folder) exist.
473/// ```
474/// #[cfg(test)]
475/// mod tests {
476///
477///     // etc exists
478///     #[test_with::path(/etc)]
479///     #[test]
480///     fn test_works() {
481///         assert!(true);
482///     }
483///
484///     // nothing does not exist
485///     #[test_with::path(/nothing)]
486///     #[test]
487///     fn test_ignored() {
488///         panic!("should be ignored")
489///     }
490///
491///     // etc and tmp exist
492///     #[test_with::path(/etc, /tmp)]
493///     #[test]
494///     fn test_works_too() {
495///         assert!(true);
496///     }
497/// }
498/// ```
499#[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/// Run test case when the example running and the path(file or folder) exist.
537///```rust
538/// // write as example in examples/*rs
539/// test_with::runner!(path);
540/// #[test_with::module]
541/// mod path {
542///     #[test_with::runtime_path(/etc)]
543///     fn test_works() {
544///         assert!(true);
545///     }
546/// }
547///```
548#[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/// Run test case when the http service exist.
603/// ```
604/// #[cfg(test)]
605/// mod tests {
606///
607///     // http service exists
608///     #[test_with::http(httpbin.org)]
609///     #[test]
610///     fn test_works() {
611///         assert!(true);
612///     }
613///
614///     // There is no not.exist.com
615///     #[test_with::http(not.exist.com)]
616///     #[test]
617///     fn test_ignored() {
618///         panic!("should be ignored")
619///     }
620/// }
621/// ```
622#[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/// Run test case when the example running and the http service exist.
663///```rust
664/// // write as example in examples/*rs
665/// test_with::runner!(http);
666/// #[test_with::module]
667/// mod http {
668///     #[test_with::runtime_http(httpbin.org)]
669///     fn test_works() {
670///         assert!(true);
671///     }
672/// }
673#[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/// Run test case when the https service exist.
730/// ```
731/// #[cfg(test)]
732/// mod tests {
733///
734///     // https server exists
735///     #[test_with::https(www.rust-lang.org)]
736///     #[test]
737///     fn test_works() {
738///         assert!(true);
739///     }
740///
741///     // There is no not.exist.com
742///     #[test_with::https(not.exist.com)]
743///     #[test]
744///     fn test_ignored() {
745///         panic!("should be ignored")
746///     }
747/// }
748/// ```
749#[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/// Run test case when the example running and the http service exist.
790///```rust
791/// // write as example in examples/*rs
792/// test_with::runner!(http);
793/// #[test_with::module]
794/// mod http {
795///     #[test_with::runtime_https(httpbin.org)]
796///     fn test_works() {
797///         assert!(true);
798///     }
799/// }
800#[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/// Run test case when the server online.
857/// Please make sure the role of test case runner have capability to open socket
858///
859/// ```
860/// #[cfg(test)]
861/// mod tests {
862///
863///     // localhost is online
864///     #[test_with::icmp(127.0.0.1)]
865///     #[test]
866///     fn test_works() {
867///         assert!(true);
868///     }
869///
870///     // 193.194.195.196 is offline
871///     #[test_with::icmp(193.194.195.196)]
872///     #[test]
873///     fn test_ignored() {
874///         panic!("should be ignored")
875///     }
876/// }
877/// ```
878#[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/// Run test case when the example running and the server online.
922/// Please make sure the role of test case runner have capability to open socket
923///```rust
924/// // write as example in examples/*rs
925/// test_with::runner!(icmp);
926/// #[test_with::module]
927/// mod icmp {
928///     // 193.194.195.196 is offline
929///     #[test_with::runtime_icmp(193.194.195.196)]
930///     fn test_ignored_with_non_existing_host() {
931///         panic!("should be ignored with non existing host")
932///     }
933/// }
934#[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/// Run test case when socket connected
991///
992/// ```
993/// #[cfg(test)]
994/// mod tests {
995///
996///     // Google DNS is online
997///     #[test_with::tcp(8.8.8.8:53)]
998///     #[test]
999///     fn test_works() {
1000///         assert!(true);
1001///     }
1002///
1003///     // 193.194.195.196 is offline
1004///     #[test_with::tcp(193.194.195.196)]
1005///     #[test]
1006///     fn test_ignored() {
1007///         panic!("should be ignored")
1008///     }
1009/// }
1010/// ```
1011#[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/// Run test case when the example running and socket connected
1049///```rust
1050/// // write as example in examples/*rs
1051/// test_with::runner!(tcp);
1052/// #[test_with::module]
1053/// mod tcp {
1054///     // Google DNS is online
1055///     #[test_with::runtime_tcp(8.8.8.8:53)]
1056///     fn test_works_with_DNS_server() {
1057///         assert!(true);
1058///     }
1059/// }
1060#[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/// Run test case when runner is root
1116///
1117/// ```
1118/// #[cfg(test)]
1119/// mod tests {
1120///
1121///     // Only works with root account
1122///     #[test_with::root()]
1123///     #[test]
1124///     fn test_ignored() {
1125///         panic!("should be ignored")
1126///     }
1127/// }
1128/// ```
1129#[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/// Run test case when runner is root
1158///```rust
1159/// // write as example in examples/*rs
1160/// test_with::runner!(user);
1161/// #[test_with::module]
1162/// mod user {
1163///     // Google DNS is online
1164///     #[test_with::runtime_root()]
1165///     fn test_ignored() {
1166///         panic!("should be ignored")
1167///     }
1168/// }
1169#[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/// Run test case when runner in group
1207///
1208/// ```
1209/// #[cfg(test)]
1210/// mod tests {
1211///
1212///     // Only works with group avengers
1213///     #[test_with::group(avengers)]
1214///     #[test]
1215///     fn test_ignored() {
1216///         panic!("should be ignored")
1217///     }
1218/// }
1219/// ```
1220#[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/// Run test case when runner in group
1264///```rust
1265/// // write as example in examples/*rs
1266/// test_with::runner!(user);
1267/// #[test_with::module]
1268/// mod user {
1269///     // Only works with group avengers
1270///     #[test_with::runtime_group(avengers)]
1271///     fn test_ignored() {
1272///         panic!("should be ignored")
1273///     }
1274/// }
1275#[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/// Run test case when runner is specific user
1332///
1333/// ```
1334/// #[cfg(test)]
1335/// mod tests {
1336///
1337///     // Only works with user
1338///     #[test_with::user(spider)]
1339///     #[test]
1340///     fn test_ignored() {
1341///         panic!("should be ignored")
1342///     }
1343/// }
1344/// ```
1345#[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/// Run test case when runner is specific user
1378///```rust
1379/// // write as example in examples/*rs
1380/// test_with::runner!(user);
1381/// #[test_with::module]
1382/// mod user {
1383///     // Only works with user
1384///     #[test_with::runtime_user(spider)]
1385///     fn test_ignored() {
1386///         panic!("should be ignored")
1387///     }
1388/// }
1389#[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/// Run test case when memory size enough
1436///
1437/// ```
1438/// #[cfg(test)]
1439/// mod tests {
1440///
1441///     // Only works with enough memory size
1442///     #[test_with::mem(100GB)]
1443///     #[test]
1444///     fn test_ignored() {
1445///         panic!("should be ignored")
1446///     }
1447/// }
1448/// ```
1449#[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/// Run test case when the example running and memory size enough
1489///```rust
1490/// // write as example in examples/*rs
1491/// test_with::runner!(resource);
1492/// #[test_with::module]
1493/// mod resource {
1494///     // Only works with enough memory size
1495///     #[test_with::runtime_mem(100GB)]
1496///     fn test_ignored_mem_not_enough() {
1497///         panic!("should be ignored")
1498///     }
1499/// }
1500#[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/// Run test case when the example running and free memory size enough
1554///```rust
1555/// // write as example in examples/*rs
1556/// test_with::runner!(resource);
1557/// #[test_with::module]
1558/// mod resource {
1559///     // Only works with enough free memory size
1560///     #[test_with::runtime_free_mem(100GB)]
1561///     fn test_ignored_free_mem_not_enough() {
1562///         panic!("should be ignored")
1563///     }
1564/// }
1565#[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/// Run test case when the example running and available memory size enough
1619///```rust
1620/// // write as example in examples/*rs
1621/// test_with::runner!(resource);
1622/// #[test_with::module]
1623/// mod resource {
1624///     // Only works with enough available memory size
1625///     #[test_with::runtime_available_mem(100GB)]
1626///     fn test_ignored_available_mem_not_enough() {
1627///         panic!("should be ignored")
1628///     }
1629/// }
1630#[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/// Run test case when swap size enough
1684///
1685/// ```
1686/// #[cfg(test)]
1687/// mod tests {
1688///
1689///     // Only works with enough swap size
1690///     #[test_with::swap(100GB)]
1691///     #[test]
1692///     fn test_ignored() {
1693///         panic!("should be ignored")
1694///     }
1695/// }
1696/// ```
1697#[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/// Run test case when the example running and swap enough
1737///```rust
1738/// // write as example in examples/*rs
1739/// test_with::runner!(resource);
1740/// #[test_with::module]
1741/// mod resource {
1742///     // Only works with enough swap size
1743///     #[test_with::runtime_swap(100GB)]
1744///     fn test_ignored_swap_not_enough() {
1745///         panic!("should be ignored")
1746///     }
1747/// }
1748#[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/// Run test case when the example running and free swap enough
1802///```rust
1803/// // write as example in examples/*rs
1804/// test_with::runner!(resource);
1805/// #[test_with::module]
1806/// mod resource {
1807///     // Only works with enough free swap size
1808///     #[test_with::runtime_free_swap(100GB)]
1809///     fn test_ignored_free_swap_not_enough() {
1810///         panic!("should be ignored")
1811///     }
1812/// }
1813#[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/// Run test case when cpu core enough
1867///
1868/// ```
1869/// #[cfg(test)]
1870/// mod tests {
1871///
1872///     // Only works with enough cpu core
1873///     #[test_with::cpu_core(32)]
1874///     #[test]
1875///     fn test_ignored() {
1876///         panic!("should be ignored")
1877///     }
1878/// }
1879/// ```
1880#[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/// Run test case when cpu core enough
1911///```rust
1912/// // write as example in examples/*rs
1913/// test_with::runner!(resource);
1914/// #[test_with::module]
1915/// mod resource {
1916///     // Only works with enough cpu core
1917///     #[test_with::runtime_cpu_core(32)]
1918///     fn test_ignored_core_not_enough() {
1919///         panic!("should be ignored")
1920///     }
1921/// }
1922#[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/// Run test case when physical cpu core enough
1969///
1970/// ```
1971/// #[cfg(test)]
1972/// mod tests {
1973///
1974///     // Only works with enough cpu core
1975///     #[test_with::phy_core(32)]
1976///     #[test]
1977///     fn test_ignored() {
1978///         panic!("should be ignored")
1979///     }
1980/// }
1981/// ```
1982#[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/// Run test case when physical core enough
2016///```rust
2017/// // write as example in examples/*rs
2018/// test_with::runner!(resource);
2019/// #[test_with::module]
2020/// mod resource {
2021///     // Only works with enough physical cpu core
2022///     #[test_with::runtime_phy_cpu_core(32)]
2023///     fn test_ignored_phy_core_not_enough() {
2024///         panic!("should be ignored")
2025///     }
2026/// }
2027#[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/// Run test case when the executables exist.
2074/// ```
2075/// #[cfg(test)]
2076/// mod tests {
2077///     // `pwd` executable command exists
2078///     #[test_with::executable(pwd)]
2079///     #[test]
2080///     fn test_executable() {
2081///         assert!(true);
2082///     }
2083///
2084///     // `/bin/sh` executable exists
2085///     #[test_with::executable(/bin/sh)]
2086///     #[test]
2087///     fn test_executable_with_path() {
2088///         assert!(true);
2089///     }
2090///
2091///     // `non` does not exist
2092///     #[test_with::executable(non)]
2093///     #[test]
2094///     fn test_non_existing_executable() {
2095///         panic!("should be ignored")
2096///     }
2097///
2098///     // `pwd` and `ls` exist
2099///     #[test_with::executable(pwd, ls)]
2100///     #[test]
2101///     fn test_executables_too() {
2102///         assert!(true);
2103///     }
2104/// }
2105/// ```
2106#[proc_macro_attribute]
2107#[proc_macro_error]
2108#[cfg(feature = "executable")]
2109pub fn executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
2110    if is_module(&stream) {
2111        mod_macro(
2112            attr,
2113            parse_macro_input!(stream as ItemMod),
2114            check_executable_condition,
2115        )
2116    } else {
2117        fn_macro(
2118            attr,
2119            parse_macro_input!(stream as ItemFn),
2120            check_executable_condition,
2121        )
2122    }
2123}
2124
2125#[cfg(feature = "executable")]
2126fn check_executable_condition(attr_str: String) -> (bool, String) {
2127    let executables: Vec<&str> = attr_str.split(',').collect();
2128    let mut missing_executables = vec![];
2129    for exe in executables.iter() {
2130        if which(exe.trim_matches('"')).is_err() {
2131            missing_executables.push(exe.to_string());
2132        }
2133    }
2134    let ignore_msg = if missing_executables.len() == 1 {
2135        format!("because executable not found: {}", missing_executables[0])
2136    } else {
2137        format!(
2138            "because following executables not found: \n{}\n",
2139            missing_executables.join("\n")
2140        )
2141    };
2142    (missing_executables.is_empty(), ignore_msg)
2143}
2144
2145/// Run test case when the executable existing
2146///```rust
2147/// // write as example in examples/*rs
2148/// test_with::runner!(exe);
2149/// #[test_with::module]
2150/// mod exe {
2151///     // `/bin/sh` executable exists
2152///     #[test_with::runtime_executable(/bin/sh)]
2153///     fn test_executable_with_path() {
2154///         assert!(true);
2155///     }
2156/// }
2157#[cfg(not(feature = "runtime"))]
2158#[proc_macro_attribute]
2159#[proc_macro_error]
2160pub fn runtime_executable(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2161    panic!("should be used with runtime feature")
2162}
2163#[cfg(all(feature = "runtime", feature = "executable"))]
2164#[proc_macro_attribute]
2165#[proc_macro_error]
2166pub fn runtime_executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
2167    let attr_str = attr.to_string().replace(' ', "");
2168    let executables: Vec<&str> = attr_str.split(',').collect();
2169    let ItemFn {
2170        attrs,
2171        vis,
2172        sig,
2173        block,
2174    } = parse_macro_input!(stream as ItemFn);
2175    let syn::Signature { ident, .. } = sig.clone();
2176    let check_ident = syn::Ident::new(
2177        &format!("_check_{}", ident.to_string()),
2178        proc_macro2::Span::call_site(),
2179    );
2180
2181    quote::quote! {
2182        fn #check_ident() -> Result<(), libtest_with::Failed> {
2183            let mut missing_executables = vec![];
2184            #(
2185                if libtest_with::which::which(#executables).is_err() {
2186                    missing_executables.push(#executables);
2187                }
2188            )*
2189            match missing_executables.len() {
2190                0 => {
2191                    #ident();
2192                    Ok(())
2193                },
2194                1 => Err(
2195                    format!("{}because executable {} not found",
2196                            libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables[0]
2197                ).into()),
2198                _ => Err(
2199                    format!("{}because following executables not found:\n{}\n",
2200                            libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables.join(", ")
2201                ).into()),
2202            }
2203        }
2204
2205        #(#attrs)*
2206        #vis #sig #block
2207
2208    }
2209    .into()
2210}
2211
2212/// Provide a test runner and test on each module
2213///```rust
2214/// // example/run-test.rs
2215///
2216/// test_with::runner!(module1, module2);
2217/// #[test_with::module]
2218/// mod module1 {
2219///     #[test_with::runtime_env(PWD)]
2220///     fn test_works() {
2221///         assert!(true);
2222///     }
2223/// }
2224///
2225/// #[test_with::module]
2226/// mod module2 {
2227///     #[test_with::runtime_env(PWD)]
2228///     fn test_works() {
2229///         assert!(true);
2230///     }
2231/// }
2232///```
2233#[cfg(not(feature = "runtime"))]
2234#[proc_macro]
2235pub fn runner(_input: TokenStream) -> TokenStream {
2236    panic!("should be used with runtime feature")
2237}
2238#[cfg(feature = "runtime")]
2239#[proc_macro]
2240pub fn runner(input: TokenStream) -> TokenStream {
2241    let input_str = input.to_string();
2242    let mod_names: Vec<syn::Ident> = input_str
2243        .split(",")
2244        .map(|s| syn::Ident::new(s.trim(), proc_macro2::Span::call_site()))
2245        .collect();
2246    quote::quote! {
2247        fn main() {
2248            let args = libtest_with::Arguments::from_args();
2249            let mut no_env_tests = Vec::new();
2250            #(
2251                match #mod_names::_runtime_tests() {
2252                    (Some(env), tests) => {
2253                        libtest_with::run(&args, tests).exit_if_failed();
2254                        drop(env);
2255                    },
2256                    (None, mut tests) => no_env_tests.append(&mut tests),
2257                }
2258            )*
2259            libtest_with::run(&args, no_env_tests).exit();
2260        }
2261    }
2262    .into()
2263}
2264
2265/// Help each function with `#[test_with::runtime_*]` in the module can register to run
2266/// Also you can set up a mock instance for all of the test in the module
2267///
2268/// ```rust
2269///  // example/run-test.rs
2270///
2271///  test_with::runner!(module1, module2);
2272///  #[test_with::module]
2273///  mod module1 {
2274///      #[test_with::runtime_env(PWD)]
2275///      fn test_works() {
2276///          assert!(true);
2277///      }
2278///  }
2279///
2280///  #[test_with::module]
2281///  mod module2 {
2282///      #[test_with::runtime_env(PWD)]
2283///      fn test_works() {
2284///          assert!(true);
2285///      }
2286///  }
2287/// ```
2288/// You can set up mock with a public struct named `TestEnv` inside the module, or a public type
2289/// named `TestEnv` inside the module.  And the type or struct should have a Default trait for
2290/// initialize the mock instance.
2291/// ```rust
2292/// use std::ops::Drop;
2293/// use std::process::{Child, Command};
2294///
2295/// test_with::runner!(net);
2296///
2297/// #[test_with::module]
2298/// mod net {
2299///     pub struct TestEnv {
2300///         p: Child,
2301///     }
2302///
2303///     impl Default for TestEnv {
2304///         fn default() -> TestEnv {
2305///             let p = Command::new("python")
2306///                 .args(["-m", "http.server"])
2307///                 .spawn()
2308///                 .expect("failed to execute child");
2309///             let mut count = 0;
2310///             while count < 3 {
2311///                 if libtest_with::reqwest::blocking::get("http://127.0.0.1:8000").is_ok() {
2312///                     break;
2313///                 }
2314///                 std::thread::sleep(std::time::Duration::from_secs(1));
2315///                 count += 1;
2316///             }
2317///             TestEnv { p }
2318///         }
2319///     }
2320///
2321///     impl Drop for TestEnv {
2322///         fn drop(&mut self) {
2323///             self.p.kill().expect("fail to kill python http.server");
2324///         }
2325///     }
2326///
2327///     #[test_with::runtime_http(127.0.0.1:8000)]
2328///     fn test_with_environment() {
2329///         assert!(true);
2330///     }
2331/// }
2332///
2333/// ```
2334/// or you can write mock struct in other place and just pass by type.
2335/// ```rust
2336/// use std::ops::Drop;
2337/// use std::process::{Child, Command};
2338///
2339/// test_with::runner!(net);
2340///
2341/// pub struct Moc {
2342///     p: Child,
2343/// }
2344///
2345/// impl Default for Moc {
2346///     fn default() -> Moc {
2347///         let p = Command::new("python")
2348///             .args(["-m", "http.server"])
2349///             .spawn()
2350///             .expect("failed to execute child");
2351///         let mut count = 0;
2352///         while count < 3 {
2353///             if libtest_with::reqwest::blocking::get("http://127.0.0.1:8000").is_ok() {
2354///                 break;
2355///             }
2356///             std::thread::sleep(std::time::Duration::from_secs(1));
2357///             count += 1;
2358///         }
2359///         Moc { p }
2360///     }
2361/// }
2362///
2363/// impl Drop for Moc {
2364///     fn drop(&mut self) {
2365///         self.p.kill().expect("fail to kill python http.server");
2366///     }
2367/// }
2368///
2369/// #[test_with::module]
2370/// mod net {
2371///     pub type TestEnv = super::Moc;
2372///
2373///     #[test_with::runtime_http(127.0.0.1:8000)]
2374///     fn test_with_environment() {
2375///         assert!(true);
2376///     }
2377/// }
2378/// ```
2379#[cfg(not(feature = "runtime"))]
2380#[proc_macro_attribute]
2381#[proc_macro_error]
2382pub fn module(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2383    panic!("should be used with runtime feature")
2384}
2385#[cfg(feature = "runtime")]
2386#[proc_macro_attribute]
2387#[proc_macro_error]
2388pub fn module(_attr: TokenStream, stream: TokenStream) -> TokenStream {
2389    let ItemMod {
2390        attrs,
2391        vis,
2392        mod_token,
2393        ident,
2394        content,
2395        ..
2396    } = parse_macro_input!(stream as ItemMod);
2397
2398    if let Some(content) = content {
2399        let content = content.1;
2400        if crate::utils::has_test_cfg(&attrs) {
2401            abort_call_site!("should not use `#[cfg(test)]` on the mod with `#[test_with::module]`")
2402        } else {
2403            let mut test_env_type = None;
2404            let test_names: Vec<String> = content
2405                .iter()
2406                .filter_map(|c| match c {
2407                    Item::Fn(ItemFn {
2408                        sig: syn::Signature { ident, .. },
2409                        attrs,
2410                        ..
2411                    }) => match crate::utils::test_with_attrs(&attrs) {
2412                        (true, true, _) => abort_call_site!(
2413                            "should not use #[test] for method in `#[test_with::module]`"
2414                        ),
2415                        (_, true, false) => abort_call_site!(
2416                            "use `#[test_with::runtime_*]` for method in `#[test_with::module]`"
2417                        ),
2418                        (false, true, true) => Some(ident.to_string()),
2419                        _ => None,
2420                    },
2421                    Item::Struct(ItemStruct { ident, vis, .. })
2422                    | Item::Type(ItemType { ident, vis, .. }) => {
2423                        if ident.to_string() == "TestEnv" {
2424                            match vis {
2425                                syn::Visibility::Public(_) => test_env_type = Some(ident),
2426                                _ => abort_call_site!("TestEnv should be pub for testing"),
2427                            }
2428                        }
2429                        None
2430                    }
2431                    _ => None,
2432                })
2433                .collect();
2434            let check_names: Vec<syn::Ident> = test_names
2435                .iter()
2436                .map(|c| {
2437                    syn::Ident::new(
2438                        &format!("_check_{}", c.to_string()),
2439                        proc_macro2::Span::call_site(),
2440                    )
2441                })
2442                .collect();
2443            if let Some(test_env_type) = test_env_type {
2444                quote::quote! {
2445                    #(#attrs)*
2446                    #vis #mod_token #ident {
2447                        use super::*;
2448                        pub fn _runtime_tests() -> (Option<#test_env_type>, Vec<libtest_with::Trial>) {
2449                            use libtest_with::Trial;
2450                            (
2451                                Some(#test_env_type::default()),
2452                                vec![
2453                                    #(Trial::test(#test_names, #check_names),)*
2454                                ]
2455                            )
2456                        }
2457                        #(#content)*
2458                    }
2459                }
2460                .into()
2461            } else {
2462                quote::quote! {
2463                    #(#attrs)*
2464                    #vis #mod_token #ident {
2465                        use super::*;
2466                        pub fn _runtime_tests() -> (Option<()>, Vec<libtest_with::Trial>) {
2467                            use libtest_with::Trial;
2468                            (
2469                                None,
2470                                vec![
2471                                    #(Trial::test(#test_names, #check_names),)*
2472                                ]
2473                            )
2474                        }
2475                        #(#content)*
2476                    }
2477                }
2478                .into()
2479            }
2480        }
2481    } else {
2482        abort_call_site!("should use on mod with context")
2483    }
2484}
2485
2486/// Ignore test case when function return some reason
2487/// The function should be `fn() -> Option<String>`
2488/// ```
2489/// test_with::runner!(custom_mod);
2490///
2491/// fn something_happened() -> Option<String> {
2492///     Some("because something happened".to_string())
2493/// }
2494///
2495/// #[test_with::module]
2496/// mod custom_mod {
2497/// #[test_with::runtime_ignore_if(something_happened)]
2498/// fn test_ignored() {
2499///     assert!(false);
2500///     }
2501/// }
2502/// ```
2503#[cfg(not(feature = "runtime"))]
2504#[proc_macro_attribute]
2505#[proc_macro_error]
2506pub fn runtime_ignore_if(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2507    panic!("should be used with runtime feature")
2508}
2509#[cfg(feature = "runtime")]
2510#[proc_macro_attribute]
2511#[proc_macro_error]
2512pub fn runtime_ignore_if(attr: TokenStream, stream: TokenStream) -> TokenStream {
2513    let ignore_function = syn::Ident::new(
2514        &attr.to_string().replace(' ', ""),
2515        proc_macro2::Span::call_site(),
2516    );
2517    let ItemFn {
2518        attrs,
2519        vis,
2520        sig,
2521        block,
2522    } = parse_macro_input!(stream as ItemFn);
2523    let syn::Signature { ident, .. } = sig.clone();
2524    let check_ident = syn::Ident::new(
2525        &format!("_check_{}", ident.to_string()),
2526        proc_macro2::Span::call_site(),
2527    );
2528    quote::quote! {
2529        fn #check_ident() -> Result<(), libtest_with::Failed> {
2530            if let Some(msg) = #ignore_function() {
2531                Err(format!("{}{msg}", libtest_with::RUNTIME_IGNORE_PREFIX).into())
2532            } else {
2533                #ident();
2534                Ok(())
2535            }
2536        }
2537
2538        #(#attrs)*
2539        #vis #sig #block
2540    }
2541    .into()
2542}
2543
2544#[cfg(test)]
2545mod tests {
2546    use super::{check_env_condition, check_no_env_condition};
2547
2548    mod env_macro {
2549        use super::*;
2550
2551        #[test]
2552        fn single_env_var_should_be_not_set() {
2553            //* Given
2554            let env_var = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2555
2556            // The `test_with::env(<attr_str>)` macro arguments
2557            let attr_str = env_var.to_string();
2558
2559            //* When
2560            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2561
2562            //* Then
2563            // Assert if the test should be ignored
2564            assert!(!is_ok);
2565            // Assert the ignore message should contain only the missing env var names
2566            assert!(ignore_msg.contains(env_var));
2567        }
2568
2569        #[test]
2570        fn multiple_env_vars_should_not_be_set() {
2571            //* Given
2572            let env_var1 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2573            let env_var2 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2574
2575            // The `test_with::env(<attr_str>)` macro arguments
2576            let attr_str = format!("{}, {}", env_var1, env_var2);
2577
2578            //* When
2579            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2580
2581            //* Then
2582            // Assert if the test should be ignored
2583            assert!(!is_ok);
2584            // Assert the ignore message should contain only the missing env var names
2585            assert!(ignore_msg.contains(env_var1));
2586            assert!(ignore_msg.contains(env_var2));
2587        }
2588
2589        #[test]
2590        fn single_env_var_should_be_set() {
2591            //* Given
2592            let env_var = "PATH";
2593
2594            // The `test_with::env(<attr_str>)` macro arguments
2595            let attr_str = env_var.to_string();
2596
2597            //* When
2598            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2599
2600            //* Then
2601            // Assert if the test should be ignored
2602            assert!(is_ok);
2603            // Assert the ignore message should contain only the missing env var names
2604            assert!(!ignore_msg.contains(env_var));
2605        }
2606
2607        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2608        /// when the attribute string contains multiple env vars containing spaces and newlines.
2609        ///
2610        /// ```no_run
2611        /// #[test_with::env(
2612        ///   PATH,
2613        ///   HOME
2614        /// )]
2615        /// #[test]
2616        /// fn some_test() {}
2617        #[test]
2618        fn multiple_env_vars_should_be_set() {
2619            //* Given
2620            let env_var1 = "PATH";
2621            let env_var2 = "HOME";
2622
2623            // The `test_with::env(<attr_str>)` macro arguments
2624            let attr_str = format!("\t{},\n\t{}\n", env_var1, env_var2);
2625
2626            //* When
2627            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2628
2629            //* Then
2630            // Assert if the test should be ignored
2631            assert!(is_ok);
2632            // Assert the ignore message should contain only the missing env var names
2633            assert!(!ignore_msg.contains(env_var1));
2634            assert!(!ignore_msg.contains(env_var2));
2635        }
2636
2637        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2638        /// when the attribute string contains multiple env vars and one of them is not set.
2639        #[test]
2640        fn multiple_env_vars_but_one_is_not_set() {
2641            //* Given
2642            let env_var1 = "PATH";
2643            let env_var2 = "HOME";
2644            let env_var3 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2645
2646            // The `test_with::env(<attr_str>)` macro arguments
2647            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2648
2649            //* When
2650            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2651
2652            //* Then
2653            // Assert if the test should be ignored
2654            assert!(!is_ok);
2655            // Assert the ignore message should contain only the missing env var names
2656            assert!(!ignore_msg.contains(env_var1));
2657            assert!(!ignore_msg.contains(env_var2));
2658            assert!(ignore_msg.contains(env_var3));
2659        }
2660
2661        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2662        /// when the attribute string contains multiple env vars and various of them are not set.
2663        #[test]
2664        fn multiple_env_vars_and_various_not_set() {
2665            //* Given
2666            let env_var1 = "PATH";
2667            let env_var2 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2668            let env_var3 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2669
2670            // The `test_with::env(<attr_str>)` macro arguments
2671            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2672
2673            //* When
2674            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2675
2676            //* Then
2677            // Assert if the test should be ignored
2678            assert!(!is_ok);
2679            // Assert the ignore message should contain only the missing env var names
2680            assert!(!ignore_msg.contains(env_var1));
2681            assert!(ignore_msg.contains(env_var2));
2682            assert!(ignore_msg.contains(env_var3));
2683        }
2684    }
2685
2686    mod no_env_macro {
2687        use super::*;
2688
2689        #[test]
2690        fn single_env_var_not_set() {
2691            //* Given
2692            let env_var = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2693
2694            // The `test_with::env(<attr_str>)` macro arguments
2695            let attr_str = env_var.to_string();
2696
2697            //* When
2698            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2699
2700            //* Then
2701            // Assert if the test should be ignored
2702            assert!(is_ok);
2703            // Assert the ignore message should contain only the found env var names
2704            assert!(!ignore_msg.contains(env_var));
2705        }
2706
2707        #[test]
2708        fn multiple_env_vars_not_set() {
2709            //* Given
2710            let env_var1 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2711            let env_var2 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2712
2713            // The `test_with::env(<attr_str>)` macro arguments
2714            let attr_str = format!("{}, {}", env_var1, env_var2);
2715
2716            //* When
2717            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2718
2719            //* Then
2720            // Assert if the test should be ignored
2721            assert!(is_ok);
2722            // Assert the ignore message should contain only the found env var names
2723            assert!(!ignore_msg.contains(env_var1));
2724            assert!(!ignore_msg.contains(env_var2));
2725        }
2726
2727        #[test]
2728        fn single_env_var_set() {
2729            //* Given
2730            let env_var = "PATH";
2731
2732            // The `test_with::env(<attr_str>)` macro arguments
2733            let attr_str = env_var.to_string();
2734
2735            //* When
2736            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2737
2738            //* Then
2739            // Assert if the test should be ignored
2740            assert!(!is_ok);
2741            // Assert the ignore message should contain only the found env var names
2742            assert!(ignore_msg.contains(env_var));
2743        }
2744
2745        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2746        /// when the attribute string contains multiple env vars containing spaces and newlines.
2747        ///
2748        /// ```no_run
2749        /// #[test_with::no_env(
2750        ///   PATH,
2751        ///   HOME
2752        /// )]
2753        /// #[test]
2754        /// fn some_test() {}
2755        #[test]
2756        fn multiple_env_vars_set() {
2757            //* Given
2758            let env_var1 = "PATH";
2759            let env_var2 = "HOME";
2760
2761            // The `test_with::env(<attr_str>)` macro arguments
2762            let attr_str = format!("\t{},\n\t{}\n", env_var1, env_var2);
2763
2764            //* When
2765            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2766
2767            //* Then
2768            // Assert if the test should be ignored
2769            assert!(!is_ok);
2770            // Assert the ignore message should contain only the found env var names
2771            assert!(ignore_msg.contains(env_var1));
2772            assert!(ignore_msg.contains(env_var2));
2773        }
2774
2775        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2776        /// when the attribute string contains multiple env vars and one of them is set.
2777        #[test]
2778        fn multiple_env_vars_but_one_is_set() {
2779            //* Given
2780            let env_var1 = "PATH";
2781            let env_var2 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2782            let env_var3 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2783
2784            // The `test_with::env(<attr_str>)` macro arguments
2785            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2786
2787            //* When
2788            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2789
2790            //* Then
2791            // Assert if the test should be ignored
2792            assert!(!is_ok);
2793            // Assert the ignore message should contain only the found env var names
2794            assert!(ignore_msg.contains(env_var1));
2795            assert!(!ignore_msg.contains(env_var2));
2796            assert!(!ignore_msg.contains(env_var3));
2797        }
2798
2799        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2800        /// when the attribute string contains multiple env vars and various of them are set.
2801        #[test]
2802        fn multiple_env_vars_and_various_are_set() {
2803            //* Given
2804            let env_var1 = "PATH";
2805            let env_var2 = "HOME";
2806            let env_var3 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2807
2808            // The `test_with::env(<attr_str>)` macro arguments
2809            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2810
2811            //* When
2812            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2813
2814            //* Then
2815            // Assert if the test should be ignored
2816            assert!(!is_ok);
2817            // Assert the ignore message should contain only the found env var names
2818            assert!(ignore_msg.contains(env_var1));
2819            assert!(ignore_msg.contains(env_var2));
2820            assert!(!ignore_msg.contains(env_var3));
2821        }
2822    }
2823}
2824
2825/// Run test case one by one when the lock is acquired
2826/// It will automatically implement a file lock for the test case to prevent it run in the same
2827/// time. Also, you can pass the second parameter to specific the waiting seconds, default will be
2828/// 60 seconds.
2829/// ```
2830/// #[cfg(test)]
2831/// mod tests {
2832///
2833///     // `LOCK` is file based lock to prevent test1 an test2 run at the same time
2834///     #[test_with::lock(LOCK)]
2835///     #[test]
2836///     fn test_1() {
2837///         assert!(true);
2838///     }
2839///
2840///     // `LOCK` is file based lock to prevent test1 an test2 run at the same time
2841///     #[test_with::lock(LOCK)]
2842///     #[test]
2843///     fn test_2() {
2844///         assert!(true);
2845///     }
2846///
2847///     // `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
2848///     // waiting time.
2849///     #[test_with::lock(ANOTHER_LOCK, 3)]
2850///     fn test_3() {
2851///         assert!(true);
2852///     }
2853///
2854///     // `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
2855///     // waiting time.
2856///     #[test_with::lock(ANOTHER_LOCK, 3)]
2857///     fn test_4() {
2858///         assert!(true);
2859///     }
2860///
2861/// }
2862#[proc_macro_attribute]
2863#[proc_macro_error]
2864pub fn lock(attr: TokenStream, stream: TokenStream) -> TokenStream {
2865    if is_module(&stream) {
2866        abort_call_site!("#[test_with::lock] only works with fn")
2867    } else {
2868        lock_macro(attr, parse_macro_input!(stream as ItemFn))
2869    }
2870}
2871
2872/// Run test case when the timezone is expected.
2873/// ```
2874/// #[cfg(test)]
2875/// mod tests {
2876///
2877///     // 0 means UTC
2878///     #[test_with::timezone(0)]
2879///     #[test]
2880///     fn test_works() {
2881///         assert!(true);
2882///     }
2883///
2884///     // UTC is GMT+0
2885///     #[test_with::timezone(UTC)]
2886///     #[test]
2887///     fn test_works_too() {
2888///         assert!(true);
2889///     }
2890///
2891///     // +8 means GMT+8
2892///     #[test_with::timezone(+8)]
2893///     #[test]
2894///     fn test_ignored() {
2895///         panic!("should be ignored")
2896///     }
2897///
2898///     // HKT GMT+8
2899///     #[test_with::timezone(HKT)]
2900///     #[test]
2901///     fn test_ignored_too() {
2902///         panic!("should be ignored")
2903///     }
2904/// }
2905/// ```
2906#[cfg(feature = "timezone")]
2907#[proc_macro_attribute]
2908#[proc_macro_error]
2909pub fn timezone(attr: TokenStream, stream: TokenStream) -> TokenStream {
2910    if is_module(&stream) {
2911        mod_macro(
2912            attr,
2913            parse_macro_input!(stream as ItemMod),
2914            check_tz_condition,
2915        )
2916    } else {
2917        fn_macro(
2918            attr,
2919            parse_macro_input!(stream as ItemFn),
2920            check_tz_condition,
2921        )
2922    }
2923}
2924
2925#[cfg(feature = "timezone")]
2926fn check_timezone(attr_str: &String) -> (bool, Vec<&str>) {
2927    let mut incorrect_tzs = vec![];
2928    let mut match_tz = false;
2929    let current_tz = chrono::Local::now().offset().local_minus_utc() / 60;
2930
2931    for tz in attr_str.split(',') {
2932        let parsed_tz = match tz {
2933            "NZDT" => Ok(13 * 60),
2934            "NZST" => Ok(12 * 60),
2935            "AEDT" => Ok(11 * 60),
2936            "ACDT" => Ok(10 * 60 + 30),
2937            "AEST" => Ok(10 * 60),
2938            "ACST" => Ok(9 * 60 + 30),
2939            "KST" | "JST" => Ok(9 * 60),
2940            "HKT" | "WITA" | "AWST" => Ok(8 * 60),
2941            "PST" => abort_call_site!("PST can be GMT+8 or GMT-8, please use +8 or -8 instead"),
2942            "WIB" => Ok(7 * 60),
2943            "CST" => abort_call_site!("PST can be GMT+8 or GMT-6, please use +8 or -6 instead"),
2944            "5.5" | "+5.5" => Ok(5 * 60 + 30),
2945            "IST" => abort_call_site!(
2946                "IST can be GMT+5.5, GMT+2 or GMT+1, please use +5.5, 2 or 1 instead"
2947            ),
2948            "PKT" => Ok(5 * 60),
2949            "EAT" | "EEST" | "IDT" | "MSK" => Ok(3 * 60),
2950            "CAT" | "EET" | "CEST" | "SAST" => Ok(2 * 60),
2951            "CET" | "WAT" | "WEST" | "BST" => Ok(1 * 60),
2952            "UTC" | "GMT" | "WET" => Ok(0),
2953            "NDT" | "-2.5" => Ok(-2 * 60 - 30),
2954            "NST" | "-3.5" => Ok(-3 * 60 - 30),
2955            "ADT" => Ok(-3 * 60),
2956            "AST" | "EDT" => Ok(-4 * 60),
2957            "EST" | "CDT" => Ok(-5 * 60),
2958            "MDT" => Ok(-6 * 60),
2959            "MST" | "PDT" => Ok(-7 * 60),
2960            "AKDT" => Ok(-8 * 60),
2961            "HDT" | "AKST" => Ok(-9 * 60),
2962            "HST" => Ok(-10 * 60),
2963            _ => tz.parse::<i32>().map(|tz| tz * 60),
2964        };
2965        if let Ok(parsed_tz) = parsed_tz {
2966            match_tz |= current_tz == parsed_tz;
2967        } else {
2968            incorrect_tzs.push(tz);
2969        }
2970    }
2971    (match_tz, incorrect_tzs)
2972}
2973
2974#[cfg(feature = "timezone")]
2975fn check_tz_condition(attr_str: String) -> (bool, String) {
2976    let (match_tz, incorrect_tzs) = check_timezone(&attr_str);
2977
2978    // Generate ignore message
2979    if incorrect_tzs.len() == 1 {
2980        (
2981            false,
2982            format!("because timezone {} is incorrect", incorrect_tzs[0]),
2983        )
2984    } else if incorrect_tzs.len() > 1 {
2985        (
2986            false,
2987            format!(
2988                "because following timezones are incorrect:\n{}\n",
2989                incorrect_tzs.join(", ")
2990            ),
2991        )
2992    } else if match_tz {
2993        (true, String::new())
2994    } else {
2995        (
2996            false,
2997            format!(
2998                "because the test case not run in following timezone:\n{}\n",
2999                attr_str
3000            ),
3001        )
3002    }
3003}
3004
3005/// Run test case when the example running within specific timzones.
3006///```rust
3007/// // write as example in examples/*rs
3008/// test_with::runner!(timezone);
3009/// #[test_with::module]
3010/// mod timezone {
3011///     // 0 means UTC timezone
3012///     #[test_with::runtime_timezone(0)]
3013///     fn test_works() {
3014///         assert!(true);
3015///     }
3016/// }
3017#[cfg(not(feature = "runtime"))]
3018#[proc_macro_attribute]
3019#[proc_macro_error]
3020pub fn runtime_timezone(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
3021    panic!("should be used with runtime feature")
3022}
3023
3024#[cfg(all(feature = "runtime", feature = "timezone"))]
3025#[proc_macro_attribute]
3026#[proc_macro_error]
3027pub fn runtime_timezone(attr: TokenStream, stream: TokenStream) -> TokenStream {
3028    let attr_str = attr.to_string();
3029    let ItemFn {
3030        attrs,
3031        vis,
3032        sig,
3033        block,
3034    } = parse_macro_input!(stream as ItemFn);
3035    let syn::Signature { ident, .. } = sig.clone();
3036    let check_ident = syn::Ident::new(
3037        &format!("_check_{}", ident.to_string()),
3038        proc_macro2::Span::call_site(),
3039    );
3040    quote::quote! {
3041        fn #check_ident() -> Result<(), libtest_with::Failed> {
3042
3043            let mut incorrect_tzs = vec![];
3044            let mut match_tz = false;
3045            let current_tz = libtest_with::chrono::Local::now().offset().local_minus_utc() / 60;
3046            for tz in #attr_str.split(',') {
3047                if let Ok(parsed_tz) = tz.parse::<i32>() {
3048                    match_tz |= current_tz == parsed_tz;
3049                } else {
3050                    incorrect_tzs.push(tz);
3051                }
3052            }
3053
3054            if match_tz && incorrect_tzs.is_empty() {
3055                    #ident();
3056                    Ok(())
3057            } else if incorrect_tzs.len() == 1 {
3058                Err(
3059                    format!("{}because timezone {} is incorrect",
3060                            libtest_with::RUNTIME_IGNORE_PREFIX, incorrect_tzs[0]
3061                ).into())
3062            } else if incorrect_tzs.len() > 1 {
3063                Err(
3064                    format!("{}because following timezones are incorrect:\n{:?}\n",
3065                            libtest_with::RUNTIME_IGNORE_PREFIX, incorrect_tzs
3066                ).into())
3067            } else {
3068                Err(
3069                    format!(
3070                    "{}because the test case not run in following timezone:\n{}\n",
3071                    libtest_with::RUNTIME_IGNORE_PREFIX,
3072                    #attr_str
3073                ).into())
3074            }
3075        }
3076
3077        #(#attrs)*
3078        #vis #sig #block
3079    }
3080    .into()
3081}