From b78145bfb6a6f81425dfd6fbacb9c03624e79b2c Mon Sep 17 00:00:00 2001 From: Sven Neumann Date: Fri, 16 Oct 2020 18:03:24 +0200 Subject: [PATCH 1/3] Port to recent versions of the quote, syn and proc_macro2 crates --- test-generator/Cargo.toml | 6 +++--- test-generator/src/lib.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test-generator/Cargo.toml b/test-generator/Cargo.toml index ac14d48..e7b45b0 100644 --- a/test-generator/Cargo.toml +++ b/test-generator/Cargo.toml @@ -16,6 +16,6 @@ proc-macro = true [dependencies] glob = "^0.3" -quote = "0.6" -syn = { version="^0.15", features=["full"] } -proc-macro2 = "^0.4" +quote = "1.0" +syn = { version="1.0", features=["full"] } +proc-macro2 = "1.0" diff --git a/test-generator/src/lib.rs b/test-generator/src/lib.rs index 7428d02..176592c 100644 --- a/test-generator/src/lib.rs +++ b/test-generator/src/lib.rs @@ -231,8 +231,8 @@ pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { Lit::Byte(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), Lit::ByteStr(_) => panic!("expected string parameter, got byte-string"), Lit::Char(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), - Lit::Int(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), - Lit::Float(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), + Lit::Int(l) => panic!(format!("expected string parameter, got '{}'", l)), + Lit::Float(l) => panic!(format!("expected string parameter, got '{}'", l)), _ => panic!("expected string parameter"), }; @@ -241,7 +241,7 @@ pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { let func_ast: ItemFn = syn::parse(func) .expect("failed to parse tokens as a function"); - let func_ident = func_ast.ident; + let func_ident = func_ast.sig.ident; let paths: Paths = glob(&pattern).expect(&format!("No such file or directory {}", &pattern)); @@ -351,8 +351,8 @@ pub fn bench_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { Lit::Byte(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), Lit::ByteStr(_) => panic!("expected string parameter, got byte-string"), Lit::Char(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), - Lit::Int(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), - Lit::Float(l) => panic!(format!("expected string parameter, got '{}'", &l.value())), + Lit::Int(l) => panic!(format!("expected string parameter, got '{}'", l)), + Lit::Float(l) => panic!(format!("expected string parameter, got '{}'", l)), _ => panic!("expected string parameter"), }; @@ -361,7 +361,7 @@ pub fn bench_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { let func_ast: ItemFn = syn::parse(func) .expect("failed to parse tokens as a function"); - let func_ident = func_ast.ident; + let func_ident = func_ast.sig.ident; let paths: Paths = glob(&pattern).expect(&format!("No such file or directory {}", &pattern)); @@ -572,7 +572,7 @@ fn expr_stringified(expr: &Expr, int_as_hex: bool) -> String { attrs: _, } => match litval { Lit::Int(lit) => { - let val = lit.value(); + let val: u64 = lit.base10_parse().unwrap(); if int_as_hex { // if u8-range, use two digits, otherwise 16 if val > 255 { @@ -594,7 +594,7 @@ fn expr_stringified(expr: &Expr, int_as_hex: bool) -> String { val } Lit::Float(lit) => { - let val = lit.value(); + let val: f64 = lit.base10_parse().unwrap(); format!("{}", val) } _ => panic!(), From 3d3d6eec854906c935e86bd58682a2f06a183a67 Mon Sep 17 00:00:00 2001 From: Sven Neumann Date: Thu, 15 Oct 2020 23:24:47 +0200 Subject: [PATCH 2/3] Extend syntax to allow ignore annotation on tests --- build-deps/src/lib.rs | 2 +- example/build.rs | 4 +- example/src/main.rs | 2 +- example/tests/mytests.rs | 26 ++++++++---- test-generator-utest/src/lib.rs | 18 +++------ test-generator/src/lib.rs | 72 +++++++++++++++++++++++++-------- 6 files changed, 85 insertions(+), 39 deletions(-) diff --git a/build-deps/src/lib.rs b/build-deps/src/lib.rs index 9c57740..c88bb41 100644 --- a/build-deps/src/lib.rs +++ b/build-deps/src/lib.rs @@ -20,7 +20,7 @@ //! Note: The cargo application ist storing the build-script-output in the build directory, //! for example: `target/debug/build/*/output`. extern crate glob; -use std::path::{Path}; +use std::path::Path; use self::glob::{glob, Paths}; diff --git a/example/build.rs b/example/build.rs index 7d5f21b..699dc7b 100644 --- a/example/build.rs +++ b/example/build.rs @@ -9,8 +9,8 @@ extern crate build_deps; fn main() { // Enumerate files in sub-folder "res/*", being relevant for the test-generation (as example) // If function returns with error, exit with error message. - build_deps::rerun_if_changed_paths( "res/*/*" ).unwrap(); + build_deps::rerun_if_changed_paths("res/*/*").unwrap(); // Adding the parent directory "res" to the watch-list will capture new-files being added - build_deps::rerun_if_changed_paths( "res/*" ).unwrap(); + build_deps::rerun_if_changed_paths("res/*").unwrap(); } diff --git a/example/src/main.rs b/example/src/main.rs index 0e474a7..e754911 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -4,4 +4,4 @@ fn main() { println!("Hello, world!"); -} \ No newline at end of file +} diff --git a/example/tests/mytests.rs b/example/tests/mytests.rs index c9388c8..bf59e79 100644 --- a/example/tests/mytests.rs +++ b/example/tests/mytests.rs @@ -26,7 +26,9 @@ mod tests { // } // ``` #[test_resources("res/*/input.txt")] - fn verify_resource(resource: &str) { assert!(std::path::Path::new(resource).exists()); } + fn verify_resource(resource: &str) { + assert!(std::path::Path::new(resource).exists()); + } } #[cfg(test)] @@ -40,12 +42,18 @@ mod testsuite { use test_generator_utest::utest; // Defining a context structure, storing the resources - struct Context<'t> { file: File, name: &'t str } + struct Context<'t> { + file: File, + name: &'t str, + } // Setup - Initializing the resources fn setup<'t>(filename: &str) -> Context { // unwrap may panic - Context { file: File::create(filename).unwrap(), name: filename } + Context { + file: File::create(filename).unwrap(), + name: filename, + } } // Teardown - Releasing the resources @@ -81,13 +89,17 @@ mod testsuite { std::mem::drop(file); } - utest!(hello_world, + utest!( + hello_world, || setup("/tmp/hello_world.txt"), |ctx_ref| test_write_hello_world(ctx_ref), - |ctx|teardown(ctx)); + |ctx| teardown(ctx) + ); - utest!(hello_europe, + utest!( + hello_europe, || setup("/tmp/hello_europe.txt"), test_write_hello_europe, - teardown); + teardown + ); } diff --git a/test-generator-utest/src/lib.rs b/test-generator-utest/src/lib.rs index cb3acf9..b04d935 100644 --- a/test-generator-utest/src/lib.rs +++ b/test-generator-utest/src/lib.rs @@ -97,11 +97,9 @@ #[macro_export] macro_rules! utest { ( $id: ident, $setup:expr, $test:expr, $teardown:expr ) => { - #[test] - fn $id() { - let context = std::panic::catch_unwind(|| { - $setup() - }); + #[test] + fn $id() { + let context = std::panic::catch_unwind(|| $setup()); assert!(context.is_ok()); @@ -111,17 +109,13 @@ macro_rules! utest { Err(_) => unreachable!(), }; - let result = std::panic::catch_unwind(|| { - $test(&ctx) - }); + let result = std::panic::catch_unwind(|| $test(&ctx)); - let finalizer = std::panic::catch_unwind(|| { - $teardown(ctx) - }); + let finalizer = std::panic::catch_unwind(|| $teardown(ctx)); assert!(result.is_ok()); assert!(finalizer.is_ok()); - } + } }; } diff --git a/test-generator/src/lib.rs b/test-generator/src/lib.rs index 176592c..4fd251c 100644 --- a/test-generator/src/lib.rs +++ b/test-generator/src/lib.rs @@ -61,6 +61,15 @@ //! //! test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out //! ``` +//! ## Annotating tests to be ignored +//! Sometimes tests should not be run by default, for example if they take a very +//! long time to run. Such tests would usually be annotated with #[ignore]. +//! The `test_resources` macro allows to do this by adding the `ignore` keyword +//! after the resource: +//! ```ignore +//! #[test_resources("res/*/input.txt", ignore)] +//! ``` +//! //! # Example usage `bench`: //! //! The `bench` functionality requires the nightly release of the Rust-compiler. @@ -126,7 +135,7 @@ use self::glob::{glob, Paths}; use quote::quote; use std::path::PathBuf; use syn::parse::{Parse, ParseStream, Result}; -use syn::{parse_macro_input, Expr, ExprLit, Ident, Lit, Token, ItemFn}; +use syn::{parse_macro_input, Expr, ExprLit, Ident, ItemFn, Lit, Token}; // Form canonical name without any punctuation/delimiter or special character fn canonical_fn_name(s: &str) -> String { @@ -146,22 +155,41 @@ fn concat_ts_cnt( (accu_cnt + 1, quote! { #accu_ts #other }) } +mod kw { + syn::custom_keyword!(ignore); +} + /// MacroAttributes elements struct MacroAttributes { glob_pattern: Lit, + ignore: bool, } /// MacroAttributes parser impl Parse for MacroAttributes { fn parse(input: ParseStream) -> Result { - let glob_pattern: Lit = input.parse()?; - if ! input.is_empty() { - panic!("found multiple parameters, expected one"); + let attrs = MacroAttributes { + glob_pattern: input.parse()?, + ignore: Self::parse_ignore_attribute(input)?, + }; + if input.is_empty() { + Ok(attrs) + } else { + Err(input.error("found unexpected extra parameters")) } + } +} - Ok(MacroAttributes { - glob_pattern, - }) +/// utility functions to parse MacroAttributes +impl MacroAttributes { + fn parse_ignore_attribute(input: ParseStream) -> Result { + if input.is_empty() { + Ok(false) + } else { + input.parse::()?; + input.parse::()?; + Ok(true) + } } } @@ -223,7 +251,10 @@ impl Parse for MacroAttributes { /// ``` #[proc_macro_attribute] pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { - let MacroAttributes { glob_pattern } = parse_macro_input!(attrs as MacroAttributes); + let MacroAttributes { + glob_pattern, + ignore, + } = parse_macro_input!(attrs as MacroAttributes); let pattern = match glob_pattern { Lit::Str(l) => l.value(), @@ -238,8 +269,7 @@ pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { let func_copy: proc_macro2::TokenStream = func.clone().into(); - let func_ast: ItemFn = syn::parse(func) - .expect("failed to parse tokens as a function"); + let func_ast: ItemFn = syn::parse(func).expect("failed to parse tokens as a function"); let func_ident = func_ast.sig.ident; @@ -261,9 +291,19 @@ pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { // quote! requires proc_macro2 elements let test_ident = proc_macro2::Ident::new(&test_name, proc_macro2::Span::call_site()); - let item = quote! { + let annotations = quote! { #[test] #[allow(non_snake_case)] + }; + let optional_annotation = match ignore { + true => quote! { + #[ignore] + }, + false => quote! {}, + }; + let item = quote! { + #annotations + #optional_annotation fn # test_ident () { # func_ident ( #path_as_str .into() ); } @@ -343,7 +383,10 @@ pub fn test_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn bench_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { - let MacroAttributes { glob_pattern } = parse_macro_input!(attrs as MacroAttributes); + let MacroAttributes { + glob_pattern, + ignore: _, + } = parse_macro_input!(attrs as MacroAttributes); let pattern = match glob_pattern { Lit::Str(l) => l.value(), @@ -358,8 +401,7 @@ pub fn bench_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { let func_copy: proc_macro2::TokenStream = func.clone().into(); - let func_ast: ItemFn = syn::parse(func) - .expect("failed to parse tokens as a function"); + let func_ast: ItemFn = syn::parse(func).expect("failed to parse tokens as a function"); let func_ident = func_ast.sig.ident; @@ -403,7 +445,6 @@ pub fn bench_resources(attrs: TokenStream, func: TokenStream) -> TokenStream { result.1.into() } - /// **Experimental** Helper function encapsulating and unwinding each phase, namely setup, test and teardown //fn run_utest(setup: U, test: T, teardown: D) -> () // where @@ -502,7 +543,6 @@ fn concat_ts( quote! { #accu #other } } - /// Parser elements struct GlobExpand { glob_pattern: Lit, From a38a2c7cfb709627f893704c26e5ee4dd430312a Mon Sep 17 00:00:00 2001 From: Sven Neumann Date: Fri, 16 Oct 2020 17:33:26 +0200 Subject: [PATCH 3/3] Add some tests for the MacroAttributes parser --- test-generator/src/lib.rs | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test-generator/src/lib.rs b/test-generator/src/lib.rs index 4fd251c..90fd65e 100644 --- a/test-generator/src/lib.rs +++ b/test-generator/src/lib.rs @@ -193,6 +193,60 @@ impl MacroAttributes { } } +#[cfg(test)] +mod tests { + + + use super::MacroAttributes; + use syn::{parse_quote, Attribute, Lit}; + + #[test] + fn parse_test_resources_attribute() { + let attr: Attribute = parse_quote! { + #[test_resources("res/*/input.txt")] + }; + let attrs: MacroAttributes = attr.parse_args().unwrap(); + if let Lit::Str(s) = attrs.glob_pattern { + assert_eq!(s.value(), "res/*/input.txt"); + } else { + panic!("expected string literal"); + } + assert_eq!(attrs.ignore, false); + } + + #[test] + fn parse_test_resources_attribute_with_ignore() { + let attr: syn::Attribute = parse_quote! { + #[test_resources("res/*/input.txt", ignore)] + }; + let attrs: MacroAttributes = attr.parse_args().unwrap(); + if let Lit::Str(s) = attrs.glob_pattern { + assert_eq!(s.value(), "res/*/input.txt"); + } else { + panic!("expected string literal"); + } + assert_eq!(attrs.ignore, true); + } + + #[test] + #[should_panic(expected = "expected `ignore`")] + fn parse_test_resources_attribute_with_unknown_attribute() { + let attr: syn::Attribute = parse_quote! { + #[test_resources("res/*/input.txt", unknown)] + }; + let _attrs: MacroAttributes = attr.parse_args().unwrap(); + } + + #[test] + #[should_panic(expected = "found unexpected extra parameters")] + fn parse_test_resources_attribute_with_too_many_attribute() { + let attr: syn::Attribute = parse_quote! { + #[test_resources("res/*/input.txt", ignore, fail)] + }; + let _attrs: MacroAttributes = attr.parse_args().unwrap(); + } +} + /// Macro generating test-functions, invoking the fn for each item matching the resource-pattern. /// /// The resource-pattern must not expand to empty list, otherwise an error is raised.