diff --git a/Cargo.toml b/Cargo.toml index 7b523d87419ee5bad123d6d4998ae2771e56e171..37be44b154ab108febb862ed7c3ad2189dc688ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,5 @@ -[package] -name = "ntest" -version = "0.1.0" -authors = ["Armin Becher "] -edition = "2018" -description = "Testing framework for rust which enhances the built-in library with some useful features." -keywords = ["test", "tests", "unit", "testing"] -categories = ["development-tools", "development-tools::testing"] -readme = "README.md" -license = "MIT" -license-file = "LICENSE" -repository = "https://gitlab.com/becheran/ntest" -documentation = "https://docs.rs/ntest" - -[badges] -gitlab = { repository = "https://gitlab.com/becheran/ntest", branch = "master" } -maintenance = { status = "actively-developed" } - -[profile.dev] -panic = "unwind" - -[lib] -path = "src/lib.rs" - -[dependencies] +[workspace] +members = [ + "ntest", + "ntest_test_cases", +] diff --git a/ntest/Cargo.toml b/ntest/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..76b3207c56333d501d7d4f1250ed7f10d53690bd --- /dev/null +++ b/ntest/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ntest" +version = "0.1.3" +authors = ["Armin Becher "] +edition = "2018" +description = "Testing framework for rust which enhances the built-in library with some useful features." +keywords = ["test", "tests", "unit", "testing"] +categories = ["development-tools", "development-tools::testing"] +readme = "README.md" +license-file = "LICENSE" +repository = "https://gitlab.com/becheran/ntest" +documentation = "https://docs.rs/ntest" + +[badges] +gitlab = { repository = "https://gitlab.com/becheran/ntest", branch = "master" } +maintenance = { status = "actively-developed" } + +[profile.dev] +panic = "unwind" + +[lib] +path = "src/lib.rs" + + +[dependencies] +ntest_test_cases = { version = "0.1.3", path = "../ntest_test_cases" } + +[dev-dependencies] +ntest_test_cases = { version = "0.1.3", path = "../ntest_test_cases" } \ No newline at end of file diff --git a/ntest/LICENSE b/ntest/LICENSE new file mode 120000 index 0000000000000000000000000000000000000000..ea5b60640b01f74e295037aa8a6b7d4ea278a739 --- /dev/null +++ b/ntest/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/ntest/README.md b/ntest/README.md new file mode 120000 index 0000000000000000000000000000000000000000..32d46ee883b58d6a383eed06eb98f33aa6530ded --- /dev/null +++ b/ntest/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/src/lib.rs b/ntest/src/lib.rs similarity index 95% rename from src/lib.rs rename to ntest/src/lib.rs index 3aae96bc7d59a5acbdaa409f9be2ee4521ecbc29..397ba5f5084763942324d4c9d745a1fee9040027 100644 --- a/src/lib.rs +++ b/ntest/src/lib.rs @@ -81,4 +81,8 @@ macro_rules! assert_panics { let result = std::panic::catch_unwind(||$x); assert!(result.is_err()); }); -} \ No newline at end of file +} + +// Reexport procedural macros +extern crate ntest_test_cases; +pub use ntest_test_cases::*; \ No newline at end of file diff --git a/ntest_test_cases/Cargo.toml b/ntest_test_cases/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6ce5496b4c01c6546667748574915953f050fd75 --- /dev/null +++ b/ntest_test_cases/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "ntest_test_cases" +version = "0.1.3" +authors = ["Armin Becher "] +edition = "2018" +description = "Test cases for ntest framwork." +keywords = ["test", "tests", "unit", "testing", "test-cases"] +categories = ["development-tools", "development-tools::testing"] +readme = "README.md" +license-file = "LICENSE" +repository = "https://gitlab.com/becheran/ntest" +documentation = "https://docs.rs/ntest" + +[lib] +name = "ntest_test_cases" +proc-macro = true + +[dependencies] +syn = {version = "0.15.44",features = ["full"]} +quote = "0.6" +proc-macro2 = "0.4" \ No newline at end of file diff --git a/ntest_test_cases/LICENSE b/ntest_test_cases/LICENSE new file mode 120000 index 0000000000000000000000000000000000000000..ea5b60640b01f74e295037aa8a6b7d4ea278a739 --- /dev/null +++ b/ntest_test_cases/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/ntest_test_cases/README.md b/ntest_test_cases/README.md new file mode 120000 index 0000000000000000000000000000000000000000..32d46ee883b58d6a383eed06eb98f33aa6530ded --- /dev/null +++ b/ntest_test_cases/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/ntest_test_cases/src/lib.rs b/ntest_test_cases/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..0fb11731070927643b936fc29eb61e8682d57357 --- /dev/null +++ b/ntest_test_cases/src/lib.rs @@ -0,0 +1,123 @@ +extern crate proc_macro; +extern crate syn; + +use proc_macro2::Span; +use syn::parse_macro_input; +use proc_macro::TokenStream; +use quote::quote; +use syn::export::TokenStream2; + +#[proc_macro_attribute] +pub fn test_case(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut test_case_descriptions: Vec = vec![]; + let attr = parse_macro_input!(attr as syn::AttributeArgs); + let input = parse_macro_input!(item as syn::ItemFn); + + // Collect test case descriptions + test_case_descriptions.push(parse_test_case_attributes(&attr)); + for at in &input.attrs { + let meta = at.parse_meta(); + match meta { + Ok(m) => { + match m { + syn::Meta::List(ml) => { + if ml.ident != "test_case" { + panic!("Only test_case attributes expected, but found {:?}", ml.ident); + } + let argument_args: syn::AttributeArgs = ml.nested.into_iter().collect(); + test_case_descriptions.push(parse_test_case_attributes(&argument_args)); + } + syn::Meta::Word(i) => { + panic!("Wrong input {:?} for test cases", i) + } + syn::Meta::NameValue(_) => { + unimplemented!("Need to check for named values"); + } + } + } + Err(e) => panic!("Could not determine meta data. Error {}", e) + } + } + + let fn_args = &input.decl.inputs; + let fn_body = &input.block; + let mut fn_args_idents: Vec = vec![]; + + for i in fn_args { + match i { + syn::FnArg::Captured(c) => { + match &c.pat { + syn::Pat::Ident(ident) => { + fn_args_idents.push(ident.ident.clone()); + } + _ => panic!("Unexpected function identifier.") + } + } + _ => panic!("Unexpected function identifier.") + } + } + + let mut result = TokenStream2::new(); + for test_case_description in test_case_descriptions { + let test_case_name = syn::Ident::new( + &format!("{}{}", &input.ident.to_string(), &test_case_description.name), + Span::call_site(), + ); + let literals = test_case_description.literals; + if &literals.len() != &fn_args_idents.len() { + panic!("Test case arguments and function input signature do not match"); + } + + // Needs to be immutable + let fn_args_idents = fn_args_idents.clone(); + + let test_case_quote = quote! { + #[test] + fn #test_case_name() { + let x = 42; + #(let #fn_args_idents = #literals;)* + #fn_body + } + }; + result.extend(test_case_quote); + } + result.into() +} + + +struct TestCaseDescription { + literals: Vec, + name: String, + // TODO add Meta attributes expected_result +} + +fn parse_test_case_attributes(attr: &syn::AttributeArgs) -> TestCaseDescription { + let mut literals: Vec = vec![]; + let mut name = "".to_string(); + + for a in attr { + match a { + syn::NestedMeta::Meta(_) => { + unimplemented!("Need to check for named values"); + } + syn::NestedMeta::Literal(lit) => { + literals.push((*lit).clone()); + name.push_str(&format!("_{}", lit_to_str(lit))); + } + } + } + + TestCaseDescription { + literals, + name, + } +} + +fn lit_to_str(lit: &syn::Lit) -> String { + match lit { + syn::Lit::Bool(s) => s.value.to_string(), + syn::Lit::Str(s) => s.value().to_string(), + syn::Lit::Int(s) => s.value().to_string(), + _ => unimplemented!(), + } +} \ No newline at end of file diff --git a/ntest_test_cases/tests/test_cases.rs b/ntest_test_cases/tests/test_cases.rs new file mode 100644 index 0000000000000000000000000000000000000000..73b36ae0006131f9caea992d7a0839c74fcebf9c --- /dev/null +++ b/ntest_test_cases/tests/test_cases.rs @@ -0,0 +1,15 @@ +extern crate ntest_test_cases; +#[doc(hidden)] +pub use ntest_test_cases::test_case; + +#[test_case(42)] +fn one_arg(x: u32) { + assert_eq!(x, 42) +} + + +#[test_case(13, 42)] +fn two_args(x: u32, y: u32) { + assert_eq!(x, 13); + assert_eq!(y, 42); +}