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///     // `non` or `ls` exist
2106///     #[test_with::executable(non || ls)]
2107///     #[test]
2108///     fn test_one_of_executables_exist() {
2109///         assert!(true);
2110///     }
2111/// }
2112/// ```
2113#[proc_macro_attribute]
2114#[proc_macro_error]
2115#[cfg(feature = "executable")]
2116pub fn executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
2117    if is_module(&stream) {
2118        mod_macro(
2119            attr,
2120            parse_macro_input!(stream as ItemMod),
2121            check_executable_condition,
2122        )
2123    } else {
2124        fn_macro(
2125            attr,
2126            parse_macro_input!(stream as ItemFn),
2127            check_executable_condition,
2128        )
2129    }
2130}
2131
2132#[cfg(feature = "executable")]
2133fn check_executable_and_condition(attr_str: String) -> (bool, String) {
2134    let executables: Vec<&str> = attr_str.split(',').collect();
2135    let mut missing_executables = vec![];
2136    for exe in executables.iter() {
2137        if which(exe.trim_matches('"')).is_err() {
2138            missing_executables.push(exe.to_string());
2139        }
2140    }
2141    let ignore_msg = if missing_executables.len() == 1 {
2142        format!("because executable not found: {}", missing_executables[0])
2143    } else {
2144        format!(
2145            "because following executables not found: \n{}\n",
2146            missing_executables.join("\n")
2147        )
2148    };
2149    (missing_executables.is_empty(), ignore_msg)
2150}
2151
2152#[cfg(feature = "executable")]
2153fn check_executable_or_condition(attr_str: String) -> (bool, String) {
2154    let executables: Vec<&str> = attr_str.split("||").collect();
2155    for exe in executables.iter() {
2156        if which(exe.trim_matches('"')).is_ok() {
2157            return (true, String::new());
2158        }
2159    }
2160    (
2161        false,
2162        format!("because none of executables can be found: {}", attr_str),
2163    )
2164}
2165
2166#[cfg(feature = "executable")]
2167fn check_executable_condition(attr_str: String) -> (bool, String) {
2168    let has_and_cond = attr_str.contains(',');
2169    let has_or_cond = attr_str.contains("||");
2170    if has_and_cond && has_or_cond {
2171        abort_call_site!("',' and '||' can not be used at the same time")
2172    } else if has_or_cond {
2173        check_executable_or_condition(attr_str)
2174    } else {
2175        check_executable_and_condition(attr_str)
2176    }
2177}
2178
2179/// Run test case when the executable existing
2180///```rust
2181/// // write as example in examples/*rs
2182/// test_with::runner!(exe);
2183/// #[test_with::module]
2184/// mod exe {
2185///     // `/bin/sh` executable exists
2186///     #[test_with::runtime_executable(/bin/sh)]
2187///     fn test_executable_with_path() {
2188///         assert!(true);
2189///     }
2190/// }
2191#[cfg(not(feature = "runtime"))]
2192#[proc_macro_attribute]
2193#[proc_macro_error]
2194pub fn runtime_executable(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2195    panic!("should be used with runtime feature")
2196}
2197#[cfg(all(feature = "runtime", feature = "executable"))]
2198#[proc_macro_attribute]
2199#[proc_macro_error]
2200pub fn runtime_executable(attr: TokenStream, stream: TokenStream) -> TokenStream {
2201    let attr_str = attr.to_string().replace(' ', "");
2202    let has_and_cond = attr_str.contains(',');
2203    let has_or_cond = attr_str.contains("||");
2204    if has_and_cond && has_or_cond {
2205        abort_call_site!("',' and '||' can not be used at the same time")
2206    }
2207    let executables: Vec<&str> = if has_or_cond {
2208        attr_str.split("||").collect()
2209    } else {
2210        attr_str.split(',').collect()
2211    };
2212    let ItemFn {
2213        attrs,
2214        vis,
2215        sig,
2216        block,
2217    } = parse_macro_input!(stream as ItemFn);
2218    let syn::Signature { ident, .. } = sig.clone();
2219    let check_ident = syn::Ident::new(
2220        &format!("_check_{}", ident.to_string()),
2221        proc_macro2::Span::call_site(),
2222    );
2223
2224    if has_or_cond {
2225        quote::quote! {
2226            fn #check_ident() -> Result<(), libtest_with::Failed> {
2227                #(
2228                    if libtest_with::which::which(#executables).is_ok() {
2229                        #ident();
2230                        return Ok(());
2231                    }
2232                )*
2233                Err(format!("{}because none of executables can be found:\n{}\n",
2234                    libtest_with::RUNTIME_IGNORE_PREFIX, attr_str).into())
2235            }
2236
2237            #(#attrs)*
2238            #vis #sig #block
2239
2240        }
2241        .into()
2242    } else {
2243        quote::quote! {
2244            fn #check_ident() -> Result<(), libtest_with::Failed> {
2245                let mut missing_executables = vec![];
2246                #(
2247                    if libtest_with::which::which(#executables).is_err() {
2248                        missing_executables.push(#executables);
2249                    }
2250                )*
2251                match missing_executables.len() {
2252                    0 => {
2253                        #ident();
2254                        Ok(())
2255                    },
2256                    1 => Err(
2257                        format!("{}because executable {} not found",
2258                                libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables[0]
2259                    ).into()),
2260                    _ => Err(
2261                        format!("{}because following executables not found:\n{}\n",
2262                                libtest_with::RUNTIME_IGNORE_PREFIX, missing_executables.join(", ")
2263                    ).into()),
2264                }
2265            }
2266
2267            #(#attrs)*
2268            #vis #sig #block
2269
2270        }
2271        .into()
2272    }
2273}
2274
2275/// Provide a test runner and test on each module
2276///```rust
2277/// // example/run-test.rs
2278///
2279/// test_with::runner!(module1, module2);
2280/// #[test_with::module]
2281/// mod module1 {
2282///     #[test_with::runtime_env(PWD)]
2283///     fn test_works() {
2284///         assert!(true);
2285///     }
2286/// }
2287///
2288/// #[test_with::module]
2289/// mod module2 {
2290///     #[test_with::runtime_env(PWD)]
2291///     fn test_works() {
2292///         assert!(true);
2293///     }
2294/// }
2295///```
2296#[cfg(not(feature = "runtime"))]
2297#[proc_macro]
2298pub fn runner(_input: TokenStream) -> TokenStream {
2299    panic!("should be used with runtime feature")
2300}
2301#[cfg(feature = "runtime")]
2302#[proc_macro]
2303pub fn runner(input: TokenStream) -> TokenStream {
2304    let input_str = input.to_string();
2305    let mod_names: Vec<syn::Ident> = input_str
2306        .split(",")
2307        .map(|s| syn::Ident::new(s.trim(), proc_macro2::Span::call_site()))
2308        .collect();
2309    quote::quote! {
2310        fn main() {
2311            let args = libtest_with::Arguments::from_args();
2312            let mut no_env_tests = Vec::new();
2313            #(
2314                match #mod_names::_runtime_tests() {
2315                    (Some(env), tests) => {
2316                        libtest_with::run(&args, tests).exit_if_failed();
2317                        drop(env);
2318                    },
2319                    (None, mut tests) => no_env_tests.append(&mut tests),
2320                }
2321            )*
2322            libtest_with::run(&args, no_env_tests).exit();
2323        }
2324    }
2325    .into()
2326}
2327
2328/// Help each function with `#[test_with::runtime_*]` in the module can register to run
2329/// Also you can set up a mock instance for all of the test in the module
2330///
2331/// ```rust
2332///  // example/run-test.rs
2333///
2334///  test_with::runner!(module1, module2);
2335///  #[test_with::module]
2336///  mod module1 {
2337///      #[test_with::runtime_env(PWD)]
2338///      fn test_works() {
2339///          assert!(true);
2340///      }
2341///  }
2342///
2343///  #[test_with::module]
2344///  mod module2 {
2345///      #[test_with::runtime_env(PWD)]
2346///      fn test_works() {
2347///          assert!(true);
2348///      }
2349///  }
2350/// ```
2351/// You can set up mock with a public struct named `TestEnv` inside the module, or a public type
2352/// named `TestEnv` inside the module.  And the type or struct should have a Default trait for
2353/// initialize the mock instance.
2354/// ```rust
2355/// use std::ops::Drop;
2356/// use std::process::{Child, Command};
2357///
2358/// test_with::runner!(net);
2359///
2360/// #[test_with::module]
2361/// mod net {
2362///     pub struct TestEnv {
2363///         p: Child,
2364///     }
2365///
2366///     impl Default for TestEnv {
2367///         fn default() -> TestEnv {
2368///             let p = Command::new("python")
2369///                 .args(["-m", "http.server"])
2370///                 .spawn()
2371///                 .expect("failed to execute child");
2372///             let mut count = 0;
2373///             while count < 3 {
2374///                 if libtest_with::reqwest::blocking::get("http://127.0.0.1:8000").is_ok() {
2375///                     break;
2376///                 }
2377///                 std::thread::sleep(std::time::Duration::from_secs(1));
2378///                 count += 1;
2379///             }
2380///             TestEnv { p }
2381///         }
2382///     }
2383///
2384///     impl Drop for TestEnv {
2385///         fn drop(&mut self) {
2386///             self.p.kill().expect("fail to kill python http.server");
2387///         }
2388///     }
2389///
2390///     #[test_with::runtime_http(127.0.0.1:8000)]
2391///     fn test_with_environment() {
2392///         assert!(true);
2393///     }
2394/// }
2395///
2396/// ```
2397/// or you can write mock struct in other place and just pass by type.
2398/// ```rust
2399/// use std::ops::Drop;
2400/// use std::process::{Child, Command};
2401///
2402/// test_with::runner!(net);
2403///
2404/// pub struct Moc {
2405///     p: Child,
2406/// }
2407///
2408/// impl Default for Moc {
2409///     fn default() -> Moc {
2410///         let p = Command::new("python")
2411///             .args(["-m", "http.server"])
2412///             .spawn()
2413///             .expect("failed to execute child");
2414///         let mut count = 0;
2415///         while count < 3 {
2416///             if libtest_with::reqwest::blocking::get("http://127.0.0.1:8000").is_ok() {
2417///                 break;
2418///             }
2419///             std::thread::sleep(std::time::Duration::from_secs(1));
2420///             count += 1;
2421///         }
2422///         Moc { p }
2423///     }
2424/// }
2425///
2426/// impl Drop for Moc {
2427///     fn drop(&mut self) {
2428///         self.p.kill().expect("fail to kill python http.server");
2429///     }
2430/// }
2431///
2432/// #[test_with::module]
2433/// mod net {
2434///     pub type TestEnv = super::Moc;
2435///
2436///     #[test_with::runtime_http(127.0.0.1:8000)]
2437///     fn test_with_environment() {
2438///         assert!(true);
2439///     }
2440/// }
2441/// ```
2442#[cfg(not(feature = "runtime"))]
2443#[proc_macro_attribute]
2444#[proc_macro_error]
2445pub fn module(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2446    panic!("should be used with runtime feature")
2447}
2448#[cfg(feature = "runtime")]
2449#[proc_macro_attribute]
2450#[proc_macro_error]
2451pub fn module(_attr: TokenStream, stream: TokenStream) -> TokenStream {
2452    let ItemMod {
2453        attrs,
2454        vis,
2455        mod_token,
2456        ident,
2457        content,
2458        ..
2459    } = parse_macro_input!(stream as ItemMod);
2460
2461    if let Some(content) = content {
2462        let content = content.1;
2463        if crate::utils::has_test_cfg(&attrs) {
2464            abort_call_site!("should not use `#[cfg(test)]` on the mod with `#[test_with::module]`")
2465        } else {
2466            let mut test_env_type = None;
2467            let test_names: Vec<String> = content
2468                .iter()
2469                .filter_map(|c| match c {
2470                    Item::Fn(ItemFn {
2471                        sig: syn::Signature { ident, .. },
2472                        attrs,
2473                        ..
2474                    }) => match crate::utils::test_with_attrs(&attrs) {
2475                        (true, true, _) => abort_call_site!(
2476                            "should not use #[test] for method in `#[test_with::module]`"
2477                        ),
2478                        (_, true, false) => abort_call_site!(
2479                            "use `#[test_with::runtime_*]` for method in `#[test_with::module]`"
2480                        ),
2481                        (false, true, true) => Some(ident.to_string()),
2482                        _ => None,
2483                    },
2484                    Item::Struct(ItemStruct { ident, vis, .. })
2485                    | Item::Type(ItemType { ident, vis, .. }) => {
2486                        if ident.to_string() == "TestEnv" {
2487                            match vis {
2488                                syn::Visibility::Public(_) => test_env_type = Some(ident),
2489                                _ => abort_call_site!("TestEnv should be pub for testing"),
2490                            }
2491                        }
2492                        None
2493                    }
2494                    _ => None,
2495                })
2496                .collect();
2497            let check_names: Vec<syn::Ident> = test_names
2498                .iter()
2499                .map(|c| {
2500                    syn::Ident::new(
2501                        &format!("_check_{}", c.to_string()),
2502                        proc_macro2::Span::call_site(),
2503                    )
2504                })
2505                .collect();
2506            if let Some(test_env_type) = test_env_type {
2507                quote::quote! {
2508                    #(#attrs)*
2509                    #vis #mod_token #ident {
2510                        use super::*;
2511                        pub fn _runtime_tests() -> (Option<#test_env_type>, Vec<libtest_with::Trial>) {
2512                            use libtest_with::Trial;
2513                            (
2514                                Some(#test_env_type::default()),
2515                                vec![
2516                                    #(Trial::test(#test_names, #check_names),)*
2517                                ]
2518                            )
2519                        }
2520                        #(#content)*
2521                    }
2522                }
2523                .into()
2524            } else {
2525                quote::quote! {
2526                    #(#attrs)*
2527                    #vis #mod_token #ident {
2528                        use super::*;
2529                        pub fn _runtime_tests() -> (Option<()>, Vec<libtest_with::Trial>) {
2530                            use libtest_with::Trial;
2531                            (
2532                                None,
2533                                vec![
2534                                    #(Trial::test(#test_names, #check_names),)*
2535                                ]
2536                            )
2537                        }
2538                        #(#content)*
2539                    }
2540                }
2541                .into()
2542            }
2543        }
2544    } else {
2545        abort_call_site!("should use on mod with context")
2546    }
2547}
2548
2549/// Ignore test case when function return some reason
2550/// The function should be `fn() -> Option<String>`
2551/// ```
2552/// test_with::runner!(custom_mod);
2553///
2554/// fn something_happened() -> Option<String> {
2555///     Some("because something happened".to_string())
2556/// }
2557///
2558/// #[test_with::module]
2559/// mod custom_mod {
2560/// #[test_with::runtime_ignore_if(something_happened)]
2561/// fn test_ignored() {
2562///     assert!(false);
2563///     }
2564/// }
2565/// ```
2566#[cfg(not(feature = "runtime"))]
2567#[proc_macro_attribute]
2568#[proc_macro_error]
2569pub fn runtime_ignore_if(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
2570    panic!("should be used with runtime feature")
2571}
2572#[cfg(feature = "runtime")]
2573#[proc_macro_attribute]
2574#[proc_macro_error]
2575pub fn runtime_ignore_if(attr: TokenStream, stream: TokenStream) -> TokenStream {
2576    let ignore_function = syn::Ident::new(
2577        &attr.to_string().replace(' ', ""),
2578        proc_macro2::Span::call_site(),
2579    );
2580    let ItemFn {
2581        attrs,
2582        vis,
2583        sig,
2584        block,
2585    } = parse_macro_input!(stream as ItemFn);
2586    let syn::Signature { ident, .. } = sig.clone();
2587    let check_ident = syn::Ident::new(
2588        &format!("_check_{}", ident.to_string()),
2589        proc_macro2::Span::call_site(),
2590    );
2591    quote::quote! {
2592        fn #check_ident() -> Result<(), libtest_with::Failed> {
2593            if let Some(msg) = #ignore_function() {
2594                Err(format!("{}{msg}", libtest_with::RUNTIME_IGNORE_PREFIX).into())
2595            } else {
2596                #ident();
2597                Ok(())
2598            }
2599        }
2600
2601        #(#attrs)*
2602        #vis #sig #block
2603    }
2604    .into()
2605}
2606
2607#[cfg(test)]
2608mod tests {
2609    use super::{check_env_condition, check_no_env_condition};
2610
2611    mod env_macro {
2612        use super::*;
2613
2614        #[test]
2615        fn single_env_var_should_be_not_set() {
2616            //* Given
2617            let env_var = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2618
2619            // The `test_with::env(<attr_str>)` macro arguments
2620            let attr_str = env_var.to_string();
2621
2622            //* When
2623            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2624
2625            //* Then
2626            // Assert if the test should be ignored
2627            assert!(!is_ok);
2628            // Assert the ignore message should contain only the missing env var names
2629            assert!(ignore_msg.contains(env_var));
2630        }
2631
2632        #[test]
2633        fn multiple_env_vars_should_not_be_set() {
2634            //* Given
2635            let env_var1 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2636            let env_var2 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2637
2638            // The `test_with::env(<attr_str>)` macro arguments
2639            let attr_str = format!("{}, {}", env_var1, env_var2);
2640
2641            //* When
2642            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2643
2644            //* Then
2645            // Assert if the test should be ignored
2646            assert!(!is_ok);
2647            // Assert the ignore message should contain only the missing env var names
2648            assert!(ignore_msg.contains(env_var1));
2649            assert!(ignore_msg.contains(env_var2));
2650        }
2651
2652        #[test]
2653        fn single_env_var_should_be_set() {
2654            //* Given
2655            let env_var = "PATH";
2656
2657            // The `test_with::env(<attr_str>)` macro arguments
2658            let attr_str = env_var.to_string();
2659
2660            //* When
2661            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2662
2663            //* Then
2664            // Assert if the test should be ignored
2665            assert!(is_ok);
2666            // Assert the ignore message should contain only the missing env var names
2667            assert!(!ignore_msg.contains(env_var));
2668        }
2669
2670        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2671        /// when the attribute string contains multiple env vars containing spaces and newlines.
2672        ///
2673        /// ```no_run
2674        /// #[test_with::env(
2675        ///   PATH,
2676        ///   HOME
2677        /// )]
2678        /// #[test]
2679        /// fn some_test() {}
2680        #[test]
2681        fn multiple_env_vars_should_be_set() {
2682            //* Given
2683            let env_var1 = "PATH";
2684            let env_var2 = "HOME";
2685
2686            // The `test_with::env(<attr_str>)` macro arguments
2687            let attr_str = format!("\t{},\n\t{}\n", env_var1, env_var2);
2688
2689            //* When
2690            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2691
2692            //* Then
2693            // Assert if the test should be ignored
2694            assert!(is_ok);
2695            // Assert the ignore message should contain only the missing env var names
2696            assert!(!ignore_msg.contains(env_var1));
2697            assert!(!ignore_msg.contains(env_var2));
2698        }
2699
2700        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2701        /// when the attribute string contains multiple env vars and one of them is not set.
2702        #[test]
2703        fn multiple_env_vars_but_one_is_not_set() {
2704            //* Given
2705            let env_var1 = "PATH";
2706            let env_var2 = "HOME";
2707            let env_var3 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2708
2709            // The `test_with::env(<attr_str>)` macro arguments
2710            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2711
2712            //* When
2713            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2714
2715            //* Then
2716            // Assert if the test should be ignored
2717            assert!(!is_ok);
2718            // Assert the ignore message should contain only the missing env var names
2719            assert!(!ignore_msg.contains(env_var1));
2720            assert!(!ignore_msg.contains(env_var2));
2721            assert!(ignore_msg.contains(env_var3));
2722        }
2723
2724        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2725        /// when the attribute string contains multiple env vars and various of them are not set.
2726        #[test]
2727        fn multiple_env_vars_and_various_not_set() {
2728            //* Given
2729            let env_var1 = "PATH";
2730            let env_var2 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2731            let env_var3 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2732
2733            // The `test_with::env(<attr_str>)` macro arguments
2734            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2735
2736            //* When
2737            let (is_ok, ignore_msg) = check_env_condition(attr_str);
2738
2739            //* Then
2740            // Assert if the test should be ignored
2741            assert!(!is_ok);
2742            // Assert the ignore message should contain only the missing env var names
2743            assert!(!ignore_msg.contains(env_var1));
2744            assert!(ignore_msg.contains(env_var2));
2745            assert!(ignore_msg.contains(env_var3));
2746        }
2747    }
2748
2749    mod no_env_macro {
2750        use super::*;
2751
2752        #[test]
2753        fn single_env_var_not_set() {
2754            //* Given
2755            let env_var = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2756
2757            // The `test_with::env(<attr_str>)` macro arguments
2758            let attr_str = env_var.to_string();
2759
2760            //* When
2761            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2762
2763            //* Then
2764            // Assert if the test should be ignored
2765            assert!(is_ok);
2766            // Assert the ignore message should contain only the found env var names
2767            assert!(!ignore_msg.contains(env_var));
2768        }
2769
2770        #[test]
2771        fn multiple_env_vars_not_set() {
2772            //* Given
2773            let env_var1 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2774            let env_var2 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2775
2776            // The `test_with::env(<attr_str>)` macro arguments
2777            let attr_str = format!("{}, {}", env_var1, env_var2);
2778
2779            //* When
2780            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2781
2782            //* Then
2783            // Assert if the test should be ignored
2784            assert!(is_ok);
2785            // Assert the ignore message should contain only the found env var names
2786            assert!(!ignore_msg.contains(env_var1));
2787            assert!(!ignore_msg.contains(env_var2));
2788        }
2789
2790        #[test]
2791        fn single_env_var_set() {
2792            //* Given
2793            let env_var = "PATH";
2794
2795            // The `test_with::env(<attr_str>)` macro arguments
2796            let attr_str = env_var.to_string();
2797
2798            //* When
2799            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2800
2801            //* Then
2802            // Assert if the test should be ignored
2803            assert!(!is_ok);
2804            // Assert the ignore message should contain only the found env var names
2805            assert!(ignore_msg.contains(env_var));
2806        }
2807
2808        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2809        /// when the attribute string contains multiple env vars containing spaces and newlines.
2810        ///
2811        /// ```no_run
2812        /// #[test_with::no_env(
2813        ///   PATH,
2814        ///   HOME
2815        /// )]
2816        /// #[test]
2817        /// fn some_test() {}
2818        #[test]
2819        fn multiple_env_vars_set() {
2820            //* Given
2821            let env_var1 = "PATH";
2822            let env_var2 = "HOME";
2823
2824            // The `test_with::env(<attr_str>)` macro arguments
2825            let attr_str = format!("\t{},\n\t{}\n", env_var1, env_var2);
2826
2827            //* When
2828            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2829
2830            //* Then
2831            // Assert if the test should be ignored
2832            assert!(!is_ok);
2833            // Assert the ignore message should contain only the found env var names
2834            assert!(ignore_msg.contains(env_var1));
2835            assert!(ignore_msg.contains(env_var2));
2836        }
2837
2838        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2839        /// when the attribute string contains multiple env vars and one of them is set.
2840        #[test]
2841        fn multiple_env_vars_but_one_is_set() {
2842            //* Given
2843            let env_var1 = "PATH";
2844            let env_var2 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2845            let env_var3 = "ANOTHER_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2846
2847            // The `test_with::env(<attr_str>)` macro arguments
2848            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2849
2850            //* When
2851            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2852
2853            //* Then
2854            // Assert if the test should be ignored
2855            assert!(!is_ok);
2856            // Assert the ignore message should contain only the found env var names
2857            assert!(ignore_msg.contains(env_var1));
2858            assert!(!ignore_msg.contains(env_var2));
2859            assert!(!ignore_msg.contains(env_var3));
2860        }
2861
2862        /// Test the `test_with::env(<attr_str>)` macro should parse the attribute string correctly
2863        /// when the attribute string contains multiple env vars and various of them are set.
2864        #[test]
2865        fn multiple_env_vars_and_various_are_set() {
2866            //* Given
2867            let env_var1 = "PATH";
2868            let env_var2 = "HOME";
2869            let env_var3 = "A_RIDICULOUS_ENV_VAR_NAME_THAT_SHOULD_NOT_BE_SET";
2870
2871            // The `test_with::env(<attr_str>)` macro arguments
2872            let attr_str = format!("{}, {}, {}", env_var1, env_var2, env_var3);
2873
2874            //* When
2875            let (is_ok, ignore_msg) = check_no_env_condition(attr_str);
2876
2877            //* Then
2878            // Assert if the test should be ignored
2879            assert!(!is_ok);
2880            // Assert the ignore message should contain only the found env var names
2881            assert!(ignore_msg.contains(env_var1));
2882            assert!(ignore_msg.contains(env_var2));
2883            assert!(!ignore_msg.contains(env_var3));
2884        }
2885    }
2886}
2887
2888/// Run test case one by one when the lock is acquired
2889/// It will automatically implement a file lock for the test case to prevent it run in the same
2890/// time. Also, you can pass the second parameter to specific the waiting seconds, default will be
2891/// 60 seconds.
2892/// ```
2893/// #[cfg(test)]
2894/// mod tests {
2895///
2896///     // `LOCK` is file based lock to prevent test1 an test2 run at the same time
2897///     #[test_with::lock(LOCK)]
2898///     #[test]
2899///     fn test_1() {
2900///         assert!(true);
2901///     }
2902///
2903///     // `LOCK` is file based lock to prevent test1 an test2 run at the same time
2904///     #[test_with::lock(LOCK)]
2905///     #[test]
2906///     fn test_2() {
2907///         assert!(true);
2908///     }
2909///
2910///     // `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
2911///     // waiting time.
2912///     #[test_with::lock(ANOTHER_LOCK, 3)]
2913///     fn test_3() {
2914///         assert!(true);
2915///     }
2916///
2917///     // `ANOTHER_LOCK` is file based lock to prevent test3 an test4 run at the same time with 3 sec
2918///     // waiting time.
2919///     #[test_with::lock(ANOTHER_LOCK, 3)]
2920///     fn test_4() {
2921///         assert!(true);
2922///     }
2923///
2924/// }
2925#[proc_macro_attribute]
2926#[proc_macro_error]
2927pub fn lock(attr: TokenStream, stream: TokenStream) -> TokenStream {
2928    if is_module(&stream) {
2929        abort_call_site!("#[test_with::lock] only works with fn")
2930    } else {
2931        lock_macro(attr, parse_macro_input!(stream as ItemFn))
2932    }
2933}
2934
2935/// Run test case when the timezone is expected.
2936/// ```
2937/// #[cfg(test)]
2938/// mod tests {
2939///
2940///     // 0 means UTC
2941///     #[test_with::timezone(0)]
2942///     #[test]
2943///     fn test_works() {
2944///         assert!(true);
2945///     }
2946///
2947///     // UTC is GMT+0
2948///     #[test_with::timezone(UTC)]
2949///     #[test]
2950///     fn test_works_too() {
2951///         assert!(true);
2952///     }
2953///
2954///     // +8 means GMT+8
2955///     #[test_with::timezone(+8)]
2956///     #[test]
2957///     fn test_ignored() {
2958///         panic!("should be ignored")
2959///     }
2960///
2961///     // HKT GMT+8
2962///     #[test_with::timezone(HKT)]
2963///     #[test]
2964///     fn test_ignored_too() {
2965///         panic!("should be ignored")
2966///     }
2967/// }
2968/// ```
2969#[cfg(feature = "timezone")]
2970#[proc_macro_attribute]
2971#[proc_macro_error]
2972pub fn timezone(attr: TokenStream, stream: TokenStream) -> TokenStream {
2973    if is_module(&stream) {
2974        mod_macro(
2975            attr,
2976            parse_macro_input!(stream as ItemMod),
2977            check_tz_condition,
2978        )
2979    } else {
2980        fn_macro(
2981            attr,
2982            parse_macro_input!(stream as ItemFn),
2983            check_tz_condition,
2984        )
2985    }
2986}
2987
2988#[cfg(feature = "timezone")]
2989fn check_timezone(attr_str: &String) -> (bool, Vec<&str>) {
2990    let mut incorrect_tzs = vec![];
2991    let mut match_tz = false;
2992    let current_tz = chrono::Local::now().offset().local_minus_utc() / 60;
2993
2994    for tz in attr_str.split(',') {
2995        let parsed_tz = match tz {
2996            "NZDT" => Ok(13 * 60),
2997            "NZST" => Ok(12 * 60),
2998            "AEDT" => Ok(11 * 60),
2999            "ACDT" => Ok(10 * 60 + 30),
3000            "AEST" => Ok(10 * 60),
3001            "ACST" => Ok(9 * 60 + 30),
3002            "KST" | "JST" => Ok(9 * 60),
3003            "HKT" | "WITA" | "AWST" => Ok(8 * 60),
3004            "PST" => abort_call_site!("PST can be GMT+8 or GMT-8, please use +8 or -8 instead"),
3005            "WIB" => Ok(7 * 60),
3006            "CST" => abort_call_site!("PST can be GMT+8 or GMT-6, please use +8 or -6 instead"),
3007            "5.5" | "+5.5" => Ok(5 * 60 + 30),
3008            "IST" => abort_call_site!(
3009                "IST can be GMT+5.5, GMT+2 or GMT+1, please use +5.5, 2 or 1 instead"
3010            ),
3011            "PKT" => Ok(5 * 60),
3012            "EAT" | "EEST" | "IDT" | "MSK" => Ok(3 * 60),
3013            "CAT" | "EET" | "CEST" | "SAST" => Ok(2 * 60),
3014            "CET" | "WAT" | "WEST" | "BST" => Ok(1 * 60),
3015            "UTC" | "GMT" | "WET" => Ok(0),
3016            "NDT" | "-2.5" => Ok(-2 * 60 - 30),
3017            "NST" | "-3.5" => Ok(-3 * 60 - 30),
3018            "ADT" => Ok(-3 * 60),
3019            "AST" | "EDT" => Ok(-4 * 60),
3020            "EST" | "CDT" => Ok(-5 * 60),
3021            "MDT" => Ok(-6 * 60),
3022            "MST" | "PDT" => Ok(-7 * 60),
3023            "AKDT" => Ok(-8 * 60),
3024            "HDT" | "AKST" => Ok(-9 * 60),
3025            "HST" => Ok(-10 * 60),
3026            _ => tz.parse::<i32>().map(|tz| tz * 60),
3027        };
3028        if let Ok(parsed_tz) = parsed_tz {
3029            match_tz |= current_tz == parsed_tz;
3030        } else {
3031            incorrect_tzs.push(tz);
3032        }
3033    }
3034    (match_tz, incorrect_tzs)
3035}
3036
3037#[cfg(feature = "timezone")]
3038fn check_tz_condition(attr_str: String) -> (bool, String) {
3039    let (match_tz, incorrect_tzs) = check_timezone(&attr_str);
3040
3041    // Generate ignore message
3042    if incorrect_tzs.len() == 1 {
3043        (
3044            false,
3045            format!("because timezone {} is incorrect", incorrect_tzs[0]),
3046        )
3047    } else if incorrect_tzs.len() > 1 {
3048        (
3049            false,
3050            format!(
3051                "because following timezones are incorrect:\n{}\n",
3052                incorrect_tzs.join(", ")
3053            ),
3054        )
3055    } else if match_tz {
3056        (true, String::new())
3057    } else {
3058        (
3059            false,
3060            format!(
3061                "because the test case not run in following timezone:\n{}\n",
3062                attr_str
3063            ),
3064        )
3065    }
3066}
3067
3068/// Run test case when the example running within specific timzones.
3069///```rust
3070/// // write as example in examples/*rs
3071/// test_with::runner!(timezone);
3072/// #[test_with::module]
3073/// mod timezone {
3074///     // 0 means UTC timezone
3075///     #[test_with::runtime_timezone(0)]
3076///     fn test_works() {
3077///         assert!(true);
3078///     }
3079/// }
3080#[cfg(not(feature = "runtime"))]
3081#[proc_macro_attribute]
3082#[proc_macro_error]
3083pub fn runtime_timezone(_attr: TokenStream, _stream: TokenStream) -> TokenStream {
3084    panic!("should be used with runtime feature")
3085}
3086
3087#[cfg(all(feature = "runtime", feature = "timezone"))]
3088#[proc_macro_attribute]
3089#[proc_macro_error]
3090pub fn runtime_timezone(attr: TokenStream, stream: TokenStream) -> TokenStream {
3091    let attr_str = attr.to_string();
3092    let ItemFn {
3093        attrs,
3094        vis,
3095        sig,
3096        block,
3097    } = parse_macro_input!(stream as ItemFn);
3098    let syn::Signature { ident, .. } = sig.clone();
3099    let check_ident = syn::Ident::new(
3100        &format!("_check_{}", ident.to_string()),
3101        proc_macro2::Span::call_site(),
3102    );
3103    quote::quote! {
3104        fn #check_ident() -> Result<(), libtest_with::Failed> {
3105
3106            let mut incorrect_tzs = vec![];
3107            let mut match_tz = false;
3108            let current_tz = libtest_with::chrono::Local::now().offset().local_minus_utc() / 60;
3109            for tz in #attr_str.split(',') {
3110                if let Ok(parsed_tz) = tz.parse::<i32>() {
3111                    match_tz |= current_tz == parsed_tz;
3112                } else {
3113                    incorrect_tzs.push(tz);
3114                }
3115            }
3116
3117            if match_tz && incorrect_tzs.is_empty() {
3118                    #ident();
3119                    Ok(())
3120            } else if incorrect_tzs.len() == 1 {
3121                Err(
3122                    format!("{}because timezone {} is incorrect",
3123                            libtest_with::RUNTIME_IGNORE_PREFIX, incorrect_tzs[0]
3124                ).into())
3125            } else if incorrect_tzs.len() > 1 {
3126                Err(
3127                    format!("{}because following timezones are incorrect:\n{:?}\n",
3128                            libtest_with::RUNTIME_IGNORE_PREFIX, incorrect_tzs
3129                ).into())
3130            } else {
3131                Err(
3132                    format!(
3133                    "{}because the test case not run in following timezone:\n{}\n",
3134                    libtest_with::RUNTIME_IGNORE_PREFIX,
3135                    #attr_str
3136                ).into())
3137            }
3138        }
3139
3140        #(#attrs)*
3141        #vis #sig #block
3142    }
3143    .into()
3144}