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//! [](https://crates.io/crates/test-generator)
7//! [](https://github.com/frehberg/test-generator/blob/master/LICENSE-MIT)
8//! [](https://github.com/frehberg/test-generator/blob/master/LICENSE-APACHE)
9//! [](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}