test_case_derive/lib.rs
1//! # This crate is deprecated. Please use instead [test-case](https://crates.io/crates/test-case)
2//!
3//! Test-case-derive has a couple of issues which I could not clean up due to lack of time. Fortunately, [@frondeus](https://crates.io/users/frondeus) forked it and resolved all of them. The most important differences:
4//! 1) The name does not contain missleading `derive` anymore. When I've created this crate, I didn't understand proc macros enough and added this unfortunate suffix without any real reason
5//! 2) It uses way newer syn and it does not work directly on tokens anymore
6//! 3) The syntax is slightly changed, `::` is not in use anymore. Instead, `;` delimits test case name now
7//! 4) It is possible to add attributes to your test cases, e.g. `#[should_panic]` or `#[ignore]`
8//! # Overview
9//! This crate provides `#[test_case]` procedural macro attribute that generates multiple parametrized tests using one body with different input parameters.
10//! A test is generated for each data set passed in `test_case` attribute.
11//! Under the hood, all test cases that share same body are grouped into `mod`, giving clear and readable test results.
12//!
13//! [](https://crates.io/crates/test-case-derive)
14//! [](https://github.com/kbknapp/clap-rs/blob/master/LICENSE-MIT)
15//! [](https://travis-ci.org/synek317/test-case-derive)
16//!
17//! [Documentation](https://docs.rs/test-case-derive/)
18//!
19//! [Repository](https://github.com/synek317/test-case-derive)
20//!
21//! # Getting Started
22//!
23//! First of all you have to add this dependency to your `Cargo.toml`:
24//!
25//! ```toml
26//! [dev-dependencies]
27//! test-case-derive = "0.2.0"
28//! ```
29//!
30//! Additionally you have to enable `proc_macro` feature and include crate. You can do this globally by adding:
31//!
32//! ```
33//! #![feature(proc_macro)]
34//! extern crate test_case_derive;
35//! ```
36//!
37//! to your `lib.rs` or `main.rs` file. Optionally you may enable proc macros only for tests:
38//!
39//! ```
40//! #![cfg_attr(test, feature(proc_macro))]
41//! #[cfg(test)]
42//! extern crate test_case_derive;
43//! ```
44//!
45//! Don't forget that procedural macros are imported with `use` statement:
46//!
47//! ```
48//! use test_case_derive::test_case;
49//! ```
50//!
51//! # Example usage:
52//!
53//! ```
54//! #![cfg(test)]
55//! #![feature(proc_macro)]
56//! extern crate test_case_derive;
57//!
58//! use test_case_derive::test_case;
59//!
60//! #[test_case( 2, 4 :: "when both operands are possitive")]
61//! #[test_case( 4, 2 :: "when operands are swapped")]
62//! #[test_case(-2, -4 :: "when both operands are negative")]
63//! fn multiplication_tests(x: i8, y: i8) {
64//! let actual = (x * y).abs();
65//!
66//! assert_eq!(8, actual)
67//! }
68//! ```
69//!
70//! Output from `cargo test` for this example:
71//!
72//! ```
73//! $ cargo test
74//!
75//! running 3 tests
76//! test multiplication_tests::when_both_operands_are_possitive ... ok
77//! test multiplication_tests::when_both_operands_are_negative ... ok
78//! test multiplication_tests::when_operands_are_swapped ... ok
79//!
80//! test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
81//! ```
82//!
83//! # Examples
84//!
85//! If your only assertion is just `assert_eq!`, you can pass the expectation as macro attribute using `=>` syntax:
86//!
87//! ```
88//! #[test_case( 2 => 2 :: "returns given number for positive input")]
89//! #[test_case(-2 => 2 :: "returns opposite number for non-positive input")]
90//! #[test_case( 0 => 0 :: "returns 0 for 0")]
91//! fn abs_tests(x: i8) -> i8 {
92//! if x > 0 { x } else { -x }
93//! }
94//! ```
95//!
96//! Which is equivalent to
97//!
98//! ```
99//! #[test_case( 2, 2 :: "returns given number for positive input")]
100//! #[test_case(-2, 2 :: "returns opposite number for non-positive input")]
101//! #[test_case( 0, 0 :: "returns 0 for 0")]
102//! fn abs_tests(x: i8, expected: i8){
103//! let actual = if x > 0 { x } else { -x };
104//!
105//! assert_eq!(expected, actual);
106//! }
107//! ```
108//!
109//! Attributes and expectation may be any expresion unless they contain `=>`, e.g.
110//!
111//! ```
112//! #[test_case(None, None => 0 :: "treats none as 0")]
113//! #[test_case(Some(2), Some(3) => 5)]
114//! #[test_case(Some(2 + 3), Some(4) => 2 + 3 + 4)]
115//! fn fancy_addition(x: Option<i8>, y: Option<i8>) -> i8 {
116//! x.unwrap_or(0) + y.unwrap_or(0)
117//! }
118//! ```
119//!
120//! Note: in fact, `=>` is not prohibited but the parser will always treat last `=>` sign as beginning of expectation definition.
121//!
122//! Test case names are optional. They are set using `::` followed by string literal at the end of macro attributes.
123//!
124//! Example generated code:
125//!
126//! ```
127//! mod fancy_addition {
128//! #[allow(unused_imports)]
129//! use super::*;
130//!
131//! fn fancy_addition(x: Option<i8>, y: Option<i8>) -> i8 {
132//! x.unwrap_or(0) + y.unwrap_or(0)
133//! }
134//!
135//! #[test]
136//! fn treats_none_as_0() {
137//! let expected = 0;
138//! let actual = fancy_addition(None, None);
139//!
140//! assert_eq!(expected, actual);
141//! }
142//!
143//! #[test]
144//! fn some_2_some_3() {
145//! let expected = 5;
146//! let actual = fancy_addition(Some(2), Some(3));
147//!
148//! assert_eq!(expected, actual);
149//! }
150//!
151//! #[test]
152//! fn some_2_3_some_4() {
153//! let expected = 2 + 3 + 4;
154//! let actual = fancy_addition(Some(2 + 3), Some(4));
155//!
156//! assert_eq!(expected, actual);
157//! }
158//! }
159//! ```
160//!
161//! ## Inconclusive (ignored) test cases (since 0.2.0)
162//!
163//! If test case name (passed using `::` syntax described above) contains word "inconclusive", generated test will be marked with `#[ignore]`.
164//!
165//! ```
166//! #[test_case("42")]
167//! #[test_case("XX" :: "inconclusive - parsing letters temporarily doesn't work but it's ok")]
168//! fn parses_input(input: &str) {
169//! // ...
170//! }
171//! ```
172//!
173//! Generated code:
174//! ```
175//! mod parses_input {
176//! // ...
177//!
178//! #[test]
179//! pub fn _42() {
180//! // ...
181//! }
182//!
183//! #[test]
184//! #[ignore]
185//! pub fn inconclusive_parsing_letters_temporarily_doesn_t_work_but_it_s_ok() {
186//! // ...
187//! }
188//!
189//! ```
190//!
191//! **Note**: word `inconclusive` is only reserved in test name given after `::`.
192//!
193//! # Contribution
194//!
195//! All contributions and comments are more than welcome! Don't be afraid to open an issue or PR whenever you find a bug or have an idea to improve this crate.
196//!
197//! # License
198//!
199//! MIT License
200//!
201//! Copyright (c) 2017 Marcin Sas-SzymaĆski
202//!
203//! Permission is hereby granted, free of charge, to any person obtaining a copy
204//! of this software and associated documentation files (the "Software"), to deal
205//! in the Software without restriction, including without limitation the rights
206//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
207//! copies of the Software, and to permit persons to whom the Software is
208//! furnished to do so, subject to the following conditions:
209//!
210//! The above copyright notice and this permission notice shall be included in all
211//! copies or substantial portions of the Software.
212//!
213//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
214//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
215//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
216//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
217//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
218//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
219//! SOFTWARE.
220
221#![feature(proc_macro)]
222
223#[macro_use]
224extern crate lazy_static;
225#[macro_use]
226extern crate quote;
227extern crate syn;
228extern crate proc_macro;
229
230mod utils;
231mod test_case;
232mod prelude;
233
234use prelude::*;
235
236/// Generates tests for given set of data
237///
238/// In general, test case consists of four elements:
239///
240/// 1. _(Required)_ Arguments passed to test body
241/// 2. _(Optional)_ Expected result
242/// 3. _(Optional)_ Test case name
243/// 4. _(Required)_ Test body
244///
245/// When _expected result_ is provided, it is compared against the actual value generated with _test body_ using `assert_eq!`.
246/// _Test cases_ that don't provide _expected result_ should contain custom assertions inside _test body_.
247///
248/// # Examples
249///
250/// - Without result and name
251///
252/// ```
253/// #[test_case(5)]
254/// #[test_case(10)]
255/// fn is_positive(x: i8) {
256/// assert!(x > 0)
257/// }
258/// ```
259///
260/// - With name, without result
261///
262/// ```
263/// #[test_case(1 :: "little number")]
264/// #[test_case(100 :: "big number")]
265/// #[test_case(5)] // some tests may use default name generated from arguments list
266/// fn is_positive(x: i8) {
267/// assert!(x > 0)
268/// }
269/// ```
270///
271/// - With result, without name
272///
273/// ```
274/// #[test_case(1, 2 => 3)]
275/// #[test_case(-1, -2 => -3)]
276/// fn addition(x: i8, y: i8) -> i8 {
277/// x + y
278/// }
279/// ```
280///
281/// - With result and name
282///
283/// ```
284/// #[test_case(1, 2 => 3 :: "both numbers possitive")]
285/// #[test_case(-1, -2 => -3 :: "both numbers negative")]
286/// fn addition(x: i8, y: i8) -> i8 {
287/// x + y
288/// }
289/// ```
290#[proc_macro_attribute]
291pub fn test_case(attr: TokenStream, input: TokenStream) -> TokenStream {
292 let attr_string = get_attr_string(&attr);
293 let input_string = format!("#[test_case{}]{}", attr_string, input);
294 let ast = syn::parse_token_trees(&input_string);
295
296 match ast {
297 Ok(token_tree) => {
298 let test_case_suit : TestCaseSuit = token_tree.into();
299 let test_cases =
300 test_case_suit
301 .gen_test_cases()
302 .to_string();
303
304 TokenStream::from_str(&test_cases)
305 .expect(&format!("generate test cases for: {}", input_string))
306 },
307 Err(e) => panic!(e)
308 }
309}
310
311fn get_attr_string(attr: &TokenStream) -> String {
312 let result = format!("{}", attr);
313
314 if result.starts_with("(") {
315 result
316 }
317 else {
318 format!("({})", result)
319 }
320}