Import rstest_reuse 0.5.0 upstream upstream/0.5.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 20 Apr 2023 00:05:13 +0000 (09:05 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Thu, 20 Apr 2023 00:05:13 +0000 (09:05 +0900)
21 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
README.md [new file with mode: 0644]
build.rs [new file with mode: 0644]
checkoutlist.md [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
tests/acceptance.rs [new file with mode: 0644]
tests/resources/copy_args_attributes_from_template.rs [new file with mode: 0644]
tests/resources/deny_docs.rs [new file with mode: 0644]
tests/resources/export_not_used.rs [new file with mode: 0644]
tests/resources/export_template.rs [new file with mode: 0644]
tests/resources/export_template_root.rs [new file with mode: 0644]
tests/resources/import_template.rs [new file with mode: 0644]
tests/resources/in_mod.rs [new file with mode: 0644]
tests/resources/no_local_macro_should_not_compile.rs [new file with mode: 0644]
tests/resources/not_used.rs [new file with mode: 0644]
tests/resources/qualify_template_use.rs [new file with mode: 0644]
tests/resources/simple_example.rs [new file with mode: 0644]
tests/resources/templates_with_same_name.rs [new file with mode: 0644]
tests/resources/use_before_define.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..4894f04
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "b12f16cfe4e714805fdb00de6f15ecfc986ecfd8"
+  },
+  "path_in_vcs": "rstest_reuse"
+}
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..0c59879
--- /dev/null
@@ -0,0 +1,57 @@
+# 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 = "rstest_reuse"
+version = "0.5.0"
+authors = ["Michele d'Amico <michele.damico@gmail.com>"]
+description = """
+Reuse rstest attributes: create a set of tests and apply it 
+to every scenario you want to test.
+"""
+homepage = "https://github.com/la10736/rstest"
+readme = "README.md"
+keywords = [
+    "test",
+    "fixture",
+]
+categories = ["development-tools::testing"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/la10736/rstest"
+
+[lib]
+proc-macro = true
+
+[dependencies.quote]
+version = "1.0.9"
+
+[dependencies.rand]
+version = "0.8.5"
+
+[dependencies.syn]
+version = "1.0.72"
+features = ["full"]
+
+[dev-dependencies.lazy_static]
+version = "1.4.0"
+
+[dev-dependencies.rstest]
+version = "0.16.0"
+
+[dev-dependencies.rstest_test]
+version = "0.10.0"
+
+[dev-dependencies.temp_testdir]
+version = "0.2.3"
+
+[build-dependencies.rustc_version]
+version = "0.4.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..484ebdf
--- /dev/null
@@ -0,0 +1,35 @@
+[package]
+authors = ["Michele d'Amico <michele.damico@gmail.com>"]
+categories = ["development-tools::testing"]
+description = """
+Reuse rstest attributes: create a set of tests and apply it 
+to every scenario you want to test.
+"""
+edition = "2018"
+homepage = "https://github.com/la10736/rstest"
+keywords = ["test", "fixture"]
+license = "MIT/Apache-2.0"
+name = "rstest_reuse"
+readme = "README.md"
+repository = "https://github.com/la10736/rstest"
+version = "0.5.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1.0.9"
+rand = "0.8.5"
+syn = {version = "1.0.72", features = ["full"]}
+
+[dev-dependencies]
+lazy_static = "1.4.0"
+rstest_test = {version = "0.10.0", path = "../rstest_test"}
+temp_testdir = "0.2.3"
+# To compile doc tests
+rstest = {version = "0.16.0"}
+
+[build-dependencies]
+rustc_version = "0.4.0"
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..2276283
--- /dev/null
+++ b/README.md
@@ -0,0 +1,268 @@
+[![Crate][crate-image]][crate-link]
+[![Status][test-action-image]][test-action-link]
+[![Apache 2.0 Licensed][license-apache-image]][license-apache-link]
+[![MIT Licensed][license-mit-image]][license-mit-link]
+# Reuse `rstest`'s parametrized cases
+
+:warning: [**Version 0.5.0 introduce a breaking change**](#dismiss-macro_use-attribute-support)
+
+This crate give a way to define a tests set and apply them to every case you need to
+test. With `rstest` crate you can define a tests list but if you want to apply the same tests
+to another test function you must rewrite all cases or write some macros that do the job.
+
+Both solutions have some drawbreak:
+- intruduce duplication
+- macros makes code harder to read and shift out the focus from tests core
+
+The aim of this crate is solve this problem. `rstest_resuse` expose two attributes:
+- `#[template]`: to define a template
+- `#[apply]`: to apply a defined template to create tests
+
+Here is a simple example:
+
+```rust
+use rstest::rstest;
+use rstest_reuse::{self, *};
+// Here we define the template. This define
+// * The test list name to `two_simple_cases`
+// * cases: here two cases
+#[template]
+#[rstest]
+#[case(2, 2)]
+#[case(4/2, 2)]
+// Define a and b as cases arguments
+fn two_simple_cases(#[case] a: u32, #[case] b: u32) {}
+// Here we apply the `two_simple_cases` template: That is expanded in
+// #[template]
+// #[rstest]
+// #[case(2, 2)]
+// #[case(4/2, 2)]
+// fn it_works(#[case] a: u32,#[case] b: u32) {
+//     assert!(a == b);
+// }
+#[apply(two_simple_cases)]
+fn it_works(a: u32, b: u32) {
+    assert!(a == b);
+}
+// Here we reuse the `two_simple_cases` template to create two 
+// other tests
+#[apply(two_simple_cases)]
+fn it_fail(a: u32, b: u32) {
+    assert!(a != b);
+}
+```
+
+If we run `cargo test` we have:
+
+```text
+    Finished test [unoptimized + debuginfo] target(s) in 0.05s
+     Running target/debug/deps/playground-8a1212f8b5eb00ce
+running 4 tests
+test it_fail::case_1 ... FAILED
+test it_works::case_1 ... ok
+test it_works::case_2 ... ok
+test it_fail::case_2 ... FAILED
+failures:
+---- it_fail::case_1 stdout ----
+thread 'it_fail::case_1' panicked at 'assertion failed: a != b', src/main.rs:34:5
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+---- it_fail::case_2 stdout ----
+thread 'it_fail::case_2' panicked at 'assertion failed: a != b', src/main.rs:34:5
+failures:
+    it_fail::case_1
+    it_fail::case_2
+test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out
+error: test failed, to rerun pass '--bin playground'
+```
+
+Simple and neat!
+
+Note that if the test arguments names match the template's ones you can don't 
+repeate the arguments attributes.
+
+## Composition and Values
+
+If you need to add some cases or values when apply a template you can leverage on
+composition. Here a simple example:
+
+```rust
+#[template]
+#[rstest]
+#[case(2, 2)]
+#[case(4/2, 2)]
+fn base(#[case] a: u32, #[case] b: u32) {}
+
+// Here we add a new case and an argument in a value list:
+#[apply(base)]
+#[case(9/3, 3)]
+fn it_works(a: u32, b: u32, #[values("a", "b")] t: &str) {
+    assert!(a == b);
+    assert!("abcd".contains(t))
+}
+```
+
+run 6 tests:
+
+```
+running 6 tests
+test it_works::case_1::t_2 ... ok
+test it_works::case_2::t_2 ... ok
+test it_works::case_2::t_1 ... ok
+test it_works::case_3::t_2 ... ok
+test it_works::case_3::t_1 ... ok
+test it_works::case_1::t_1 ... ok
+```
+
+Template can also used for values and with arguments if you need:
+
+```rust
+#[template]
+#[rstest]
+fn base(#[with(42)] fix: u32, #[values(1,2,3)] v: u32) {}
+
+#[fixture]
+fn fix(#[default(0)] inner: u32) -> u32 {
+    inner
+}
+
+#[apply(base)]
+fn use_it_with_fixture(fix: u32, v: u32) {
+    assert!(fix%v == 0);
+}
+
+#[apply(base)]
+fn use_it_without_fixture(v: u32) {
+    assert!(24 % v == 0);
+}
+```
+
+Run also 6 tests:
+
+```
+running 6 tests
+test use_it_with_fixture::v_1 ... ok
+test use_it_without_fixture::v_1 ... ok
+test use_it_with_fixture::v_3 ... ok
+test use_it_without_fixture::v_2 ... ok
+test use_it_without_fixture::v_3 ... ok
+test use_it_with_fixture::v_2 ... ok
+```
+
+## Cavelets
+
+### `use rstest_resuse` at the top of your crate
+You **should** add `use rstest_resuse` at the top of your crate:
+
+```rust
+#[cfg(test)]
+use rstest_reuse;
+```
+
+This is due `rstest_reuse::template` define a macro that need to call a `rstest_resuse`'s macro.
+I hope to remove this in the future but for now we should live with it.
+
+Note that
+
+```rust
+use rstest_reuse::*;
+```
+is not enougth: this statment doesn't include `rstest_reuse` but just its public items.
+
+## `#[export]` Attribute
+
+:warning: **Version 0.5.0 introduce a breaking change**
+
+Now `#[export]` attribute give you the possibility to export your template across crates
+but don't lift the macro definition at the top of your crate (that was the default behaviour 
+prior the 0.5.0 version).
+
+Now if you want put your template at the root of your crate you can define it in the root
+module or reexport it at the top with something like the following line at the top of
+your crate:
+
+```rust
+pub use my::modules::path::of::my::template::my_template;
+```
+
+When you want to export your template you should also take care to declare `rstest_reuse` as `pub`
+at the top of your crate to enable to use it from the modules that would import the template.
+
+So in this case in the crate that would export template you should put at the root of your
+crate
+
+```rust
+#[cfg(test)]
+pub use rstest_reuse;
+```
+
+And not just `use rstest_reuse` like in the standard cases.
+
+## Dismiss `#[macro_use]` Attribute Support
+
+:warning: **Version 0.5.0 introduce a breaking change**
+
+Till version 0.4.0 you can use `#[macro_use]` to annotate your modules and lift your
+macro template to the up level. Now `rstest` leverege only on import and paths like all
+othter function and original macro is hidden by a random name.
+
+So now if you would use your template from other module you should import it like any 
+other symbol.
+
+```rust
+mod inner {
+    pub(crate) mod sub {
+        use rstest_reuse::*;
+        #[template]
+        #[rstest(a,  b,
+            case(2, 2),
+            case(4/2, 2),
+            )
+        ]
+        fn two_simple_cases(a: u32, b: u32) {}
+    }
+}
+use rstest_reuse::*;
+use rstest::*;
+
+#[apply(inner::sub::two_simple_cases)]
+fn it_works_by_path(a: u32, b: u32) {
+    assert!(a == b);
+}
+
+use inner::sub::two_simple_cases
+#[apply(inner::sub::two_simple_cases)]
+fn it_works_after_use(a: u32, b: u32) {
+    assert!(a == b);
+}
+
+```
+
+## Disclamer
+
+This crate is in a development stage. I don't know if I'll include it in `rstest` or change some syntax in the future.
+
+I did't test it in a lot of cases: if you have some cases where it doesn't works file a ticket on [`rstest`][rstest-link]
+
+
+## License
+
+Licensed under either of
+
+* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
+[license-apache-link])
+
+* MIT license [LICENSE-MIT](LICENSE-MIT) or [license-MIT-link]
+at your option.
+
+[//]: # (links)
+
+[crate-image]: https://img.shields.io/crates/v/rstest_reuse.svg
+[crate-link]: https://crates.io/crates/rstest_reuse
+[test-action-image]: https://github.com/la10736/rstest/workflows/Test/badge.svg
+[test-action-link]: https://github.com/la10736/rstest/actions?query=workflow:Test
+[license-apache-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg
+[license-mit-image]: https://img.shields.io/badge/license-MIT-blue.svg
+[license-apache-link]: http://www.apache.org/licenses/LICENSE-2.0
+[license-MIT-link]: http://opensource.org/licenses/MIT
+[rstest-link]: https://github.com/la10736/rstest
diff --git a/build.rs b/build.rs
new file mode 100644 (file)
index 0000000..e6d6328
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,10 @@
+use rustc_version::{Version, version};
+
+fn main() {
+    let ver = version().unwrap();
+    assert!(ver.major >= 1);
+
+    if ver <= Version::parse("1.50.0").unwrap() {
+        println!("cargo:rustc-cfg=sanitize_multiple_should_panic_compiler_bug");
+    }
+}
diff --git a/checkoutlist.md b/checkoutlist.md
new file mode 100644 (file)
index 0000000..ebc1f0b
--- /dev/null
@@ -0,0 +1,15 @@
+# TODO list
+
+- [ ] Update rustup
+- [ ] Update dependency `cargo upgrade`
+- [ ] Run all test
+  - [ ] Stable: `RSTEST_TEST_CHANNEL=stable; cargo +${RSTEST_TEST_CHANNEL} test`
+  - [ ] Beta: `RSTEST_TEST_CHANNEL=beta; cargo +${RSTEST_TEST_CHANNEL} test`
+  - [ ] Nightly: `RSTEST_TEST_CHANNEL=nightly; cargo +${RSTEST_TEST_CHANNEL} test`
+- [ ] Check Cargo.toml version
+- [ ] Check README
+- [ ] prepare deploy `cargo publish --dry-run`
+- [ ] deploy `cargo publish`
+- [ ] Update `rstest` dependency
+- [ ] Change next version
+  - [ ] `Cargo.toml`
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..5372aab
--- /dev/null
@@ -0,0 +1,455 @@
+//! # Reuse `rstest`'s parametrized cases
+//!
+//! This crate give a way to define a tests set and apply them to every case you need to
+//! test.
+//!
+//! With `rstest` crate you can define a tests list but if you want to apply the same tests
+//! to another test function you must rewrite all cases or write some macros that do the job.
+//! Both solutions have some drawbreak:
+//!
+//! - introduce duplication
+//! - macros makes code harder to read and shift out the focus from tests core
+//!
+//! The aim of this crate is solve this problem. `rstest_reuse` expose two attributes:
+//!
+//! - `#[template]`: to define a template
+//! - `#[apply]`: to apply a defined template to create tests
+//!
+//! Here is a simple example:
+//!
+//! ```
+//! use rstest::rstest;
+//! use rstest_reuse::{self, *};
+//!
+//! // Here we define the template. This define
+//! // * The test list name to `two_simple_cases`
+//! // * cases: here two cases that feed the `a`, `b` values
+//! #[template]
+//! #[rstest]
+//! #[case(2, 2)]
+//! #[case(4/2, 2)]
+//! fn two_simple_cases(#[case] a: u32,#[case] b: u32) {}
+//!
+//! // Here we apply the `two_simple_cases` template: That is expanded in
+//! // #[rstest]
+//! // #[case(2, 2)]
+//! // #[case(4/2, 2)]
+//! // fn it_works(#[case] a: u32,#[case] b: u32) {
+//! //     assert!(a == b);
+//! // }
+//! #[apply(two_simple_cases)]
+//! fn it_works(a: u32, b: u32) {
+//!     assert!(a == b);
+//! }
+//!
+//!
+//! // Here we reuse the `two_simple_cases` template to create two
+//! // other tests
+//! #[apply(two_simple_cases)]
+//! fn it_fail(a: u32, b: u32) {
+//!     assert!(a != b);
+//! }
+//! ```
+//! If we run `cargo test` we have:
+//!
+//! ```text
+//!     Finished test [unoptimized + debuginfo] target(s) in 0.05s
+//!      Running target/debug/deps/playground-8a1212f8b5eb00ce
+//!
+//! running 4 tests
+//! test it_fail::case_1 ... FAILED
+//! test it_works::case_1 ... ok
+//! test it_works::case_2 ... ok
+//! test it_fail::case_2 ... FAILED
+//!
+//! failures:
+//!
+//! ---- it_fail::case_1 stdout ----
+//! thread 'it_fail::case_1' panicked at 'assertion failed: a != b', src/main.rs:34:5
+//! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+//!
+//! ---- it_fail::case_2 stdout ----
+//! thread 'it_fail::case_2' panicked at 'assertion failed: a != b', src/main.rs:34:5
+//!
+//!
+//! failures:
+//!     it_fail::case_1
+//!     it_fail::case_2
+//!
+//! test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out
+//!
+//! error: test failed, to rerun pass '--bin playground'
+//! ```
+//!
+//! Simple and neat!
+//!
+//! Note that if the test arguments names match the template's ones you can don't
+//! repeate the arguments attributes.
+//!
+//! ## Composition and Values
+//!
+//! If you need to add some cases or values when apply a template you can leverage on
+//! composition. Here a simple example:
+//!
+//! ```
+//! use rstest::rstest;
+//! use rstest_reuse::{self, *};
+//!
+//! #[template]
+//! #[rstest]
+//! #[case(2, 2)]
+//! #[case(4/2, 2)]
+//! fn base(#[case] a: u32, #[case] b: u32) {}
+//!
+//! // Here we add a new case and an argument in a value list:
+//! #[apply(base)]
+//! #[case(9/3, 3)]
+//! fn it_works(a: u32, b: u32, #[values("a", "b")] t: &str) {
+//!     assert!(a == b);
+//!     assert!("abcd".contains(t))
+//! }
+//! ```
+//!
+//! `cargo test` runs 6 tests:
+//!
+//! ```text
+//! running 6 tests
+//! test it_works::case_1::t_2 ... ok
+//! test it_works::case_2::t_2 ... ok
+//! test it_works::case_2::t_1 ... ok
+//! test it_works::case_3::t_2 ... ok
+//! test it_works::case_3::t_1 ... ok
+//! test it_works::case_1::t_1 ... ok
+//! ```
+//!
+//! Template can also used for `#[values]` and `#[with]` arguments if you need:
+//!
+//! ```
+//! use rstest::*;
+//! use rstest_reuse::{self, *};
+//!
+//! #[template]
+//! #[rstest]
+//! fn base(#[with(42)] fix: u32, #[values(1,2,3)] v: u32) {}
+//!
+//! #[fixture]
+//! fn fix(#[default(0)] inner: u32) -> u32 {
+//!     inner
+//! }
+//!
+//! #[apply(base)]
+//! fn use_it_with_fixture(fix: u32, v: u32) {
+//!     assert!(fix%v == 0);
+//! }
+//!
+//! #[apply(base)]
+//! fn use_it_without_fixture(v: u32) {
+//!     assert!(24 % v == 0);
+//! }
+//! ```
+//!
+//! `cargo test` runs 6 tests:
+//!
+//! ```text
+//! running 6 tests
+//! test use_it_with_fixture::v_1 ... ok
+//! test use_it_without_fixture::v_1 ... ok
+//! test use_it_with_fixture::v_3 ... ok
+//! test use_it_without_fixture::v_2 ... ok
+//! test use_it_without_fixture::v_3 ... ok
+//! test use_it_with_fixture::v_2 ... ok
+//! ```
+//!
+//!
+//! ## Cavelets
+//!
+//! ### `use rstest_reuse` at the top of your crate
+//!
+//! You **should** add `use rstest_reuse` at the top of your crate:
+//!
+//! ```
+//! #[cfg(test)]
+//! use rstest_reuse;
+//! ```
+//!
+//! This is due `rstest_reuse::template` define a macro that need to call a `rstest_reuse`'s macro.
+//! I hope to remove this in the future but for now we should live with it.
+//!
+//! Note that
+//! ```
+//! use rstest_reuse::*;
+//! ```
+//! is not enougth: this statment doesn't include `rstest_reuse` but just its public items.
+//!
+//! ## Disclamer
+//!
+//! This crate is in developer stage. I don't know if I'll include it in `rstest` or changing some syntax in
+//! the future.
+//!
+//! I did't test it in a lot of cases: if you have some cases where it doesn't works file a ticket on
+//! [`rstest`](https://github.com/la10736/rstest)
+
+extern crate proc_macro;
+use std::collections::HashMap;
+
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{
+    self, parse, parse::Parse, parse_macro_input, Attribute, Ident, ItemFn, PatType, Path, Token,
+};
+
+struct MergeAttrs {
+    template: ItemFn,
+    function: ItemFn,
+}
+
+impl Parse for MergeAttrs {
+    fn parse(input: parse::ParseStream) -> syn::Result<Self> {
+        let template = input.parse()?;
+        let _comma: Token![,] = input.parse()?;
+        let function = input.parse()?;
+        Ok(Self { template, function })
+    }
+}
+
+#[cfg(sanitize_multiple_should_panic_compiler_bug)]
+fn is_should_panic(attr: &syn::Attribute) -> bool {
+    let should_panic: Ident = syn::parse_str("should_panic").unwrap();
+    attr.path.is_ident(&should_panic)
+}
+
+#[cfg(sanitize_multiple_should_panic_compiler_bug)]
+fn sanitize_should_panic_duplication_bug(
+    mut attributes: Vec<syn::Attribute>,
+) -> Vec<syn::Attribute> {
+    if attributes.len() != 2 || attributes[0] != attributes[1] || !is_should_panic(&attributes[0]) {
+        // Nothing to do
+        return attributes;
+    }
+    attributes.pop();
+    attributes
+}
+
+fn collect_template_args(template: &ItemFn) -> HashMap<&Ident, &PatType> {
+    template
+        .sig
+        .inputs
+        .iter()
+        .filter_map(|arg| match arg {
+            syn::FnArg::Typed(a) => Some(a),
+            _ => None,
+        })
+        .filter_map(|arg| match *arg.pat {
+            syn::Pat::Ident(ref id) => Some((&id.ident, arg)),
+            _ => None,
+        })
+        .collect()
+}
+
+fn merge_arg_attributes(dest: &mut Vec<Attribute>, source: &[Attribute]) {
+    for s in source.iter() {
+        if !dest.contains(s) {
+            dest.push(s.clone())
+        }
+    }
+}
+
+fn resolve_template_arg<'a>(
+    template: &HashMap<&'a Ident, &'a PatType>,
+    arg: &Ident,
+) -> Option<&'a PatType> {
+    let id_name = arg.to_string();
+    match (template.get(arg), id_name.starts_with('_')) {
+        (Some(&arg), _) => Some(arg),
+        (None, true) => template.get(&format_ident!("{}", id_name[1..])).copied(),
+        _ => None,
+    }
+}
+
+fn expand_function_arguments(dest: &mut ItemFn, source: &ItemFn) {
+    let to_merge_args = collect_template_args(source);
+
+    for arg in dest.sig.inputs.iter_mut() {
+        if let syn::FnArg::Typed(a) = arg {
+            if let syn::Pat::Ident(ref id) = *a.pat {
+                if let Some(source_arg) = resolve_template_arg(&to_merge_args, &id.ident) {
+                    merge_arg_attributes(&mut a.attrs, &source_arg.attrs);
+                }
+            }
+        }
+    }
+}
+
+#[doc(hidden)]
+#[proc_macro]
+pub fn merge_attrs(item: TokenStream) -> TokenStream {
+    let MergeAttrs {
+        template,
+        mut function,
+    } = parse_macro_input!(item as MergeAttrs);
+
+    expand_function_arguments(&mut function, &template);
+
+    let mut attrs = template.attrs;
+    #[cfg(sanitize_multiple_should_panic_compiler_bug)]
+    {
+        function.attrs = sanitize_should_panic_duplication_bug(function.attrs);
+    }
+    attrs.append(&mut function.attrs);
+    function.attrs = attrs;
+
+    let tokens = quote! {
+        #function
+    };
+    tokens.into()
+}
+
+fn get_export(attributes: &[Attribute]) -> Option<&Attribute> {
+    attributes
+        .iter()
+        .find(|&attr| attr.path.is_ident(&format_ident!("export")))
+}
+
+/// Define a template where the name is given from the function name. This attribute register all
+/// attributes. The function signature don't really mater but to make it clear is better that you
+/// use a signature like if you're wrinting a standard `rstest`.
+///
+/// If you need to export the template at the root of your crate or use it from another crate you
+/// should annotate it with `#[export]` attribute. This attribute add `#[macro_export]` attribute to
+/// the template macro and make possible to use it from another crate.
+///
+/// When define a template you can also set the arguments attributes like `#[case]`, `#[values]`
+/// and `#[with]`: when you apply it attributes will be copied to the matched by name arguments.
+///
+#[proc_macro_attribute]
+pub fn template(_args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> TokenStream {
+    let mut template: ItemFn = parse(input).unwrap();
+
+    let rstest_index = template
+        .attrs
+        .iter()
+        .position(|attr| attr.path.is_ident(&format_ident!("rstest")));
+
+    let mut attributes = template.attrs;
+
+    template.attrs = match rstest_index {
+        Some(idx) => attributes.split_off(idx),
+        None => std::mem::take(&mut attributes),
+    };
+
+    let (macro_attribute, visibility) = match get_export(&attributes) {
+        Some(_) => (
+            quote! {
+                #[macro_export]
+            },
+            quote! {
+                pub
+            },
+        ),
+        None => (
+            quote! {},
+            quote! {
+                pub(crate)
+            },
+        ),
+    };
+
+    let macro_name = template.sig.ident.clone();
+    let macro_name_rand = format_ident!("{}_{}", macro_name, rand::random::<u64>());
+
+    let tokens = quote! {
+        /// Apply #macro_name template to given body
+        #macro_attribute
+        macro_rules! #macro_name_rand {
+            ( $test:item ) => {
+                        $crate::rstest_reuse::merge_attrs! {
+                            #template,
+                            $test
+                        }
+                    }
+        }
+        #[allow(unused_imports)]
+        #visibility use #macro_name_rand as #macro_name;
+    };
+    tokens.into()
+}
+
+/// Apply a defined template. The function signature should satisfy the template attributes
+/// but can also add some other fixtures.
+/// Example:
+///
+/// ```
+/// use rstest::{rstest, fixture};
+/// use rstest_reuse::{self, *};
+///
+/// #[fixture]
+/// fn empty () -> Vec<u32> {
+///     Vec::new()    
+/// }
+///
+/// #[template]
+/// #[rstest]
+/// #[case(2, 2)]
+/// #[case(4/2, 2)]
+/// fn two_simple_cases(#[case] a: u32, #[case] b: u32) {}
+///
+/// #[apply(two_simple_cases)]
+/// fn it_works(mut empty: Vec<u32>, a: u32, b: u32) {
+///     empty.append(a);
+///     assert!(empty.last() == b);
+/// }
+/// ```
+/// When use `#[apply]` you can also
+/// 1. Use a path for template
+/// 2. Ignore an argument by underscore
+/// 3. add some cases
+/// 4. add some values
+///
+///
+/// ```
+/// use rstest::{rstest, fixture};
+/// use rstest_reuse::{self, *};
+///
+/// #[fixture]
+/// fn fix (#[default(0)] inner: u32) -> u32 {
+///     inner
+/// }
+///
+/// mod outer {
+///     pub(crate) mod inner {
+///         use rstest_reuse::template;
+///
+///         #[template]
+///         #[rstest]
+///         #[case(2, 2)]
+///         #[case(4/2, 2)]
+///         fn two_simple_cases(#[case] a: u32, #[case] b: u32) {}
+///     }
+/// }
+///
+///
+/// #[apply(outer::inner::two_simple_cases)]
+/// // Add a case
+/// #[case(9/3, 3)]
+/// // Use fixture with 42 as argument
+/// // Ignore b case values
+/// // add 2 cases with other in 4, 5 for each case
+/// fn lot_of_tests(fix: u32, a: u32, _b: u32, #[values(4, 5)] other: u32) {
+///     assert_eq!(fix, 42);
+///     assert_eq!(a, 2);
+///     assert!([4, 5].contains(other));
+/// }
+/// ```
+///
+
+#[proc_macro_attribute]
+pub fn apply(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> TokenStream {
+    let template: Path = parse(args).unwrap();
+    let test: ItemFn = parse(input).unwrap();
+    let tokens = quote! {
+        #template! {
+            #test
+        }
+    };
+    tokens.into()
+}
diff --git a/tests/acceptance.rs b/tests/acceptance.rs
new file mode 100644 (file)
index 0000000..0309c03
--- /dev/null
@@ -0,0 +1,189 @@
+use rstest_test::{
+    assert_in, assert_not_in, sanitize_name, testname, Project, Stringable, TestResults,
+};
+
+use lazy_static::lazy_static;
+
+use rstest::rstest;
+use std::path::{Path, PathBuf};
+use temp_testdir::TempDir;
+
+pub fn resources<O: AsRef<Path>>(name: O) -> PathBuf {
+    Path::new("tests").join("resources").join(name)
+}
+
+fn create_prj(name: &str) -> Project {
+    let prj = ROOT_PROJECT.subproject(name);
+    prj.add_local_dependency("rstest_reuse");
+    prj.add_dependency("rstest", r#""*""#);
+    prj
+}
+
+fn prj(res: impl AsRef<Path>) -> Project {
+    let prj_name = sanitize_name(testname());
+
+    let prj = create_prj(&prj_name);
+    prj.set_code_file(resources(res))
+}
+
+fn run_test(res: impl AsRef<Path>) -> (std::process::Output, String) {
+    let prj = prj(res);
+    (
+        prj.run_tests().unwrap(),
+        prj.get_name().to_owned().to_string(),
+    )
+}
+
+#[test]
+fn simple_example() {
+    let (output, _) = run_test("simple_example.rs");
+
+    TestResults::new()
+        .ok("it_works::case_1")
+        .ok("it_works::case_2")
+        .fail("it_fail::case_1")
+        .fail("it_fail::case_2")
+        .ok("it_fail_but_ok::case_1")
+        .ok("it_fail_but_ok::case_2")
+        .assert(output);
+}
+
+#[test]
+fn use_before_define() {
+    let (output, _) = run_test("use_before_define.rs");
+
+    TestResults::new()
+        .ok("it_works::case_1")
+        .ok("it_works::case_2")
+        .assert(output);
+}
+
+#[rstest]
+#[case::simple("simple_example.rs")]
+#[case::export_not_used("export_not_used.rs")]
+fn not_show_any_warning(#[case] path: &str) {
+    let (output, _) = run_test(path);
+
+    assert_not_in!(output.stderr.str(), "warning:");
+}
+
+#[test]
+fn should_show_warning_if_not_used_template() {
+    let (output, _) = run_test("not_used.rs");
+
+    assert_in!(output.stderr.str(), "warning:");
+}
+
+#[test]
+fn in_mod() {
+    let (output, _) = run_test("in_mod.rs");
+
+    TestResults::new()
+        .ok("sub::it_works::case_1")
+        .ok("sub::it_works::case_2")
+        .fail("sub::it_fail::case_1")
+        .fail("sub::it_fail::case_2")
+        .assert(output);
+}
+
+#[test]
+fn import_from_mod() {
+    let (output, _) = run_test("qualify_template_use.rs");
+
+    TestResults::new()
+        .ok("user::it_works::case_1")
+        .ok("user::it_works::case_2")
+        .ok("qualify::it_works::case_1")
+        .ok("qualify::it_works::case_2")
+        .assert(output);
+}
+
+#[test]
+fn copy_case_attributes_from_template() {
+    let (output, _) = run_test("copy_args_attributes_from_template.rs");
+
+    TestResults::new()
+        .ok("cases::it_works::case_1")
+        .ok("cases::it_works::case_2")
+        .ok("cases::should_not_copy_attributes_if_already_present::case_1")
+        .ok("cases::should_not_copy_attributes_if_already_present::case_2")
+        .ok("cases::add_a_case::case_1")
+        .ok("cases::add_a_case::case_2")
+        .ok("cases::add_a_case::case_3_more")
+        .ok_in("cases::add_values::case_1::_add_some_tests_1")
+        .ok_in("cases::add_values::case_1::_add_some_tests_2")
+        .ok_in("cases::add_values::case_1::_add_some_tests_3")
+        .ok_in("cases::add_values::case_2::_add_some_tests_1")
+        .ok_in("cases::add_values::case_2::_add_some_tests_2")
+        .ok_in("cases::add_values::case_2::_add_some_tests_3")
+        .ok("cases::should_copy_cases_also_from_underscored_attrs::case_1")
+        .ok("cases::should_copy_cases_also_from_underscored_attrs::case_2")
+        .ok_in("values::it_works::cases_1")
+        .ok_in("values::it_works::cases_2")
+        .ok_in("values::add_a_case::case_1_more::cases_1")
+        .ok_in("values::add_a_case::case_1_more::cases_2")
+        .ok_with("values::add_values::a_1", false, 2)
+        .ok_with("values::add_values::a_2", false, 2)
+        .ok_in("values::should_copy_values_also_from_underscored_attrs::_cases_1")
+        .ok_in("values::should_copy_values_also_from_underscored_attrs::_cases_2")
+        .assert(output);
+}
+
+#[test]
+fn deny_docs() {
+    let (output, _) = run_test("deny_docs.rs");
+
+    TestResults::new()
+        .ok("it_works::case_1")
+        .ok("it_works::case_2")
+        .assert(output);
+}
+
+#[test]
+fn enable_export_macros() {
+    let (output, _) = run_test("export_template.rs");
+
+    TestResults::new()
+        .ok("foo::bar::test::case_1")
+        .ok("test_path::case_1")
+        .ok("test_import::case_1")
+        .assert(output);
+}
+
+#[test]
+fn use_same_name_for_more_templates() {
+    let (output, _) = run_test("templates_with_same_name.rs");
+
+    TestResults::new()
+        .ok("inner1::it_works::case_1")
+        .ok("inner1::it_works::case_2")
+        .ok("inner2::it_works::case_1")
+        .ok("inner2::it_works::case_2")
+        .assert(output);
+}
+
+#[test]
+fn no_local_macro_should_not_compile() {
+    let (output, _) = run_test("no_local_macro_should_not_compile.rs");
+
+    assert!(!output.status.success());
+}
+
+#[test]
+fn should_export_main_root() {
+    // Add project with template
+    let _prj_template =
+        create_prj("export_template_root").set_code_file(resources("export_template_root.rs"));
+
+    // Main test project that use template
+    let prj = prj("import_template.rs");
+    prj.add_path_dependency("export_template_root", "../export_template_root");
+
+    let output = prj.run_tests().unwrap();
+    TestResults::new().ok("test::case_1").assert(output);
+}
+
+lazy_static! {
+    static ref ROOT_DIR: TempDir = TempDir::new(std::env::temp_dir().join("rstest_reuse"), false);
+    static ref ROOT_PROJECT: Project = Project::new(ROOT_DIR.as_ref());
+}
diff --git a/tests/resources/copy_args_attributes_from_template.rs b/tests/resources/copy_args_attributes_from_template.rs
new file mode 100644 (file)
index 0000000..39347a7
--- /dev/null
@@ -0,0 +1,66 @@
+use rstest_reuse;
+
+mod cases {
+    use rstest::rstest;
+    use rstest_reuse::*;
+
+    #[template]
+    #[rstest]
+    #[case(2, 2)]
+    #[case(4/2, 2)]
+    fn copy_cases(#[case] a: u32, #[case] b: u32) {}
+
+    #[apply(copy_cases)]
+    fn it_works(a: u32, b: u32) {
+        assert!(a == b);
+    }
+
+    #[apply(copy_cases)]
+    fn should_not_copy_attributes_if_already_present(#[case] a: u32, b: u32) {
+        assert!(a == b);
+    }
+
+    #[apply(copy_cases)]
+    #[case::more(8/4, 2)]
+    fn add_a_case(a: u32, b: u32) {
+        assert!(a == b);
+    }
+
+    #[apply(copy_cases)]
+    fn add_values(a: u32, b: u32, #[values(1, 2, 3)] _add_some_tests: u32) {
+        assert!(a == b);
+    }
+
+    #[apply(copy_cases)]
+    fn should_copy_cases_also_from_underscored_attrs(_a: u32, _b: u32) {}
+}
+
+mod values {
+    use rstest::rstest;
+    use rstest_reuse::*;
+
+    #[template]
+    #[rstest]
+    fn copy_values(#[values(1, 2)] cases: u32) {}
+
+    #[apply(copy_values)]
+    fn it_works(cases: u32) {
+        assert!([1, 2].contains(&cases));
+    }
+
+    #[apply(copy_values)]
+    #[case::more(8/4, 2)]
+    fn add_a_case(#[case] a: u32, #[case] b: u32, cases: u32) {
+        assert!([1, 2].contains(&cases));
+        assert!(a == b);
+    }
+
+    #[apply(copy_values)]
+    fn add_values(#[values(3, 4)] a: u32, cases: u32) {
+        assert!([1, 2].contains(&cases));
+        assert!([3, 4].contains(&a));
+    }
+
+    #[apply(copy_values)]
+    fn should_copy_values_also_from_underscored_attrs(_cases: u32) {}
+}
diff --git a/tests/resources/deny_docs.rs b/tests/resources/deny_docs.rs
new file mode 100644 (file)
index 0000000..04dda7a
--- /dev/null
@@ -0,0 +1,13 @@
+#![deny(missing_docs)]
+
+use rstest::rstest;
+use rstest_reuse::{self, *};
+
+#[template]
+#[rstest(a,  b, case(2, 2), case(4/2, 2))]
+fn two_simple_cases(a: u32, b: u32) {}
+
+#[apply(two_simple_cases)]
+fn it_works(a: u32, b: u32) {
+    assert!(a == b);
+}
diff --git a/tests/resources/export_not_used.rs b/tests/resources/export_not_used.rs
new file mode 100644 (file)
index 0000000..b58ec19
--- /dev/null
@@ -0,0 +1,9 @@
+mod foo {
+    use rstest_reuse::template;
+
+    #[template]
+    #[export]
+    #[rstest]
+    #[case("bar")]
+    fn not_used(#[case] s: &str) {}
+}
diff --git a/tests/resources/export_template.rs b/tests/resources/export_template.rs
new file mode 100644 (file)
index 0000000..534eae9
--- /dev/null
@@ -0,0 +1,39 @@
+use rstest_reuse;
+
+mod foo {
+    pub(crate) mod bar {
+        use rstest::rstest;
+        use rstest_reuse::{self, *};
+
+        #[template]
+        #[export]
+        #[rstest]
+        #[case("bar")]
+        fn my_template(#[case] s: &str) {}
+
+        #[apply(my_template)]
+        fn test(#[case] s: &str) {
+            assert_eq!("bar", s);
+        }
+    }
+}
+
+use rstest::rstest;
+use rstest_reuse::*;
+
+#[apply(foo::bar::my_template)]
+fn test_path(#[case] s: &str) {
+    assert_eq!("bar", s);
+}
+
+use foo::bar::my_template;
+#[apply(my_template)]
+fn test_import(#[case] s: &str) {
+    assert_eq!("bar", s);
+}
+
+#[template]
+#[export]
+#[rstest]
+#[case("bar")]
+fn root_level(#[case] s: &str) {}
diff --git a/tests/resources/export_template_root.rs b/tests/resources/export_template_root.rs
new file mode 100644 (file)
index 0000000..8c7a33a
--- /dev/null
@@ -0,0 +1,8 @@
+pub use rstest_reuse;
+use rstest_reuse::template;
+
+#[template]
+#[export]
+#[rstest]
+#[case("bar")]
+fn root_level(#[case] s: &str) {}
diff --git a/tests/resources/import_template.rs b/tests/resources/import_template.rs
new file mode 100644 (file)
index 0000000..129b52d
--- /dev/null
@@ -0,0 +1,8 @@
+use export_template_root::root_level;
+use rstest::*;
+use rstest_reuse::apply;
+
+#[apply(root_level)]
+fn test(#[case] s: &str) {
+    assert_eq!("bar", s);
+}
diff --git a/tests/resources/in_mod.rs b/tests/resources/in_mod.rs
new file mode 100644 (file)
index 0000000..80f0b36
--- /dev/null
@@ -0,0 +1,20 @@
+use rstest_reuse;
+
+mod sub {
+    use rstest::rstest;
+    use rstest_reuse::*;
+
+    #[template]
+    #[rstest(a,  b, case(2, 2), case(4/2, 2))]
+    fn two_simple_cases(a: u32, b: u32) {}
+
+    #[apply(two_simple_cases)]
+    fn it_works(a: u32, b: u32) {
+        assert!(a == b);
+    }
+
+    #[apply(two_simple_cases)]
+    fn it_fail(a: u32, b: u32) {
+        assert!(a != b);
+    }
+}
diff --git a/tests/resources/no_local_macro_should_not_compile.rs b/tests/resources/no_local_macro_should_not_compile.rs
new file mode 100644 (file)
index 0000000..b45d6df
--- /dev/null
@@ -0,0 +1,17 @@
+use rstest_reuse;
+
+mod foo {
+    use rstest_reuse::{self, *};
+
+    #[template]
+    #[rstest]
+    #[case("bar")]
+    fn my_template(#[case] s: &str) {}
+}
+use rstest::rstest;
+use rstest_reuse::apply;
+
+#[apply(my_template)]
+fn test(#[case] s: &str) {
+    assert_eq!("bar", s);
+}
diff --git a/tests/resources/not_used.rs b/tests/resources/not_used.rs
new file mode 100644 (file)
index 0000000..7fbb04d
--- /dev/null
@@ -0,0 +1,8 @@
+mod foo {
+    use rstest_reuse::template;
+
+    #[template]
+    #[rstest]
+    #[case("bar")]
+    fn not_used(#[case] s: &str) {}
+}
diff --git a/tests/resources/qualify_template_use.rs b/tests/resources/qualify_template_use.rs
new file mode 100644 (file)
index 0000000..1f4b5ef
--- /dev/null
@@ -0,0 +1,31 @@
+use rstest_reuse;
+
+mod template {
+    use rstest::rstest;
+    use rstest_reuse::template;
+
+    #[template]
+    #[rstest(a,  b, case(2, 2), case(4/2, 2))]
+    fn two_simple_cases(a: u32, b: u32) {}
+}
+
+mod user {
+    use rstest::rstest;
+    use rstest_reuse::apply;
+    use crate::template::two_simple_cases;
+
+    #[apply(two_simple_cases)]
+    fn it_works(a: u32, b: u32) {
+        assert!(a == b);
+    }
+}
+
+mod qualify {
+    use rstest::rstest;
+    use rstest_reuse::apply;
+
+    #[apply(crate::template::two_simple_cases)]
+    fn it_works(a: u32, b: u32) {
+        assert!(a == b);
+    }
+}
diff --git a/tests/resources/simple_example.rs b/tests/resources/simple_example.rs
new file mode 100644 (file)
index 0000000..01388f2
--- /dev/null
@@ -0,0 +1,22 @@
+use rstest::rstest;
+use rstest_reuse::{self, *};
+
+#[template]
+#[rstest(a,  b, case(2, 2), case(4/2, 2))]
+fn two_simple_cases(a: u32, b: u32) {}
+
+#[apply(two_simple_cases)]
+fn it_works(a: u32, b: u32) {
+    assert!(a == b);
+}
+
+#[apply(two_simple_cases)]
+fn it_fail(a: u32, b: u32) {
+    assert!(a != b);
+}
+
+#[apply(two_simple_cases)]
+#[should_panic]
+fn it_fail_but_ok(a: u32, b: u32) {
+    assert!(a != b);
+}
diff --git a/tests/resources/templates_with_same_name.rs b/tests/resources/templates_with_same_name.rs
new file mode 100644 (file)
index 0000000..7a3554a
--- /dev/null
@@ -0,0 +1,27 @@
+use rstest_reuse;
+
+mod inner1 {
+    use rstest::rstest;
+    use rstest_reuse::*;
+
+    #[template]
+    #[rstest(a,  b, case(2, 2), case(4/2, 2))]
+    fn my_template(a: u32, b: u32) {}
+
+    #[apply(my_template)]
+    fn it_works(a: u32, b: u32) {
+        assert!(a == b);
+    }
+}
+
+mod inner2 {
+    use rstest::rstest;
+    use rstest_reuse::*;
+
+    #[template]
+    #[rstest(a, case(2), case(4))]
+    fn my_template(a: u32) {}
+
+    #[apply(my_template)]
+    fn it_works(a: u32) {}
+}
diff --git a/tests/resources/use_before_define.rs b/tests/resources/use_before_define.rs
new file mode 100644 (file)
index 0000000..e5c0adb
--- /dev/null
@@ -0,0 +1,11 @@
+use rstest::rstest;
+use rstest_reuse::{self, *};
+
+#[apply(two_simple_cases)]
+fn it_works(a: u32, b: u32) {
+    assert!(a == b);
+}
+
+#[template]
+#[rstest(a,  b, case(2, 2), case(4/2, 2))]
+fn two_simple_cases(a: u32, b: u32) {}