Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build-deps/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
4 changes: 2 additions & 2 deletions example/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
2 changes: 1 addition & 1 deletion example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

fn main() {
println!("Hello, world!");
}
}
26 changes: 19 additions & 7 deletions example/tests/mytests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
Expand Down Expand Up @@ -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
);
}
18 changes: 6 additions & 12 deletions test-generator-utest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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());
}
}
};
}
6 changes: 3 additions & 3 deletions test-generator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
142 changes: 118 additions & 24 deletions test-generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -146,22 +155,95 @@ 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<Self> {
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<bool> {
if input.is_empty() {
Ok(false)
} else {
input.parse::<Token![,]>()?;
input.parse::<kw::ignore>()?;
Ok(true)
}
}
}

#[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();
}
}

Expand Down Expand Up @@ -223,25 +305,27 @@ 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(),
Lit::Bool(l) => panic!(format!("expected string parameter, got '{}'", &l.value)),
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"),
};

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.ident;
let func_ident = func_ast.sig.ident;

let paths: Paths = glob(&pattern).expect(&format!("No such file or directory {}", &pattern));

Expand All @@ -261,9 +345,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() );
}
Expand Down Expand Up @@ -343,25 +437,27 @@ 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(),
Lit::Bool(l) => panic!(format!("expected string parameter, got '{}'", &l.value)),
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"),
};

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.ident;
let func_ident = func_ast.sig.ident;

let paths: Paths = glob(&pattern).expect(&format!("No such file or directory {}", &pattern));

Expand Down Expand Up @@ -403,7 +499,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<U, T, D, C>(setup: U, test: T, teardown: D) -> ()
// where
Expand Down Expand Up @@ -502,7 +597,6 @@ fn concat_ts(
quote! { #accu #other }
}


/// Parser elements
struct GlobExpand {
glob_pattern: Lit,
Expand Down Expand Up @@ -572,7 +666,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 {
Expand All @@ -594,7 +688,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!(),
Expand Down