test_generator/
lib.rs

1//! # Overview
2//! This crate provides `#[test_resources]` and `#[bench_resources]` procedural macro attributes
3//! that generates multiple parametrized tests using one body with different resource input parameters.
4//! A test is generated for each resource matching the specific resource location pattern.
5//!
6//! [![Crates.io](https://img.shields.io/crates/v/test-generator.svg)](https://crates.io/crates/test-generator)
7//! [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/frehberg/test-generator/blob/master/LICENSE-MIT)
8//! [![Apache License](http://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/frehberg/test-generator/blob/master/LICENSE-APACHE)
9//! [![Example](http://img.shields.io/badge/crate-Example-red.svg)](https://github.com/frehberg/test-generator/tree/master/example)
10//!
11//! [Documentation](https://docs.rs/test-generator/)
12//!
13//! [Repository](https://github.com/frehberg/test-generator/)
14//!
15//! # Getting Started
16//!
17//! First of all you have to add this dependency to your `Cargo.toml`:
18//!
19//! ```toml
20//! [dev-dependencies]
21//! test-generator = "^0.3"
22//! ```
23//! The test-functionality is supports stable Rust since version 1.30,
24//! whereas the bench-functionality requires an API from unstable nightly release.
25//!
26//! ```ignore
27//! #![cfg(test)]
28//! extern crate test_generator;
29//!
30//! // Don't forget that procedural macros are imported with `use` statement,
31//! // for example importing the macro 'test_resources'
32//! #![cfg(test)]
33//! use test_generator::test_resources;
34//! ```
35//!
36//! # Example usage `test`:
37//!
38//! The `test` functionality supports the stable release of Rust-compiler since version 1.30.
39//!
40//! ```ignore
41//! #![cfg(test)]
42//! extern crate test_generator;
43//!
44//! use test_generator::test_resources;
45//!
46//! #[test_resources("res/*/input.txt")]
47//! fn verify_resource(resource: &str) {
48//!    assert!(std::path::Path::new(resource).exists());
49//! }
50//! ```
51//!
52//! Output from `cargo test` for 3 test-input-files matching the pattern, for this example:
53//!
54//! ```console
55//! $ cargo test
56//!
57//! running 3 tests
58//! test tests::verify_resource_res_set1_input_txt ... ok
59//! test tests::verify_resource_res_set2_input_txt ... ok
60//! test tests::verify_resource_res_set3_input_txt ... ok
61//!
62//! test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
63//! ```
64//! # Example usage `bench`:
65//!
66//! The `bench` functionality requires the nightly release of the Rust-compiler.
67//!
68//! ```ignore
69//! #![feature(test)] // nightly feature required for API test::Bencher
70//!
71//! #[macro_use]
72//! extern crate test_generator;
73//!
74//! extern crate test; /* required for test::Bencher */
75//!
76//! mod bench {
77//!     #[bench_resources("res/*/input.txt")]
78//!     fn measure_resource(b: &mut test::Bencher, resource: &str) {
79//!         let path = std::path::Path::new(resource);
80//!         b.iter(|| path.exists());
81//!     }
82//! }
83//! ```
84//! Output from `cargo +nightly bench` for 3 bench-input-files matching the pattern, for this example:
85//!
86//! ```console
87//! running 3 tests
88//! test bench::measure_resource_res_set1_input_txt ... bench:       2,492 ns/iter (+/- 4,027)
89//! test bench::measure_resource_res_set2_input_txt ... bench:       2,345 ns/iter (+/- 2,167)
90//! test bench::measure_resource_res_set3_input_txt ... bench:       2,269 ns/iter (+/- 1,527)
91//!
92//! test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured; 0 filtered out
93//! ```
94//!
95//! # Example
96//! The [example](https://github.com/frehberg/test-generator/tree/master/example) demonstrates usage
97//! and configuration of these macros, in combination with the crate
98//! `build-deps` monitoring for any change of these resource files and conditional rebuild.
99//!
100//! # Internals
101//! Let's assume the following code and 3 files matching the pattern "res/*/input.txt"
102//! ```ignore
103//! #[test_resources("res/*/input.txt")]
104//! fn verify_resource(resource: &str) { assert!(std::path::Path::new(resource).exists()); }
105//! ```
106//! the generated code for this input resource will look like
107//! ```
108//! #[test]
109//! #[allow(non_snake_case)]
110//! fn verify_resource_res_set1_input_txt() { verify_resource("res/set1/input.txt".into()); }
111//! #[test]
112//! #[allow(non_snake_case)]
113//! fn verify_resource_res_set2_input_txt() { verify_resource("res/set2/input.txt".into()); }
114//! #[test]
115//! #[allow(non_snake_case)]
116//! fn verify_resource_res_set3_input_txt() { verify_resource("res/set3/input.txt".into()); }
117//! ```
118//! Note: The trailing `into()` method-call permits users to implement the `Into`-Trait for auto-conversations.
119//!
120extern crate glob;
121extern crate proc_macro;
122
123use proc_macro::TokenStream;
124
125use self::glob::{glob, Paths};
126use quote::quote;
127use std::path::PathBuf;
128use syn::parse::{Parse, ParseStream, Result};
129use syn::{parse_macro_input, Expr, ExprLit, Ident, Lit, Token, ItemFn};
130
131// Form canonical name without any punctuation/delimiter or special character
132fn canonical_fn_name(s: &str) -> String {
133    // remove delimiters and special characters
134    s.replace(
135        &['"', ' ', '.', ':', '-', '*', '/', '\\', '\n', '\t', '\r'][..],
136        "_",
137    )
138}
139
140/// Return the concatenation of two token-streams
141fn concat_ts_cnt(
142    accu: (u64, proc_macro2::TokenStream),
143    other: proc_macro2::TokenStream,
144) -> (u64, proc_macro2::TokenStream) {
145    let (accu_cnt, accu_ts) = accu;
146    (accu_cnt + 1, quote! { #accu_ts #other })
147}
148
149/// MacroAttributes elements
150struct MacroAttributes {
151    glob_pattern: Lit,
152}
153
154/// MacroAttributes parser
155impl Parse for MacroAttributes {
156    fn parse(input: ParseStream) -> Result<Self> {
157        let glob_pattern: Lit = input.parse()?;
158        if ! input.is_empty() {
159            panic!("found multiple parameters, expected one");
160        }
161
162        Ok(MacroAttributes {
163            glob_pattern,
164        })
165    }
166}
167
168/// Macro generating test-functions, invoking the fn for each item matching the resource-pattern.
169///
170/// The resource-pattern must not expand to empty list, otherwise an error is raised.
171/// The generated test-functions is aregular tests, being compiled by the rust-compiler; and being
172/// executed in parallel by the test-framework.
173/// ```
174/// #[cfg(test)]
175/// extern crate test_generator;
176///
177/// #[cfg(test)]
178/// mod tests {
179///   use test_generator::test_resources;
180///
181///   #[test_resources("res/*/input.txt")]
182///   fn verify_resource(resource: &str) {
183///      assert!(std::path::Path::new(resource).exists());
184///   }
185/// }
186/// ```
187/// Assuming the following package layout with test file `mytests.rs` and resource folder `res/`,
188/// the output below will be printed on console. The functionality of `build.rs` is explained at crate
189/// [build-deps](https://crates.io/crates/build-deps) and demonstrated with
190/// [example](https://github.com/frehberg/test-generator/tree/master/example)
191///
192/// ```ignore
193/// ├── build.rs
194/// ├── Cargo.toml
195/// ├── res
196/// │   ├── set1
197/// │   │   ├── expect.txt
198/// │   │   └── input.txt
199/// │   ├── set2
200/// │   │   ├── expect.txt
201/// │   │   └── input.txt
202/// │   └── set3
203/// │       ├── expect.txt
204/// │       └── input.txt
205/// ├── src
206/// │   └── main.rs
207/// ├── benches
208/// │   └── mybenches.rs
209/// └── tests
210///     └── mytests.rs
211/// ```
212/// Producing the following test output
213///
214/// ```ignore
215/// $ cargo test
216///
217/// running 3 tests
218/// test tests::verify_resource_res_set1_input_txt ... ok
219/// test tests::verify_resource_res_set2_input_txt ... ok
220/// test tests::verify_resource_res_set3_input_txt ... ok
221///
222/// test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
223/// ```
224#[proc_macro_attribute]
225pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream {
226    let MacroAttributes { glob_pattern } = parse_macro_input!(attrs as MacroAttributes);
227
228    let pattern = match glob_pattern {
229        Lit::Str(l) => l.value(),
230        Lit::Bool(l) => panic!("expected string parameter, got '{}'", &l.value),
231        Lit::Byte(l) => panic!("expected string parameter, got '{}'", &l.value()),
232        Lit::ByteStr(_) => panic!("expected string parameter, got byte-string"),
233        Lit::Char(l) => panic!("expected string parameter, got '{}'", &l.value()),
234        Lit::Int(l) => panic!("expected string parameter, got '{}'", &l.value()),
235        Lit::Float(l) => panic!("expected string parameter, got '{}'", &l.value()),
236        _ => panic!("expected string parameter"),
237    };
238
239    let func_copy: proc_macro2::TokenStream = func.clone().into();
240
241    let func_ast: ItemFn = syn::parse(func)
242        .expect("failed to parse tokens as a function");
243
244    let func_ident = func_ast.ident;
245
246    let paths: Paths = glob(&pattern).expect(&format!("No such file or directory {}", &pattern));
247
248    // for each path generate a test-function and fold them to single tokenstream
249    let result = paths
250        .map(|path| {
251            let path_as_str = path
252                .expect("No such file or directory")
253                .into_os_string()
254                .into_string()
255                .expect("bad encoding");
256            let test_name = format!("{}_{}", func_ident.to_string(), &path_as_str);
257
258            // create function name without any delimiter or special character
259            let test_name = canonical_fn_name(&test_name);
260
261            // quote! requires proc_macro2 elements
262            let test_ident = proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site());
263
264            let item = quote! {
265                #[test]
266                #[allow(non_snake_case)]
267                fn # test_ident () {
268                    # func_ident ( #path_as_str .into() );
269                }
270            };
271
272            item
273        })
274        .fold((0, func_copy), concat_ts_cnt);
275
276    // panic, the pattern did not match any file or folder
277    if result.0 == 0 {
278        panic!("no resource matching the pattern {}", &pattern);
279    }
280    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
281    result.1.into()
282}
283
284/// Macro generating bench-functions, invoking the fn for each item matching the resource-pattern.
285///
286/// The resource-pattern must not expand to empty list, otherwise an error is raised.
287/// The generated test-functions is a regular bench, being compiled by the rust-compiler; and being
288/// executed in sequentially by the bench-framework.
289/// ```ignore
290/// #![feature(test)] // nightly feature required for API test::Bencher
291///
292/// #[cfg(test)]
293/// extern crate test; /* required for test::Bencher */
294/// #[cfg(test)]
295/// extern crate test_generator;
296///
297/// #[cfg(test)]
298/// mod tests {
299///   use test_generator::bench_resources;
300///
301///   #[bench_resources("res/*/input.txt")]
302///   fn measure_resource(b: &mut test::Bencher, resource: &str) {
303///      let path = std::path::Path::new(resource);
304///      b.iter(|| path.exists());
305///   }
306/// }
307/// ```
308/// Assuming the following package layout with the bench file `mybenches.rs` and resource folder `res/`,
309/// the output below will be printed on console. The functionality of `build.rs` is explained at crate
310/// [build-deps](https://crates.io/crates/build-deps) and demonstrated with
311/// [example](https://github.com/frehberg/test-generator/tree/master/example)
312///
313/// ```ignore
314/// ├── build.rs
315/// ├── Cargo.toml
316/// ├── res
317/// │   ├── set1
318/// │   │   ├── expect.txt
319/// │   │   └── input.txt
320/// │   ├── set2
321/// │   │   ├── expect.txt
322/// │   │   └── input.txt
323/// │   └── set3
324/// │       ├── expect.txt
325/// │       └── input.txt
326/// ├── src
327/// │   └── main.rs
328/// ├── benches
329/// │   └── mybenches.rs
330/// └── tests
331///     └── mytests.rs
332/// ```
333/// Output from `cargo +nightly bench` for 3 bench-input-files matching the pattern, for this example:
334///
335/// ```ignore
336/// running 3 tests
337/// test bench::measure_resource_res_set1_input_txt ... bench:       2,492 ns/iter (+/- 4,027)
338/// test bench::measure_resource_res_set2_input_txt ... bench:       2,345 ns/iter (+/- 2,167)
339/// test bench::measure_resource_res_set3_input_txt ... bench:       2,269 ns/iter (+/- 1,527)
340///
341/// test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured; 0 filtered out
342/// ```
343#[proc_macro_attribute]
344pub fn bench_resources(attrs: TokenStream, func: TokenStream) -> TokenStream {
345    let MacroAttributes { glob_pattern } = parse_macro_input!(attrs as MacroAttributes);
346
347    let pattern = match glob_pattern {
348        Lit::Str(l) => l.value(),
349        Lit::Bool(l) => panic!("expected string parameter, got '{}'", &l.value),
350        Lit::Byte(l) => panic!("expected string parameter, got '{}'", &l.value()),
351        Lit::ByteStr(_) => panic!("expected string parameter, got byte-string"),
352        Lit::Char(l) => panic!("expected string parameter, got '{}'", &l.value()),
353        Lit::Int(l) => panic!("expected string parameter, got '{}'", &l.value()),
354        Lit::Float(l) => panic!("expected string parameter, got '{}'", &l.value()),
355        _ => panic!("expected string parameter"),
356    };
357
358    let func_copy: proc_macro2::TokenStream = func.clone().into();
359
360    let func_ast: ItemFn = syn::parse(func)
361        .expect("failed to parse tokens as a function");
362
363    let func_ident = func_ast.ident;
364
365    let paths: Paths = glob(&pattern).expect(&format!("No such file or directory {}", &pattern));
366
367    // for each path generate a test-function and fold them to single tokenstream
368    let result = paths
369        .map(|path| {
370            let path_as_str = path
371                .expect("No such file or directory")
372                .into_os_string()
373                .into_string()
374                .expect("bad encoding");
375            let test_name = format!("{}_{}", func_ident.to_string(), &path_as_str);
376
377            // create function name without any delimiter or special character
378            let test_name = canonical_fn_name(&test_name);
379
380            // quote! requires proc_macro2 elements
381            let test_ident = proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site());
382
383            let item = quote! {
384                #[bench]
385                #[allow(non_snake_case)]
386                fn # test_ident (b: &mut test::Bencher) {
387                    # func_ident ( b, #path_as_str .into() );
388                }
389            };
390
391            item
392        })
393        .fold((0, func_copy), concat_ts_cnt);
394
395    // panic, the pattern did not match any file or folder
396    if result.0 == 0 {
397        panic!("no resource matching the pattern {}", &pattern);
398    }
399
400    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
401    result.1.into()
402}
403
404
405/// **Experimental** Helper function encapsulating and unwinding each phase, namely setup, test and teardown
406//fn run_utest<U, T, D, C>(setup: U, test: T, teardown: D) -> ()
407//    where
408//        U: FnOnce() -> C + std::panic::UnwindSafe,
409//        T: FnOnce(&C) -> () + std::panic::UnwindSafe,
410//        D: FnOnce(C) -> () + std::panic::UnwindSafe,
411//        C: std::panic::UnwindSafe + std::panic::RefUnwindSafe
412//{
413//    let context = std::panic::catch_unwind(|| {
414//        setup()
415//    });
416//
417//    assert!(context.is_ok());
418//
419//    // unwrap the internal context item
420//    let ctx = match context {
421//        Ok(ctx) => ctx,
422//        Err(_) => unreachable!(),
423//    };
424//
425//    let result = std::panic::catch_unwind(|| {
426//        test(&ctx)
427//    });
428//
429//    let finalizer = std::panic::catch_unwind(|| {
430//        teardown(ctx)
431//    });
432//
433//    assert!(result.is_ok());
434//
435//    assert!(finalizer.is_ok());
436//}
437
438/// **Experimental** Executing a 3-phase unit-test: setup, test, teardown
439///
440/// ## Usage
441/// ```
442/// extern crate test_generator;
443///
444/// #[cfg(test)]
445/// mod testsuite {
446///    use test_generator::utest;
447///    use std::fs::File;
448///
449///    struct Context { file: File }
450///
451///    fn setup() -> Context {
452///
453///    }
454/// }
455/// ```
456///
457//#[macro_export]
458//macro_rules! v1_utest {
459//    ( $id: ident, $setup:expr, $test:expr, $teardown:expr ) => {
460//       #[test]
461//       fn $id() {
462//            let context = std::panic::catch_unwind(|| {
463//                $setup()
464//            });
465//
466//            assert!(context.is_ok());
467//
468//            // unwrap the internal context item
469//            let ctx = match context {
470//                Ok(ctx) => ctx,
471//                Err(_) => unreachable!(),
472//            };
473//
474//            let result = std::panic::catch_unwind(|| {
475//                $test(&ctx)
476//            });
477//
478//            let finalizer = std::panic::catch_unwind(|| {
479//                $teardown(ctx)
480//            });
481//
482//            assert!(result.is_ok());
483//
484//            assert!(finalizer.is_ok());
485//       }
486//    };
487//}
488
489//
490// ------------------ deprecated features ------------------
491//
492
493const CONTENT_MAX_LEN: usize = 100;
494
495/// Return the concatenation of two token-streams
496fn concat_ts(
497    accu: proc_macro2::TokenStream,
498    other: proc_macro2::TokenStream,
499) -> proc_macro2::TokenStream {
500    quote! { #accu #other }
501}
502
503
504/// Parser elements
505struct GlobExpand {
506    glob_pattern: Lit,
507    lambda: Ident,
508}
509
510/// Parser reading the Literal and function-identifier from token-stream
511impl Parse for GlobExpand {
512    fn parse(input: ParseStream) -> Result<Self> {
513        let glob_pattern: Lit = input.parse()?;
514        input.parse::<Token![;]>()?;
515        let lambda: Ident = input.parse()?;
516
517        Ok(GlobExpand {
518            glob_pattern,
519            lambda,
520        })
521    }
522}
523
524/// Prefix for each generated test-function
525const PREFIX: &str = "gen_";
526
527// Compose a new function-identifier from input
528fn fn_ident_from_path(fn_ident: &Ident, path: &PathBuf) -> Ident {
529    let path_as_str = path
530        .clone()
531        .into_os_string()
532        .into_string()
533        .expect("bad encoding");
534
535    // prefixed name & remove delimiters and special characters
536    let stringified = format!("{}_{}", fn_ident.to_string(), &path_as_str);
537
538    // quote! requires proc_macro2 elements
539    let gen_fn_ident = proc_macro2::Ident::new(
540        &canonical_fn_name(&stringified),
541        proc_macro2::Span::call_site(),
542    );
543
544    gen_fn_ident
545}
546
547// Compose a new function-identifier from input
548fn fn_ident_from_string(fn_ident: &Ident, name: &str) -> Ident {
549    // use at most CONTENT_MAX_LEN
550    let safe_len = std::cmp::min(name.len(), CONTENT_MAX_LEN);
551    let safe_name = &name[0..safe_len];
552
553    // prefixed name & remove delimiters and special characters
554    let stringified = format!("{}_{}", fn_ident.to_string(), safe_name);
555    // quote! requires proc_macro2 elements
556    let gen_fn_ident = proc_macro2::Ident::new(
557        &canonical_fn_name(&stringified),
558        proc_macro2::Span::call_site(),
559    );
560
561    gen_fn_ident
562}
563
564// Stringify the expression: arrays are enumerated, identifier-names are embedded
565fn expr_stringified(expr: &Expr, int_as_hex: bool) -> String {
566    let stringified = match expr {
567        Expr::Lit(lit) => match lit {
568            ExprLit {
569                lit: litval,
570                attrs: _,
571            } => match litval {
572                Lit::Int(lit) => {
573                    let val = lit.value();
574                    if int_as_hex {
575                        // if u8-range, use two digits, otherwise 16
576                        if val > 255 {
577                            // not a u8
578                            format!("{:016x}", val)
579                        } else {
580                            format!("{:02x}", val as u8)
581                        }
582                    } else {
583                        format!("{:010}", val)
584                    }
585                }
586                Lit::Char(lit) => {
587                    let val = lit.value();
588                    format!("{}", val)
589                }
590                Lit::Str(lit) => {
591                    let val = lit.value();
592                    val
593                }
594                Lit::Float(lit) => {
595                    let val = lit.value();
596                    format!("{}", val)
597                }
598                _ => panic!(),
599            },
600        },
601        Expr::Array(ref array_expr) => {
602            let elems = &array_expr.elems;
603            let mut composed = String::new();
604            // do not
605            let mut cnt: usize = 0;
606            // concat as hex-numbers, group by 8
607            for expr in elems.iter() {
608                // after 8 elements, always insert '_', do not begin with '_'
609                if cnt > 0 && cnt % 8 == 0 {
610                    composed.push_str("_");
611                }
612                cnt = cnt + 1;
613
614                let expr_str = expr_stringified(&expr, true);
615                composed.push_str(&expr_str);
616            }
617            composed
618        }
619        Expr::Path(ref expr_path) => {
620            let path = &expr_path.path;
621            let leading_colon = path.leading_colon.is_some();
622            let mut composed = String::new();
623
624            for segment in &path.segments {
625                if !composed.is_empty() || leading_colon {
626                    composed.push_str("_")
627                }
628                let ident = &segment.ident;
629                composed.push_str(&ident.to_string());
630            }
631            composed
632        }
633        Expr::Reference(ref reference) => {
634            let ref_expr = &reference.expr;
635
636            expr_stringified(&ref_expr, int_as_hex)
637        }
638        _ => panic!(),
639    };
640    stringified
641}
642
643// Compose a new function-identifier from input
644fn fn_ident_from_expr(fn_ident: &Ident, expr: &Expr) -> Ident {
645    let stringified = expr_stringified(expr, false);
646
647    fn_ident_from_string(fn_ident, &format!("{}", &stringified))
648}
649
650/// **deprecated** Function-Attribute macro expanding glob-file-pattern to a list of directories
651/// and generating a test-function for each one.
652///
653/// ```
654/// #[cfg(test)]
655/// mod tests {
656///   extern crate test_generator;
657///   test_generator::glob_expand! { "res/*"; test_exists }
658///
659///   fn test_exists(filename: &str) { assert!(std::path::Path::new(filename).exists()); }
660/// }
661/// ```
662/// The macro will expand the code for each subfolder in `"res/*"`, generating the following
663/// code. This code is not visible in IDE. Every build-time, the code will be newly generated.
664///
665///```
666/// #[cfg(test)]
667/// mod tests {
668///    #[test]
669///    fn gen_res_set1() {
670///        test_exists("res/testset1");
671///    }
672///
673///    #[test]
674///    fn gen_res_set2() {
675///        test_exists("res/testset2");
676///    }
677/// }
678///
679///```
680
681#[proc_macro]
682pub fn glob_expand(item: TokenStream) -> TokenStream {
683    let GlobExpand {
684        glob_pattern,
685        lambda,
686    } = parse_macro_input!(item as GlobExpand);
687
688    let pattern = if let Lit::Str(s) = glob_pattern {
689        s.value()
690    } else {
691        panic!();
692    };
693
694    let empty_ts: proc_macro2::TokenStream = "".parse().unwrap();
695
696    let paths: Paths = glob(&pattern).expect("Failed to read testdata dir.");
697
698    /// helper, concatting two token-streams
699    fn concat(
700        accu: proc_macro2::TokenStream,
701        ts: proc_macro2::TokenStream,
702    ) -> proc_macro2::TokenStream {
703        quote! { # accu # ts }
704    }
705
706    // for each path generate a test-function and fold them to single tokenstream
707    let result = paths
708        .map(|path| {
709            let path_as_str = path
710                .expect("No such file or directory")
711                .into_os_string()
712                .into_string()
713                .expect("bad encoding");
714
715            // remove delimiters and special characters
716            let canonical_name = path_as_str
717                .replace("\"", " ")
718                .replace(" ", "_")
719                .replace("-", "_")
720                .replace("*", "_")
721                .replace("/", "_");
722
723            // form an identifier with prefix
724            let mut func_name = PREFIX.to_string();
725            func_name.push_str(&canonical_name);
726
727            // quote! requires proc_macro2 elements
728            let func_ident = proc_macro2::Ident::new(&func_name, proc_macro2::Span::call_site());
729
730            let item = quote! {
731                # [test]
732                fn # func_ident () {
733                    let f = #lambda;
734                    f( #path_as_str );
735                }
736            };
737
738            item
739        })
740        .fold(empty_ts, concat);
741
742    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
743    result.into()
744}
745
746/// Parser elements
747struct ExpandPaths {
748    fn_ident: Ident,
749    glob_pattern: Lit,
750}
751
752/// Parser
753impl Parse for ExpandPaths {
754    fn parse(input: ParseStream) -> Result<Self> {
755        let fn_ident: Ident = input.parse()?;
756        input.parse::<Token![; ]>()?;
757        let glob_pattern: Lit = input.parse()?;
758
759        Ok(ExpandPaths {
760            glob_pattern,
761            fn_ident,
762        })
763    }
764}
765
766/// **deprecated** Generate a test-function call for each file matching the pattern
767/// ```
768/// extern crate test_generator;
769/// #[cfg(test)]
770/// mod tests {
771///   test_generator::test_expand_paths! { test_exists; "res/*" }
772///
773///   fn test_exists(dir_name: &str) { assert!(std::path::Path::new(dir_name).exists()); }
774/// }
775/// ```
776/// Assuming  `"res/*"` expands to "res/set1", and "res/set2" the macro will expand to
777///```
778/// mod tests {
779///    #[test]
780///    fn test_exists_res_set1() {
781///        test_exists("res/set1");
782///    }
783///
784///    #[test]
785///    fn test_exists_res_set2() {
786///        test_exists("res/set2");
787///    }
788/// }
789///```
790#[proc_macro]
791pub fn test_expand_paths(item: TokenStream) -> TokenStream {
792    let ExpandPaths {
793        fn_ident,
794        glob_pattern,
795    } = parse_macro_input!(item as ExpandPaths);
796
797    let pattern = if let Lit::Str(s) = glob_pattern {
798        s.value()
799    } else {
800        panic!();
801    };
802
803    let empty_ts: proc_macro2::TokenStream = "".parse().unwrap();
804
805    let paths: Paths = glob(&pattern).expect("Invalid 'paths' pattern.");
806
807    // for each path generate a test-function and fold them to single tokenstream
808    let result = paths
809        .map(|path| {
810            // check for error, shadow the name
811            let path = path.expect("No such file or directory");
812
813            // form a function identifier, each path is unique => no index required
814            let gen_fn_ident = fn_ident_from_path(&fn_ident, &path);
815
816            let path_as_str = path.into_os_string().into_string().expect("bad encoding");
817
818            let item = quote! {
819                # [test]
820                fn #gen_fn_ident () {
821                    #fn_ident ( #path_as_str );
822                }
823            };
824
825            item
826        })
827        .fold(empty_ts, concat_ts);
828
829    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
830    result.into()
831}
832
833/// **deprecated** Generate a benchmark-function call for each file matching the pattern
834/// ```
835/// extern crate test_generator;
836/// #[cfg(test)]
837/// mod tests {
838///   test_generator::bench_expand_paths! { bench_exists; "res/*" }
839///
840///   fn bench_exists(bencher: &mut test::Bencher, filename: &str) {
841///        let path = std::path::Path::new(filename);
842///        b.iter(|| { path.exists() });
843///    }
844/// }
845/// ```
846/// Assuming  `"res/*"` expands to "res/set1", and "res/set2" the macro will expand to
847///```ignore
848/// #[cfg(test)]
849/// mod tests {
850///    #[bench]
851///    fn bench_exists_res_set1(bencher: & mut test::Bencher) {
852///        bench_exists(bencher, "res/set1");
853///    }
854///
855///    #[bench]
856///    fn bench_exists_res_set2(bencher: & mut test::Bencher) {
857///        bench_exists(bencher, "res/set2");
858///    }
859/// }
860///```
861#[proc_macro]
862pub fn bench_expand_paths(item: TokenStream) -> TokenStream {
863    let ExpandPaths {
864        fn_ident,
865        glob_pattern,
866    } = parse_macro_input!(item as ExpandPaths);
867
868    let pattern = if let Lit::Str(s) = glob_pattern {
869        s.value()
870    } else {
871        panic!();
872    };
873
874    let empty_ts: proc_macro2::TokenStream = "".parse().unwrap();
875
876    let paths: Paths = glob(&pattern).expect("Invalid 'paths' pattern.");
877
878    // for each path generate a test-function and fold them to single tokenstream
879    let result = paths
880        .map(|path| {
881            // check for error, shadow the name
882            let path = path.expect("No such file or directory");
883
884            // form a function identifier, each path is unique => no index required
885            let gen_fn_ident = fn_ident_from_path(&fn_ident, &path);
886
887            let path_as_str = path.into_os_string().into_string().expect("bad encoding");
888
889            let item = quote! {
890                # [bench]
891                fn #gen_fn_ident (bencher: & mut test::Bencher) {
892                    #fn_ident (bencher, #path_as_str );
893                }
894            };
895
896            item
897        })
898        .fold(empty_ts, concat_ts);
899
900    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
901    result.into()
902}
903
904/// Parser elements
905struct ExpandList {
906    fn_ident: Ident,
907    listing: Expr,
908}
909
910/// Parser
911impl Parse for ExpandList {
912    fn parse(input: ParseStream) -> Result<Self> {
913        let fn_ident: Ident = input.parse()?;
914        input.parse::<Token![; ]>()?;
915        let listing: syn::Expr = input.parse()?;
916
917        Ok(ExpandList { fn_ident, listing })
918    }
919}
920
921/// **deprecated** Generate a test-function call for each list-element
922/// ```
923/// extern crate test_generator;
924/// #[cfg(test)]
925/// mod tests {
926///   test_generator::test_expand_list! { test_size; [ 10, 100, 1000 ]}
927///
928///   fn test_size(value: &usize) { assert!( *value > 0 ); }
929///
930///   const VEC1: &[u8] = &[ 1, 2, 3, 4 ]; /* speaking array names */
931///   const VEC2: &[u8] = &[ 5, 6, 7, 8 ];
932///   test_generator::test_expand_list! { test_array_size; [ &VEC1, &VEC2 ]}
933///   test_generator::test_expand_list! { test_array_size; [ [1, 2, 3, 4], [ 5, 6, 7, 8 ] ] }
934///
935///   fn test_array_size<T>(ar: &[T]) {
936///        assert!(ar.len() > 0);
937///   }
938/// }
939/// ```
940/// Will expand to test-functions incorporating the array-elements
941///```
942/// #[cfg(test)]
943/// mod tests {
944///    #[test]
945///    fn test_size_0000000010() { test_size(&10); }
946///    #[test]
947///    fn test_size_0000000100() { test_size(&100); }
948///    #[test]
949///    fn test_size_0000001000() { test_size(&1000); }
950///
951///    #[test]
952///    fn test_array_size_VEC1() { test_array_size( &VEC1 ); }
953///    #[test]
954///    fn test_array_size_VEC2() { test_array_size( &VEC2 ); }
955///
956///    #[test]
957///    fn test_array_size_01020304() { test_array_size( &[ 1, 2, 3, 4 ] ); }
958///    fn test_array_size_05060708() { test_array_size( &[ 5, 6, 7, 8 ] ); }
959///
960///    fn test_array_size<T>(ar: &[T]) {
961///        assert!(ar.len() > 0);
962///    }
963/// }
964///```
965#[proc_macro]
966pub fn test_expand_list(item: TokenStream) -> TokenStream {
967    let ExpandList { fn_ident, listing } = parse_macro_input!(item as ExpandList);
968
969    let expr_array = if let Expr::Array(expr_array) = listing {
970        expr_array
971    } else {
972        panic!();
973    };
974
975    let empty_ts: proc_macro2::TokenStream = "".parse().unwrap();
976
977    let elems: syn::punctuated::Punctuated<Expr, _> = expr_array.elems;
978
979    let item = elems
980        .iter()
981        .map(|expr| {
982            let gen_fn_ident = fn_ident_from_expr(&fn_ident, expr);
983            let ref_symbol_ts: proc_macro2::TokenStream = match expr {
984                Expr::Reference(_) => "".parse().unwrap(),
985                _ => "&".parse().unwrap(),
986            };
987
988            quote! {
989                #[test]
990                fn #gen_fn_ident() {
991                    let local = #ref_symbol_ts #expr;
992                    #fn_ident ( local );
993                }
994            }
995        })
996        .fold(empty_ts, concat_ts);
997
998    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
999    item.into()
1000}
1001
1002/// **deprecated** Generate a benchmark-function call for each list-element
1003/// ```
1004/// extern crate test_generator;
1005/// #[cfg(test)]
1006/// mod tests {
1007///   test_generator::bench_expand_list! { bench_size; [ 10, 100, 1000 ]}
1008///
1009///   fn bench_size(b: &mut test::Bencher, val: &usize) {
1010///      let input = val;
1011///      b.iter(|| { *input > 0 });
1012///   }
1013/// }
1014/// ```
1015/// Will expand to bench-functions incorporating the array-elements
1016///```
1017///#[cfg(test)]
1018///mod tests {
1019///    #[bench]
1020///    fn bench_size_0000000010(bencher: & mut test::Bencher) {
1021///        bench_exists(bencher, &10);
1022///    }
1023///    #[bench]
1024///    fn bench_size_0000000100(bencher: & mut test::Bencher) {
1025///        bench_exists(bencher, &100);
1026///    }
1027///    #[bench]
1028///    fn bench_size_0000001000(bencher: & mut test::Bencher) {
1029///        bench_exists(bencher, &1000);
1030///    }
1031///}
1032///```
1033#[proc_macro]
1034pub fn bench_expand_list(item: TokenStream) -> TokenStream {
1035    let ExpandList { fn_ident, listing } = parse_macro_input!(item as ExpandList);
1036
1037    let expr_array = if let Expr::Array(expr_array) = listing {
1038        expr_array
1039    } else {
1040        panic!();
1041    };
1042
1043    let empty_ts: proc_macro2::TokenStream = "".parse().unwrap();
1044
1045    let elems: syn::punctuated::Punctuated<Expr, _> = expr_array.elems;
1046
1047    let item = elems
1048        .iter()
1049        .map(|expr| {
1050            let gen_fn_ident = fn_ident_from_expr(&fn_ident, expr);
1051            let ref_symbol_ts: proc_macro2::TokenStream = match expr {
1052                Expr::Reference(_) => "".parse().unwrap(),
1053                _ => "&".parse().unwrap(),
1054            };
1055
1056            quote! {
1057                # [bench]
1058                fn #gen_fn_ident (bencher: & mut test::Bencher) {
1059                    let local = #ref_symbol_ts #expr;
1060                    #fn_ident (bencher, local );
1061                }
1062            }
1063        })
1064        .fold(empty_ts, concat_ts);
1065
1066    // transforming proc_macro2::TokenStream into proc_macro::TokenStream
1067    item.into()
1068}