ntest_test_cases-0.8.0/Cargo.toml0000644000000021320000000000100124070ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "ntest_test_cases" version = "0.8.0" authors = ["Armin Becher "] description = "Test cases for ntest framework." documentation = "https://docs.rs/ntest" readme = "README.md" keywords = [ "test", "tests", "unit", "testing", "test-cases", ] categories = [ "development-tools", "development-tools::testing", ] license = "MIT" repository = "https://github.com/becheran/ntest" [lib] name = "ntest_test_cases" proc-macro = true [dependencies.proc-macro2] version = "1.0" [dependencies.quote] version = "1.0" [dependencies.syn] version = "1.0" features = ["full"] ntest_test_cases-0.8.0/Cargo.toml.orig000064400000000000000000000011450072674642500161230ustar 00000000000000[package] name = "ntest_test_cases" version = "0.8.0" authors = [ "Armin Becher ",] edition = "2018" description = "Test cases for ntest framework." keywords = [ "test", "tests", "unit", "testing", "test-cases",] categories = [ "development-tools", "development-tools::testing",] readme = "README.md" license = "MIT" repository = "https://github.com/becheran/ntest" documentation = "https://docs.rs/ntest" [lib] name = "ntest_test_cases" proc-macro = true [dependencies] quote = "1.0" proc-macro2 = "1.0" [dependencies.syn] version = "1.0" features = [ "full",] ntest_test_cases-0.8.0/LICENSE000064400000000000000000000021000072674642500142310ustar 00000000000000MIT License Copyright (c) 2019 Armin Becher Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.ntest_test_cases-0.8.0/README.md000064400000000000000000000013630072674642500145150ustar 00000000000000# NTest TestCases Part of the [NTest library](https://crates.io/crates/ntest). Add test cases to the rust test framework using [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html). ## Examples Example with a single argument: ```rust #[test_case(13)] #[test_case(42)] fn one_arg(x: u32) { assert!(x == 13 || x == 42) } ``` The test cases above will be parsed at compile time and two rust test functions will be generated instead: ```rust #[test] fn one_arg_13() { x = 13; assert!(x == 13 || x == 42) } #[test] fn one_arg_42() { x = 42; assert!(x == 13 || x == 42) } ``` For more examples and information read the [documentation](https://docs.rs/ntest_test_cases/). ntest_test_cases-0.8.0/src/lib.rs000064400000000000000000000236770072674642500151550ustar 00000000000000//! Part of the ntest library. Add test cases to the rust test framework. extern crate proc_macro; extern crate syn; use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; use syn::parse_macro_input; mod syn_helper; /// Test cases can be used to have multiple inputs for a given function. /// With the `#[test_case]` attribute multiple tests will be generated using the /// [Procedural Macros](https://blog.rust-lang.org/2018/12/21/Procedural-Macros-in-Rust-2018.html) /// capabilities of rust. /// /// The function input can be of type `int`, `bool`, or `str`. /// /// Please note that rust functions can only contain alphanumeric characters and '_' signs. /// Special characters will be escaped using a meaning full replacement (for example `#` will be replaced with `_hash`), /// or as a default the '_' sign. /// /// WARNING! /// It is currently not possible to have negative numbers as macro input. For example /// this `#[test_case(-13)]` will not work. /// /// A function annotated with a `#[test_case]` attribute will be split into multiple rust functions annotated with the `#[test]` attribute. /// /// # Examples /// /// Example with a single argument /// ```ignore /// #[test_case(13)] /// #[test_case(42)] /// fn one_arg(x: u32) { /// assert!(x == 13 || x == 42) /// } /// ``` /// /// The test cases above will be parsed at compile time and two rust test functions will be generated instead: /// ```ignore /// #[test] /// fn one_arg_13() { /// x = 13; /// assert!(x == 13 || x == 42) /// } /// /// #[test] /// fn one_arg_42() { /// x = 42; /// assert!(x == 13 || x == 42) /// } /// ``` /// /// Example with multiple arguments: /// ```ignore /// #[test_case(true, "true", 1)] /// fn test_mix(x: bool, y: &str, z: u16) { /// assert!(x); /// assert_eq!(y, "true"); /// assert_eq!(z, 1); /// } /// ``` /// /// Example with name attribute: /// ```ignore /// #[test_case(42, name="my_fancy_test")] /// fn with_name(x: u32) { /// assert_eq!(x, 42) /// } /// ``` /// /// Example with rust test attributes. /// All attributes after a test case will be appended after the generated `#[test]` attribute. /// For example the following test cases... /// /// ```ignore /// #[test_case(18)] /// #[ignore] /// #[test_case(15)] /// #[should_panic(expected = "I am panicing")] /// fn attributes_test_case(x: u32) { /// panic!("I am panicing"); /// } /// ``` /// /// ... will be compiled to these two tests. One gets ignored and the other suceeds: /// /// ```ignore /// #[test] /// #[ignore] /// fn attributes_test_case_18 { /// let x = 18; /// panic!("I am panicing"); /// } /// /// #[test] /// #[should_panic(expected = "I am panicing")] /// fn attributes_test_case_15() { /// let x = 15; /// panic!("I am panicing"); /// } /// ``` /// /// Test functions with a `Result` return are also supported: /// /// ```ignore /// #[test_case(27)] /// #[test_case(33)] /// fn returns_result(x: u32) -> Result<(), ()> { /// Ok(()) /// } /// ``` #[proc_macro_attribute] pub fn test_case(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as syn::ItemFn); let attribute_args = parse_macro_input!(attr as syn::AttributeArgs); let test_descriptions: Vec = collect_test_descriptions(&input, &attribute_args); let fn_body = &input.block; let fn_args_idents = collect_function_arg_idents(&input); let fn_return = &input.sig.output; let mut result = proc_macro2::TokenStream::new(); for test_description in test_descriptions { let test_case_name = syn::Ident::new(&test_description.name, Span::call_site()); let literals = test_description.literals; let attributes = test_description.attributes; if literals.len() != fn_args_idents.len() { panic!("Test case arguments and function input signature mismatch."); } let test_case_quote = quote! { #[test] #(#attributes)* fn #test_case_name() #fn_return { #(let #fn_args_idents = #literals;)* #fn_body } }; result.extend(test_case_quote); } result.into() } fn collect_function_arg_idents(input: &syn::ItemFn) -> Vec { let mut fn_args_idents: Vec = vec![]; let fn_args = &input.sig.inputs; for i in fn_args { match i { syn::FnArg::Typed(t) => { let ubox_t = *(t.pat.clone()); match ubox_t { syn::Pat::Ident(i) => { fn_args_idents.push(i.ident.clone()); } _ => panic!("Unexpected function identifier."), } } syn::FnArg::Receiver(_) => { panic!("Receiver function not expected for test case attribute.") } } } fn_args_idents } struct TestDescription { literals: Vec, name: String, attributes: Vec, } fn collect_test_descriptions( input: &syn::ItemFn, attribute_args: &syn::AttributeArgs, ) -> Vec { let mut test_case_descriptions: Vec = vec![]; let fn_name = input.sig.ident.to_string(); let test_case_parameter = parse_test_case_attributes(attribute_args); let test_name = calculate_test_name(&test_case_parameter, &fn_name); let curr_test_attributes = TestDescription { literals: test_case_parameter.literals, name: test_name, attributes: vec![], }; test_case_descriptions.push(curr_test_attributes); for attribute in &input.attrs { let meta = attribute.parse_meta(); match meta { Ok(m) => match m { syn::Meta::Path(p) => { let identifier = p.get_ident().expect("Expected identifier!"); if identifier == "test_case" { panic!("Test case attributes need at least one argument such as #[test_case(42)]."); } else { test_case_descriptions .last_mut() .unwrap() .attributes .push(attribute.clone()); } } syn::Meta::List(ml) => { let identifier = ml.path.get_ident().expect("Expected identifier!"); if identifier == "test_case" { let argument_args: syn::AttributeArgs = ml.nested.into_iter().collect(); let test_case_parameter = parse_test_case_attributes(&argument_args); let test_name = calculate_test_name(&test_case_parameter, &fn_name); let curr_test_attributes = TestDescription { literals: test_case_parameter.literals, name: test_name, attributes: vec![], }; test_case_descriptions.push(curr_test_attributes); } else { test_case_descriptions .last_mut() .unwrap() .attributes .push(attribute.clone()); } } syn::Meta::NameValue(_) => { test_case_descriptions .last_mut() .unwrap() .attributes .push(attribute.clone()); } }, Err(e) => panic!("Could not determine meta data. Error {}.", e), } } test_case_descriptions } struct TestCaseAttributes { literals: Vec, custom_name: Option, } fn parse_test_case_attributes(attr: &syn::AttributeArgs) -> TestCaseAttributes { let mut literals: Vec = vec![]; let mut custom_name: Option = None; for a in attr { match a { syn::NestedMeta::Meta(m) => match m { syn::Meta::Path(_) => { panic!("Path not expected."); } syn::Meta::List(_) => { panic!("Metalist not expected."); } syn::Meta::NameValue(nv) => { let identifier = nv.path.get_ident().expect("Expected identifier!"); if identifier == "test_name" || identifier == "name" { if custom_name.is_some() { panic!("Test name can only be defined once."); } match &nv.lit { syn::Lit::Str(_) => { custom_name = Some(syn_helper::lit_to_str(&nv.lit)); } _ => unimplemented!("Unexpected type for test name. Expected string."), } } else { panic!("Unexpected identifier '{}'", identifier) } } }, syn::NestedMeta::Lit(lit) => { literals.push((*lit).clone()); } } } TestCaseAttributes { literals, custom_name, } } fn calculate_test_name(attr: &TestCaseAttributes, fn_name: &str) -> String { let mut name = "".to_string(); match &attr.custom_name { None => { name.push_str(fn_name); for lit in &attr.literals { name.push_str(&format!("_{}", syn_helper::lit_to_str(lit))); } } Some(custom_name) => name = custom_name.to_string(), } name } ntest_test_cases-0.8.0/src/syn_helper.rs000064400000000000000000000045710072674642500165470ustar 00000000000000pub fn lit_to_str(lit: &syn::Lit) -> String { match lit { syn::Lit::Bool(s) => s.value.to_string(), syn::Lit::Str(s) => string_to_identifier(&s.value()), syn::Lit::Int(s) => number_to_identifier(s.base10_digits()), syn::Lit::Float(s) => number_to_identifier(s.base10_digits()), _ => unimplemented!("String conversion for literal. Only bool, str, positive int, and float values are supported."), } } fn number_to_identifier(num: &str) -> String { num.chars() .map(|x| match x { '.' => 'd', '0'..='9' => x, _ => panic!("This is not a valid number. Contains unknown sign {}", x), }) .collect() } fn string_to_identifier(num: &str) -> String { num.chars() .map(|x| match x { '0'..='9' => x.to_string(), 'a'..='z' => x.to_string(), 'A'..='Z' => x.to_string(), '!' => "_exclamation".to_string(), '"' => "_double_quote".to_string(), '#' => "_hash".to_string(), '$' => "_dollar".to_string(), '%' => "_percent".to_string(), '&' => "_ampercand".to_string(), '\'' => "_quote".to_string(), '(' => "_left_paranthesis".to_string(), ')' => "_right_paranthesis".to_string(), '*' => "_asterisk".to_string(), '+' => "_plus".to_string(), ',' => "_comma".to_string(), '-' => "_minus".to_string(), '.' => "_full_stop".to_string(), '/' => "_slash".to_string(), ':' => "_colon".to_string(), ';' => "_semicolon".to_string(), '<' => "_less_than".to_string(), '=' => "_equal".to_string(), '>' => "_greater_than".to_string(), '?' => "_questionmark".to_string(), '@' => "_at".to_string(), '[' => "_left_bracket".to_string(), '\\' => "_back_slash".to_string(), ']' => "_right_bracket".to_string(), '^' => "_caret".to_string(), '`' => "_backtick".to_string(), '{' => "_left_brace".to_string(), '|' => "_vertical_bar".to_string(), '}' => "_right_brace".to_string(), '~' => "_tilde".to_string(), _ => '_'.to_string(), }) .collect() } ntest_test_cases-0.8.0/tests/test_cases.rs000064400000000000000000000024220072674642500171000ustar 00000000000000extern crate ntest_test_cases; use ntest_test_cases::test_case; #[test_case(42)] fn one_arg(x: u32) { assert_eq!(x, 42) } #[test_case(1, 42)] #[test_case(9, 18)] #[test_case(5, 20)] fn two_args(x: u8, y: u32) { assert!(x < 10); assert!(y > 10); } #[test_case(42.42)] fn float(x: f64) { assert_eq!(x, 42.42) } #[test_case("walter", "white")] fn test_string(x: &str, y: &str) { assert_eq!(x, "walter"); assert_eq!(y, "white"); } #[test_case("-390)(#$*Q)")] fn test_string_special_chars(x: &str) { assert_eq!(x, "-390)(#$*Q)"); } #[test_case(true)] fn test_bool(x: bool) { assert!(x); } #[test_case(true, "true", 1)] fn test_mix(x: bool, y: &str, z: u16) { assert!(x); assert_eq!(y, "true"); assert_eq!(z, 1); } #[test_case(42, name="my_fancy_test")] fn with_name(x: u32) { assert_eq!(x, 42) } #[test_case(42, name="my_snd_fancy_testSPECIALCHARS^$(*")] fn with_name(x: u32) { assert_eq!(x, 42) } #[test_case(18)] #[ignore] #[test_case(15)] #[should_panic(expected = "I am panicing")] fn attributes_test_case(x: u32) { panic!("I am panicing {}", x); } #[test_case(42)] fn return_result(x: u32) -> core::result::Result<(), ()> { assert_eq!(x, 42); Ok(()) }